Add checks to nus content subcommand, and improve error handling

This commit is contained in:
Campbell 2024-07-11 21:11:58 +10:00
parent 8599c43c2d
commit 183498025a
Signed by: NinjaCheetah
GPG Key ID: 670C282B3291D63D
2 changed files with 44 additions and 10 deletions

View File

@ -142,28 +142,52 @@ def handle_nus_content(args):
tid = args.tid tid = args.tid
cid = args.cid cid = args.cid
version = args.version version = args.version
out = args.output
if args.decrypt: if args.decrypt:
decrypt_content = True decrypt_content = True
else: else:
decrypt_content = False decrypt_content = False
content_id = int.from_bytes(binascii.unhexlify(cid)) # Only accepting the 000000xx format because it's the one that would be most commonly known, rather than using the
# actual integer that the hex Content ID translates to.
try:
content_id = int.from_bytes(binascii.unhexlify(cid))
except binascii.Error:
print("Invalid Content ID! Content ID must be in format \"000000xx\"!")
return
content_file_name = hex(content_id)[2:] # Use the supplied output path if one was specified, otherwise generate one using the Content ID.
while len(content_file_name) < 8: if out is None:
content_file_name = "0" + content_file_name content_file_name = hex(content_id)[2:]
while len(content_file_name) < 8:
content_file_name = "0" + content_file_name
output_path = pathlib.Path(content_file_name)
else:
output_path = pathlib.Path(out)
content_data = libWiiPy.title.download_content(tid, content_id) # Try to download the content, and catch the ValueError libWiiPy will throw if it can't be found.
print("Downloading content with Content ID " + cid + "...")
try:
content_data = libWiiPy.title.download_content(tid, content_id)
except ValueError:
print("The Title ID or Content ID you specified could not be found!")
return
if decrypt_content is True: if decrypt_content is True:
content_file_name = content_file_name + ".app" # Ensure that a version was supplied, because we need the matching TMD for decryption to work.
if version is None:
print("You must specify the version that the requested content belongs to for decryption!")
return
output_path = output_path.with_suffix(".app")
tmd = libWiiPy.title.TMD() tmd = libWiiPy.title.TMD()
tmd.load(libWiiPy.title.download_tmd(tid, version)) tmd.load(libWiiPy.title.download_tmd(tid, version))
# Try to get a Ticket for the title, if a common one is available.
try: try:
ticket = libWiiPy.title.Ticket() ticket = libWiiPy.title.Ticket()
ticket.load(libWiiPy.title.download_ticket(tid)) ticket.load(libWiiPy.title.download_ticket(tid, wiiu_endpoint=True))
except ValueError: except ValueError:
print(" - No Ticket is available!") print("No Ticket is available! Content cannot be decrypted!")
return return
content_hash = 'gggggggggggggggggggggggggggggggggggggggg' content_hash = 'gggggggggggggggggggggggggggggggggggggggg'
@ -175,17 +199,25 @@ def handle_nus_content(args):
content_size = record.content_size content_size = record.content_size
content_index = record.index content_index = record.index
# If the default hash never changed, then a content record matching the downloaded content couldn't be found,
# which most likely means that the wrong version was specified.
if content_hash == 'gggggggggggggggggggggggggggggggggggggggg':
print("Content was not found in the TMD from the specified version! Content cannot be decrypted!")
return
# Manually decrypt the content and verify its hash, which is what libWiiPy's get_content() methods do. We just
# can't really use that here because that require setting up a lot more of the title than is necessary.
content_dec = libWiiPy.title.decrypt_content(content_data, ticket.get_title_key(), content_index, content_size) content_dec = libWiiPy.title.decrypt_content(content_data, ticket.get_title_key(), content_index, content_size)
content_dec_hash = hashlib.sha1(content_dec).hexdigest() content_dec_hash = hashlib.sha1(content_dec).hexdigest()
if content_hash != content_dec_hash: if content_hash != content_dec_hash:
raise ValueError("The decrypted content provided does not match the record at the provided index. \n" raise ValueError("The decrypted content provided does not match the record at the provided index. \n"
"Expected hash is: {}\n".format(content_hash) + "Expected hash is: {}\n".format(content_hash) +
"Actual hash is: {}".format(content_dec_hash)) "Actual hash is: {}".format(content_dec_hash))
file = open(content_file_name, "wb") file = open(output_path, "wb")
file.write(content_dec) file.write(content_dec)
file.close() file.close()
else: else:
file = open(content_file_name, "wb") file = open(output_path, "wb")
file.write(content_data) file.write(content_data)
file.close() file.close()

View File

@ -57,6 +57,8 @@ if __name__ == "__main__":
help="Content ID to download (in \"000000xx\" format)") help="Content ID to download (in \"000000xx\" format)")
nus_content_parser.add_argument("-v", "--version", metavar="VERSION", type=int, nus_content_parser.add_argument("-v", "--version", metavar="VERSION", type=int,
help="version this content belongs to (required for decryption)") help="version this content belongs to (required for decryption)")
nus_content_parser.add_argument("-o", "--output", metavar="OUT", type=str,
help="path to download the content to (optional)")
nus_content_parser.add_argument("-d", "--decrypt", action="store_true", help="decrypt this content") nus_content_parser.add_argument("-d", "--decrypt", action="store_true", help="decrypt this content")
# Argument parser for the U8 subcommand. # Argument parser for the U8 subcommand.