From 0a9733a8d376d876083c200ec6a19539d671820b Mon Sep 17 00:00:00 2001 From: NinjaCheetah <58050615+NinjaCheetah@users.noreply.github.com> Date: Thu, 10 Oct 2024 14:42:12 -0400 Subject: [PATCH] Changed setting generation syntax, added commands to encrypt/decrypt setting file --- modules/nand/emunand.py | 57 +++++++++++++ modules/{title/emunand.py => nand/setting.py} | 83 ++++++++----------- wiipy.py | 42 +++++++--- 3 files changed, 123 insertions(+), 59 deletions(-) create mode 100644 modules/nand/emunand.py rename modules/{title/emunand.py => nand/setting.py} (53%) diff --git a/modules/nand/emunand.py b/modules/nand/emunand.py new file mode 100644 index 0000000..2da4ed5 --- /dev/null +++ b/modules/nand/emunand.py @@ -0,0 +1,57 @@ +# "modules/nand/emunand.py" from WiiPy by NinjaCheetah +# https://github.com/NinjaCheetah/WiiPy + +import pathlib +import libWiiPy + + +def handle_emunand_title(args): + emunand = libWiiPy.nand.EmuNAND(args.emunand) + if args.skip_hash: + skip_hash = True + else: + skip_hash = False + + # Code for if the --install argument was passed. + if args.install: + input_path = pathlib.Path(args.install) + + if not input_path.exists(): + raise FileNotFoundError(input_path) + + if input_path.is_dir(): + wad_files = list(input_path.glob("*.[wW][aA][dD]")) + if not wad_files: + raise FileNotFoundError("No WAD files were found in the provided input directory!") + wad_count = 0 + for wad in wad_files: + title = libWiiPy.title.Title() + title.load_wad(open(wad, "rb").read()) + try: + emunand.install_title(title, skip_hash=skip_hash) + wad_count += 1 + except ValueError: + print(f"WAD {wad} could not be installed!") + print(f"Successfully installed {wad_count} WAD(s) to EmuNAND!") + else: + title = libWiiPy.title.Title() + title.load_wad(open(input_path, "rb").read()) + emunand.install_title(title, skip_hash=skip_hash) + print("Successfully installed WAD to EmuNAND!") + + # Code for if the --uninstall argument was passed. + elif args.uninstall: + input_str = args.uninstall + if pathlib.Path(input_str).exists(): + title = libWiiPy.title.Title() + title.load_wad(open(pathlib.Path(input_str), "rb").read()) + target_tid = title.tmd.title_id + else: + target_tid = input_str + + if len(target_tid) != 16: + raise ValueError("Invalid Title ID! Title IDs must be 16 characters long.") + + emunand.uninstall_title(target_tid) + + print("Title uninstalled from EmuNAND!") diff --git a/modules/title/emunand.py b/modules/nand/setting.py similarity index 53% rename from modules/title/emunand.py rename to modules/nand/setting.py index 1ed9236..cbc18f5 100644 --- a/modules/title/emunand.py +++ b/modules/nand/setting.py @@ -1,63 +1,47 @@ -# "modules/title/emunand.py" from WiiPy by NinjaCheetah +# "modules/nand/setting.py" from WiiPy by NinjaCheetah # https://github.com/NinjaCheetah/WiiPy import pathlib import libWiiPy -def handle_emunand_title(args): - emunand = libWiiPy.nand.EmuNAND(args.emunand) - if args.skip_hash: - skip_hash = True +def handle_setting_decrypt(args): + input_path = pathlib.Path(args.input) + if args.output is not None: + output_path = pathlib.Path(args.output) else: - skip_hash = False + output_path = pathlib.Path(input_path.stem + "_dec" + input_path.suffix) - # Code for if the --install argument was passed. - if args.install: - input_path = pathlib.Path(args.install) + if not input_path.exists(): + raise FileNotFoundError(input_path) - if not input_path.exists(): - raise FileNotFoundError(input_path) - - if input_path.is_dir(): - wad_files = list(input_path.glob("*.[wW][aA][dD]")) - if not wad_files: - raise FileNotFoundError("No WAD files were found in the provided input directory!") - wad_count = 0 - for wad in wad_files: - title = libWiiPy.title.Title() - title.load_wad(open(wad, "rb").read()) - try: - emunand.install_title(title, skip_hash=skip_hash) - wad_count += 1 - except ValueError: - print(f"WAD {wad} could not be installed!") - print(f"Successfully installed {wad_count} WAD(s) to EmuNAND!") - else: - title = libWiiPy.title.Title() - title.load_wad(open(input_path, "rb").read()) - emunand.install_title(title, skip_hash=skip_hash) - print("Successfully installed WAD to EmuNAND!") - - # Code for if the --uninstall argument was passed. - elif args.uninstall: - input_str = args.uninstall - if pathlib.Path(input_str).exists(): - title = libWiiPy.title.Title() - title.load_wad(open(pathlib.Path(input_str), "rb").read()) - target_tid = title.tmd.title_id - else: - target_tid = input_str - - if len(target_tid) != 16: - raise ValueError("Invalid Title ID! Title IDs must be 16 characters long.") - - emunand.uninstall_title(target_tid) - - print("Title uninstalled from EmuNAND!") + # Load and decrypt the provided file. + setting = libWiiPy.nand.SettingTxt() + setting.load(open(input_path, "rb").read()) + # Write out the decrypted data. + open(output_path, "w").write(setting.dump_decrypted()) + print("Successfully decrypted setting.txt!") -def handle_emunand_gensetting(args): +def handle_setting_encrypt(args): + input_path = pathlib.Path(args.input) + if args.output is not None: + output_path = pathlib.Path(args.output) + else: + output_path = pathlib.Path("setting.txt") + + if not input_path.exists(): + raise FileNotFoundError(input_path) + + # Load and encrypt the provided file. + setting = libWiiPy.nand.SettingTxt() + setting.load_decrypted(open(input_path, "r").read()) + # Write out the encrypted data. + open(output_path, "wb").write(setting.dump()) + print("Successfully encrypted setting.txt!") + + +def handle_setting_gen(args): # Validate the provided SN. It should be 2 or 3 letters followed by 9 numbers. if len(args.serno) != 11 and len(args.serno) != 12: raise ValueError("The provided Serial Number is not valid!") @@ -113,3 +97,4 @@ def handle_emunand_gensetting(args): setting.game = game # Write out the setting.txt file. open("setting.txt", "wb").write(setting.dump()) + print(f"Successfully created setting.txt for console with serial number {args.serno}!") diff --git a/wiipy.py b/wiipy.py index 2981253..f759594 100644 --- a/wiipy.py +++ b/wiipy.py @@ -6,8 +6,9 @@ from importlib.metadata import version from modules.archive.ash import * from modules.archive.u8 import * +from modules.nand.emunand import * +from modules.nand.setting import * from modules.title.ciosbuild import * -from modules.title.emunand import * from modules.title.fakesign import * from modules.title.info import * from modules.title.iospatcher import * @@ -71,15 +72,6 @@ if __name__ == "__main__": "accepts a WAD file to read the TID from)") emunand_title_parser.add_argument("-s", "--skip-hash", help="skips validating the hashes of decrypted " "content (install only)", action="store_true") - # Setting generation EmuNAND command. - emunand_gensetting_parser = emunand_subparsers.add_parser("gen-setting", - help="generate a new setting.txt based on the provided values", - description="generate a new setting.txt based on the provided values") - emunand_gensetting_parser.set_defaults(func=handle_emunand_gensetting) - emunand_gensetting_parser.add_argument("serno", metavar="SERNO", type=str, - help="serial number of the console these settings are for") - emunand_gensetting_parser.add_argument("region", metavar="REGION", type=str, - help="region of the console these settings are for (USA, EUR, JPN, or KOR)") # Argument parser for the fakesign subcommand. fakesign_parser = subparsers.add_parser("fakesign", help="fakesign a TMD, Ticket, or WAD (trucha bug)", @@ -156,6 +148,36 @@ if __name__ == "__main__": nus_tmd_parser.add_argument("-o", "--output", metavar="OUT", type=str, help="path to download the TMD to (optional)") + # Argument parser for the setting subcommand. + setting_parser = subparsers.add_parser("setting", help="manage setting.txt", + description="manage setting.txt") + setting_subparsers = setting_parser.add_subparsers(dest="subcommand", required=True) + # Decrypt setting.txt subcommand. + setting_dec_parser = setting_subparsers.add_parser("decrypt", help="decrypt setting.txt", + description="decrypt setting.txt; by default, this will output " + "to setting_dec.txt") + setting_dec_parser.set_defaults(func=handle_setting_decrypt) + setting_dec_parser.add_argument("input", metavar="IN", type=str, help="encrypted setting.txt file to decrypt") + setting_dec_parser.add_argument("-o", "--output", metavar="OUT", type=str, + help="path to output the decrypted file to (optional)") + # Encrypt setting.txt subcommand. + setting_enc_parser = setting_subparsers.add_parser("encrypt", help="encrypt setting.txt", + description="encrypt setting.txt; by default, this will output " + "to setting.txt") + setting_enc_parser.set_defaults(func=handle_setting_encrypt) + setting_enc_parser.add_argument("input", metavar="IN", type=str, help="decrypted setting.txt file to encrypt") + setting_enc_parser.add_argument("-o", "--output", metavar="OUT", type=str, + help="path to output the encrypted file to (optional)") + # Generate setting.txt subcommand. + setting_gen_parser = setting_subparsers.add_parser("gen", + help="generate a new setting.txt based on the provided values", + description="generate a new setting.txt based on the provided values") + setting_gen_parser.set_defaults(func=handle_setting_gen) + setting_gen_parser.add_argument("serno", metavar="SERNO", type=str, + help="serial number of the console these settings are for") + setting_gen_parser.add_argument("region", metavar="REGION", type=str, + help="region of the console these settings are for (USA, EUR, JPN, or KOR)") + # Argument parser for the U8 subcommand. u8_parser = subparsers.add_parser("u8", help="pack/unpack a U8 archive", description="pack/unpack a U8 archive")