From 6336791be07135321a01e8c1ecf4ef7e5d10508b Mon Sep 17 00:00:00 2001 From: NinjaCheetah <58050615+NinjaCheetah@users.noreply.github.com> Date: Sun, 10 Nov 2024 19:58:31 -0500 Subject: [PATCH] Rewrote error output to be much clearer, no longer raises Python exceptions --- commands/archive/ash.py | 3 +- commands/archive/theme.py | 19 +++++------ commands/archive/u8.py | 6 ++-- commands/nand/emunand.py | 7 ++-- commands/nand/setting.py | 17 ++++++---- commands/title/ciosbuild.py | 28 ++++++++------- commands/title/fakesign.py | 5 +-- commands/title/info.py | 5 +-- commands/title/iospatcher.py | 9 ++--- commands/title/nus.py | 32 ++++++++--------- commands/title/tmd.py | 9 ++--- commands/title/wad.py | 66 ++++++++++++++++++------------------ modules/core.py | 9 +++++ wiipy.py | 2 +- 14 files changed, 115 insertions(+), 102 deletions(-) create mode 100644 modules/core.py diff --git a/commands/archive/ash.py b/commands/archive/ash.py index 4e2f916..5c7b684 100644 --- a/commands/archive/ash.py +++ b/commands/archive/ash.py @@ -3,6 +3,7 @@ import pathlib import libWiiPy +from modules.core import fatal_error def handle_ash_compress(args): @@ -21,7 +22,7 @@ def handle_ash_decompress(args): dist_tree_bits = args.dist_bits if not input_path.exists(): - raise FileNotFoundError(input_path) + fatal_error(f"The specified ASH file \"{input_path}\" does not exist!") ash_data = input_path.read_bytes() # Decompress ASH file using the provided symbol/distance tree widths. diff --git a/commands/archive/theme.py b/commands/archive/theme.py index 5594e67..e68e052 100644 --- a/commands/archive/theme.py +++ b/commands/archive/theme.py @@ -7,6 +7,7 @@ import shutil import tempfile import zipfile import libWiiPy +from modules.core import fatal_error def handle_apply_mym(args): @@ -15,9 +16,9 @@ def handle_apply_mym(args): output_path = pathlib.Path(args.output) if not mym_path.exists(): - raise FileNotFoundError(mym_path) + fatal_error(f"The specified MYM file \"{mym_path}\" does not exist!") if not base_path.exists(): - raise FileNotFoundError(base_path) + fatal_error(f"The specified base file \"{base_path}\" does not exist!") if output_path.suffix != ".csm": output_path = output_path.with_suffix(".csm") @@ -29,21 +30,18 @@ def handle_apply_mym(args): with zipfile.ZipFile(mym_path) as mym: mym.extractall(tmp_path.joinpath("mym_out")) except zipfile.BadZipfile: - print("Error: The provided MYM theme is not valid!") - exit(1) + fatal_error("The provided MYM theme is not valid!") mym_tmp_path = pathlib.Path(tmp_path.joinpath("mym_out")) # Extract the asset archive into the temp directory. try: libWiiPy.archive.extract_u8(base_path.read_bytes(), str(tmp_path.joinpath("base_out"))) except ValueError: - print("Error: The provided base assets are not valid!") - exit(1) + fatal_error("The provided base assets are not valid!") base_temp_path = pathlib.Path(tmp_path.joinpath("base_out")) # Parse the mym.ini file in the root of the extracted MYM file. mym_ini = configparser.ConfigParser() if not mym_tmp_path.joinpath("mym.ini").exists(): - print("Error: mym.ini could not be found! The provided theme is not valid.") - exit(1) + fatal_error("mym.ini could not be found in the theme! The provided theme is not valid.") mym_ini.read(mym_tmp_path.joinpath("mym.ini")) # Iterate over every key in the ini file and apply the theme based the source and target of each key. for section in mym_ini.sections(): @@ -53,9 +51,8 @@ def handle_apply_mym(args): source_file = source_file.joinpath(piece) # Check that this source file is actually valid, and error out if it isn't. if not source_file.exists(): - print(f"Error: A source file specified in mym.ini, \"{mym_ini[section]['source']}\", does not exist! " - f"The provided theme is not valid.") - exit(1) + fatal_error(f"A source file specified in mym.ini, \"{mym_ini[section]['source']}\", does not exist! " + f"The provided theme is not valid.") # Build the target path the same way. target_file = base_temp_path for piece in mym_ini[section]["file"].replace("\\", "/").split("/"): diff --git a/commands/archive/u8.py b/commands/archive/u8.py index ef1a602..908d27b 100644 --- a/commands/archive/u8.py +++ b/commands/archive/u8.py @@ -3,6 +3,7 @@ import pathlib import libWiiPy +from modules.core import fatal_error def handle_u8_pack(args): @@ -12,8 +13,7 @@ def handle_u8_pack(args): try: u8_data = libWiiPy.archive.pack_u8(input_path) except ValueError: - print("Error: Specified input file/folder does not exist!") - return + fatal_error(f"The specified input file/folder \"{input_path}\" does not exist!") out_file = open(output_path, "wb") out_file.write(u8_data) @@ -27,7 +27,7 @@ def handle_u8_unpack(args): output_path = pathlib.Path(args.output) if not input_path.exists(): - raise FileNotFoundError(args.input) + fatal_error(f"The specified input file \"{input_path}\" does not exist!") u8_data = open(input_path, "rb").read() diff --git a/commands/nand/emunand.py b/commands/nand/emunand.py index 2db011c..798960d 100644 --- a/commands/nand/emunand.py +++ b/commands/nand/emunand.py @@ -3,6 +3,7 @@ import pathlib import libWiiPy +from modules.core import fatal_error def handle_emunand_title(args): @@ -17,12 +18,12 @@ def handle_emunand_title(args): input_path = pathlib.Path(args.install) if not input_path.exists(): - raise FileNotFoundError(input_path) + fatal_error("The specified WAD file does not exist!") 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!") + fatal_error("No WAD files were found in the provided input directory!") wad_count = 0 for wad in wad_files: title = libWiiPy.title.Title() @@ -50,7 +51,7 @@ def handle_emunand_title(args): target_tid = input_str if len(target_tid) != 16: - raise ValueError("Invalid Title ID! Title IDs must be 16 characters long.") + fatal_error("The provided Title ID is invalid! Title IDs must be 16 characters long.") emunand.uninstall_title(target_tid) diff --git a/commands/nand/setting.py b/commands/nand/setting.py index 0a01896..b1a8477 100644 --- a/commands/nand/setting.py +++ b/commands/nand/setting.py @@ -3,6 +3,7 @@ import pathlib import libWiiPy +from modules.core import fatal_error def handle_setting_decrypt(args): @@ -13,7 +14,7 @@ def handle_setting_decrypt(args): output_path = pathlib.Path(input_path.stem + "_dec" + input_path.suffix) if not input_path.exists(): - raise FileNotFoundError(input_path) + fatal_error("The specified setting file does not exist!") # Load and decrypt the provided file. setting = libWiiPy.nand.SettingTxt() @@ -31,7 +32,7 @@ def handle_setting_encrypt(args): output_path = pathlib.Path("setting.txt") if not input_path.exists(): - raise FileNotFoundError(input_path) + fatal_error("The specified setting file does not exist!") # Load and encrypt the provided file. setting = libWiiPy.nand.SettingTxt() @@ -44,11 +45,11 @@ def handle_setting_encrypt(args): 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!") + fatal_error("The provided Serial Number is not valid!") try: int(args.serno[-9:]) except ValueError: - raise ValueError("The provided Serial Number is not valid!") + fatal_error("The provided Serial Number is not valid!") prefix = args.serno[:-9] # Detect the console revision based on the SN. match prefix[0].upper(): @@ -64,11 +65,13 @@ def handle_setting_gen(args): # of 11 characters, while other regions have a three-letter prefix for a total length of 12 characters. valid_regions = ["USA", "EUR", "JPN", "KOR"] if args.region not in valid_regions: - raise ValueError("The provided region is not valid!") + fatal_error(f"The provided region \"{args.region}\" is not valid!") if len(prefix) == 2 and args.region != "USA": - raise ValueError("The provided region does not match the provided Serial Number!") + fatal_error(f"The provided region \"{args.region}\" does not match the provided Serial Number " + f"\"{args.serno}\"!\"") elif len(prefix) == 3 and args.region == "USA": - raise ValueError("The provided region does not match the provided Serial Number!") + fatal_error(f"The provided region \"{args.region}\" does not match the provided Serial Number " + f"\"{args.serno}\"!\"") # Get the values for VIDEO and GAME. video = "" game = "" diff --git a/commands/title/ciosbuild.py b/commands/title/ciosbuild.py index e142941..be3705e 100644 --- a/commands/title/ciosbuild.py +++ b/commands/title/ciosbuild.py @@ -6,6 +6,7 @@ import os import xml.etree.ElementTree as ET import pathlib import libWiiPy +from modules.core import fatal_error def build_cios(args): @@ -18,11 +19,11 @@ def build_cios(args): output_path = pathlib.Path(args.output) if not base_path.exists(): - raise FileNotFoundError(base_path) + fatal_error(f"The specified base IOS file \"{base_path}\" does not exist!") if not map_path.exists(): - raise FileNotFoundError(map_path) + fatal_error(f"The specified cIOS map file \"{map_path}\" does not exist!") if not modules_path.exists(): - raise FileNotFoundError(modules_path) + fatal_error(f"The specified cIOS modules directory \"{modules_path}\" does not exist!") title = libWiiPy.title.Title() title.load_wad(open(base_path, 'rb').read()) @@ -39,7 +40,7 @@ def build_cios(args): target_cios = child break if target_cios is None: - raise ValueError("The target cIOS could not be found in the provided map!") + fatal_error(f"The target cIOS \"{args.cios_ver}\" could not be found in the provided map!") # Iterate over all bases in the target cIOS to find a base that matches the provided WAD. If one is found, ensure # that the version of the base in the map matches the version of the IOS WAD. @@ -51,15 +52,16 @@ def build_cios(args): target_base = child break if target_base is None: - raise ValueError("The provided base IOS doesn't match any bases found in the provided map!") + fatal_error(f"The provided base (IOS{provided_base}) doesn't match any bases found in the provided map!") base_version = int(target_base.get("version")) if title.tmd.title_version != base_version: - raise ValueError("The provided base IOS does not match the required version for this base!") + fatal_error(f"The provided base (IOS{provided_base} v{title.tmd.title_version}) doesn't match the required " + f"version (v{base_version})!") print(f"Building cIOS \"{args.cios_ver}\" from base IOS{target_base.get('ios')} v{base_version}...") # We're ready to begin building the cIOS now. Find all the tags that have tags, and then apply # the patches listed in them to the content. - print("Patching existing commands...") + print("Patching existing modules...") for content in target_base.findall("content"): patches = content.findall("patch") if patches: @@ -85,14 +87,14 @@ def build_cios(args): content_data.seek(offset) content_data.write(new_data) else: - raise Exception("An error occurred while patching! Please make sure your base IOS is valid.") + fatal_error("An error occurred while patching! Please make sure your base IOS is valid.") content_data.seek(0x0) dec_content = content_data.read() # Set the content in the title to the newly-patched content, and set the type to normal. title.set_content(dec_content, content_index, content_type=libWiiPy.title.ContentType.NORMAL) - # Next phase of cIOS building is to add the required extra commands. - print("Adding required additional commands...") + # Next phase of cIOS building is to add the required extra modules. + print("Adding required additional modules...") for content in target_base.findall("content"): target_module = content.get("module") if target_module is not None: @@ -101,7 +103,7 @@ def build_cios(args): cid = int(content.get("id"), 16) target_path = modules_path.joinpath(target_module + ".app") if not target_path.exists(): - raise Exception(f"A required module \"{target_module}.app\" could not be found!") + fatal_error(f"A required module \"{target_module}\" could not be found!") # Check where this module belongs. If it's -1, add it to the end. If it's any other value, this module needs # to go at the index specified. new_module = target_path.read_bytes() @@ -120,11 +122,11 @@ def build_cios(args): tid = title.tmd.title_id[:-2] + f"{slot:02X}" title.set_title_id(tid) else: - raise ValueError(f"The provided slot \"{slot}\" is not valid!") + fatal_error(f"The specified slot \"{slot}\" is not valid!") try: title.set_title_version(args.version) except ValueError: - raise ValueError(f"The provided version \"{args.version}\" is not valid!") + fatal_error(f"The specified version \"{args.version}\" is not valid!") print(f"Set cIOS slot to \"{slot}\" and cIOS version to \"{args.version}\"!") # If this is a vWii cIOS, then we need to re-encrypt it with the Wii Common key so that it's installable from diff --git a/commands/title/fakesign.py b/commands/title/fakesign.py index 990cd66..608d496 100644 --- a/commands/title/fakesign.py +++ b/commands/title/fakesign.py @@ -3,6 +3,7 @@ import pathlib import libWiiPy +from modules.core import fatal_error def handle_fakesign(args): @@ -13,7 +14,7 @@ def handle_fakesign(args): output_path = pathlib.Path(args.input) if not input_path.exists(): - raise FileNotFoundError(input_path) + fatal_error(f"The specified input file \"{input_path}\" does not exist!") if input_path.suffix.lower() == ".tmd": tmd = libWiiPy.title.TMD() @@ -34,4 +35,4 @@ def handle_fakesign(args): open(output_path, "wb").write(title.dump_wad()) print("WAD fakesigned successfully!") else: - raise TypeError("This does not appear to be a TMD, Ticket, or WAD! Cannot fakesign.") + fatal_error("The provided file does not appear to be a TMD, Ticket, or WAD and cannot be fakesigned!") diff --git a/commands/title/info.py b/commands/title/info.py index eb4217f..d30b8f6 100644 --- a/commands/title/info.py +++ b/commands/title/info.py @@ -4,6 +4,7 @@ import pathlib import binascii import libWiiPy +from modules.core import fatal_error def _print_tmd_info(tmd: libWiiPy.title.TMD): @@ -136,7 +137,7 @@ def handle_info(args): input_path = pathlib.Path(args.input) if not input_path.exists(): - raise FileNotFoundError(input_path) + fatal_error(f"The specified input file \"{input_path}\" does not exist!") if input_path.suffix.lower() == ".tmd": tmd = libWiiPy.title.TMD() @@ -151,4 +152,4 @@ def handle_info(args): title.load_wad(open(input_path, "rb").read()) _print_wad_info(title) else: - raise TypeError("This does not appear to be a TMD, Ticket, or WAD! No info can be provided.") + fatal_error("This does not appear to be a TMD, Ticket, or WAD! No information can be provided.") diff --git a/commands/title/iospatcher.py b/commands/title/iospatcher.py index 270e85f..2bff729 100644 --- a/commands/title/iospatcher.py +++ b/commands/title/iospatcher.py @@ -3,6 +3,7 @@ import pathlib import libWiiPy +from modules.core import fatal_error def _patch_fakesigning(ios_patcher: libWiiPy.title.IOSPatcher) -> int: @@ -60,14 +61,14 @@ def _patch_drive_inquiry(ios_patcher: libWiiPy.title.IOSPatcher) -> int: def handle_iospatch(args): input_path = pathlib.Path(args.input) if not input_path.exists(): - raise FileNotFoundError(input_path) + fatal_error(f"The specified IOS file \"{input_path}\" does not exist!") title = libWiiPy.title.Title() title.load_wad(open(input_path, "rb").read()) tid = title.tmd.title_id if tid[:8] != "00000001" or tid[8:] == "00000001" or tid[8:] == "00000002": - raise ValueError("This WAD does not appear to contain an IOS! Patching cannot continue.") + fatal_error(f"The provided WAD does not appear to contain an IOS! No patches can be applied.") patch_count = 0 @@ -105,8 +106,8 @@ def handle_iospatch(args): print(f"\nTotal patches applied: {patch_count}") if patch_count == 0 and args.version is None and args.slot is None: - raise ValueError("No patches were applied! Please select patches to apply, and ensure that selected patches are" - " compatible with this IOS.") + fatal_error("No patches were applied! Please specify patches to apply, and ensure that selected patches are " + "compatible with this IOS.") if patch_count > 0 or args.version is not None or args.slot is not None: # Set patched content to non-shared if that argument was passed. diff --git a/commands/title/nus.py b/commands/title/nus.py index 4c1dc8a..e9c3d25 100644 --- a/commands/title/nus.py +++ b/commands/title/nus.py @@ -5,6 +5,7 @@ import hashlib import pathlib import binascii import libWiiPy +from modules.core import fatal_error def handle_nus_title(args): @@ -23,8 +24,7 @@ def handle_nus_title(args): try: title_version = int(args.version) except ValueError: - print("Enter a valid integer for the Title Version.") - return + fatal_error("The specified Title Version must be a valid integer!") # If --wad was passed, check to make sure the path is okay. if args.wad is not None: @@ -37,7 +37,7 @@ def handle_nus_title(args): output_dir = pathlib.Path(args.output) if output_dir.exists(): if output_dir.is_file(): - raise ValueError("A file already exists with the provided directory name!") + fatal_error("A file already exists with the provided directory name!") else: output_dir.mkdir() @@ -73,8 +73,7 @@ def handle_nus_title(args): # ticket so that they aren't attempted later. print(" - No Ticket is available!") if wad_file is not None and output_dir is None: - print("--wad was passed, but this title cannot be packed into a WAD!") - return + fatal_error("--wad was passed, but this title has no common ticket and cannot be packed into a WAD!") # Load the content records from the TMD, and begin iterating over the records. title.load_content_records() @@ -137,7 +136,7 @@ def handle_nus_content(args): try: content_id = int.from_bytes(binascii.unhexlify(cid)) except binascii.Error: - raise ValueError("Invalid Content ID! Content ID must be in format \"000000xx\"!") + fatal_error("The provided Content ID is invalid! The Content ID must be in the format \"000000xx\"!") # Use the supplied output path if one was specified, otherwise generate one using the Content ID. if args.output is None: @@ -148,15 +147,14 @@ def handle_nus_content(args): # Ensure that a version was supplied before downloading, because we need the matching TMD for decryption to work. if decrypt_content is True and version is None: - print("You must specify the version that the requested content belongs to for decryption!") - return + fatal_error("You must specify the version that the requested content belongs to for decryption!") # 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: - raise ValueError("The Title ID or Content ID you specified could not be found!") + fatal_error("The specified Title ID or Content ID could not be found!") if decrypt_content is True: output_path = output_path.with_suffix(".app") @@ -167,8 +165,7 @@ def handle_nus_content(args): ticket = libWiiPy.title.Ticket() ticket.load(libWiiPy.title.download_ticket(tid, wiiu_endpoint=True)) except ValueError: - print("No Ticket is available! Content cannot be decrypted!") - return + fatal_error("No Ticket is available! Content cannot be decrypted.") content_hash = 'gggggggggggggggggggggggggggggggggggggggg' content_size = 0 @@ -182,17 +179,16 @@ def handle_nus_content(args): # 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 + fatal_error("Content was not found in the TMD for the specified version! Content cannot be decrypted.") # 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_hash = hashlib.sha1(content_dec).hexdigest() if content_hash != content_dec_hash: - raise ValueError("The decrypted content provided does not match the record at the provided index. \n" - "Expected hash is: {}\n".format(content_hash) + - "Actual hash is: {}".format(content_dec_hash)) + fatal_error("The decrypted content provided does not match the record at the provided index. \n" + "Expected hash is: {}\n".format(content_hash) + + "Actual hash is: {}".format(content_dec_hash)) output_path.write_bytes(content_dec) else: output_path.write_bytes(content_data) @@ -208,7 +204,7 @@ def handle_nus_tmd(args): try: version = int(args.version) except ValueError: - raise ValueError("Enter a valid integer for the TMD Version.") + fatal_error("The specified TMD version must be a valid integer!") else: version = None @@ -227,7 +223,7 @@ def handle_nus_tmd(args): try: tmd_data = libWiiPy.title.download_tmd(tid, version) except ValueError: - raise ValueError("The Title ID or version you specified could not be found!") + fatal_error("The specified Title ID or version could not be found!") output_path.write_bytes(tmd_data) diff --git a/commands/title/tmd.py b/commands/title/tmd.py index 0901105..168d10c 100644 --- a/commands/title/tmd.py +++ b/commands/title/tmd.py @@ -3,6 +3,7 @@ import pathlib import libWiiPy +from modules.core import fatal_error def handle_tmd_remove(args): @@ -13,7 +14,7 @@ def handle_tmd_remove(args): output_path = pathlib.Path(args.input) if not input_path.exists(): - raise FileNotFoundError(input_path) + fatal_error("The specified TMD files does not exist!") tmd = libWiiPy.title.TMD() tmd.load(input_path.read_bytes()) @@ -21,7 +22,7 @@ def handle_tmd_remove(args): if args.index is not None: # Make sure the target index exists, then remove it from the TMD. if args.index >= len(tmd.content_records): - raise ValueError("The provided index could not be found in this TMD!") + fatal_error("The specified index could not be found in the provided TMD!") tmd.content_records.pop(args.index) tmd.num_contents -= 1 # Auto fakesign because we've edited the TMD. @@ -31,14 +32,14 @@ def handle_tmd_remove(args): elif args.cid is not None: if len(args.cid) != 8: - raise ValueError("The provided Content ID is invalid!") + fatal_error("The specified Content ID is invalid!") target_cid = int(args.cid, 16) # List Contents IDs in the title, and ensure that the target Content ID exists. valid_ids = [] for record in tmd.content_records: valid_ids.append(record.content_id) if target_cid not in valid_ids: - raise ValueError("The provided Content ID could not be found in this TMD!") + fatal_error("The specified Content ID could not be found in the provided TMD!") tmd.content_records.pop(valid_ids.index(target_cid)) tmd.num_contents -= 1 # Auto fakesign because we've edited the TMD. diff --git a/commands/title/wad.py b/commands/title/wad.py index 236acf6..0baca27 100644 --- a/commands/title/wad.py +++ b/commands/title/wad.py @@ -4,6 +4,7 @@ import pathlib from random import randint import libWiiPy +from modules.core import fatal_error def handle_wad_add(args): @@ -15,9 +16,9 @@ def handle_wad_add(args): output_path = pathlib.Path(args.input) if not input_path.exists(): - raise FileNotFoundError(input_path) + fatal_error(f"The specified WAD file \"{input_path}\" does not exist!") if not content_path.exists(): - raise FileNotFoundError(content_path) + fatal_error(f"The specified content file \"{content_path}\" does not exist!") title = libWiiPy.title.Title() title.load_wad(input_path.read_bytes()) @@ -27,11 +28,11 @@ def handle_wad_add(args): # We need to both validate that this is a real CID, and also that it isn't already taken by another content. if args.cid is not None: if len(args.cid) != 8: - raise ValueError("The provided Content ID is invalid!") + fatal_error("The specified Content ID is not valid!") target_cid = int(args.cid, 16) for record in title.content.content_records: if target_cid == record.content_id: - raise ValueError("The provided Content ID is already being used by this title!") + fatal_error("The specified Content ID is already in use by this title!") print(f"Using provided Content ID \"{target_cid:08X}\".") # If we weren't given a CID, then we need to randomly assign one, and ensure it isn't being used. else: @@ -53,7 +54,7 @@ def handle_wad_add(args): case "dlc": target_type = libWiiPy.title.ContentType.DLC case _: - raise ValueError("The provided content type is invalid!") + fatal_error(f"The provided content type \"{args.type}\" is not valid!") else: target_type = libWiiPy.title.ContentType.NORMAL @@ -76,7 +77,7 @@ def handle_wad_convert(args): elif args.vwii: target = "vWii" else: - raise ValueError("No valid target was provided!") + fatal_error("No valid encryption target was specified!") if args.output is None: match target: @@ -87,22 +88,22 @@ def handle_wad_convert(args): case "vWii": output_path = pathlib.Path(input_path.stem + "_vWii" + input_path.suffix) case _: - raise ValueError("No valid target was provided!") + fatal_error("No valid encryption target was specified!") else: output_path = pathlib.Path(args.output) if not input_path.exists(): - raise FileNotFoundError(input_path) + fatal_error(f"The specified WAD file \"{input_path}\" does not exist!") title = libWiiPy.title.Title() title.load_wad(input_path.read_bytes()) # First, verify that this WAD isn't already the type we're trying to convert to. if title.ticket.is_dev and target == "development": - raise ValueError("This is already a development WAD!") + fatal_error("This is already a development WAD!") elif not title.ticket.is_dev and not title.tmd.vwii and target == "retail": - raise ValueError("This is already a retail WAD!") + fatal_error("This is already a retail WAD!") elif not title.ticket.is_dev and title.tmd.vwii and target == "vWii": - raise ValueError("This is already a vWii WAD!") + fatal_error("This is already a vWii WAD!") # Get the current type to display later. if title.ticket.is_dev: source = "development" @@ -143,40 +144,39 @@ def handle_wad_pack(args): # Make sure input path both exists and is a directory. Separate checks because this provides more relevant # errors than just a NotADirectoryError if the actual issue is that there's nothing at all. if not input_path.exists(): - raise FileNotFoundError(input_path) + fatal_error(f"The specified input directory \"{input_path}\" does not exist!") if not input_path.is_dir(): - raise NotADirectoryError(input_path) + fatal_error(f"The specified input path \"{input_path}\" is not a directory!") # Get a list of all files ending in .tmd, and then make sure that that list has *only* 1 entry. More than 1 # means we can't pack a WAD because we couldn't really tell which TMD is intended for this WAD. tmd_list = list(input_path.glob('*.[tT][mM][dD]')) if len(tmd_list) > 1: - raise FileExistsError("More than one TMD file was found! Only one TMD can be packed into a WAD.") + fatal_error("More than one TMD file was found! Only one TMD can be packed into a WAD.") elif len(tmd_list) == 0: - raise FileNotFoundError("No TMD file found! Cannot pack WAD.") + fatal_error("No TMD file was found! Cannot pack WAD.") tmd_file = pathlib.Path(tmd_list[0]) # Repeat the same process as above for all .tik files. ticket_list = list(input_path.glob('*.[tT][iI][kK]')) if len(ticket_list) > 1: - raise FileExistsError("More than one Ticket file was found! Only one Ticket can be packed into a WAD.") + fatal_error("More than one Ticket file was found! Only one Ticket can be packed into a WAD.") elif len(ticket_list) == 0: - raise FileNotFoundError("No Ticket file found! Cannot pack WAD.") + fatal_error("No Ticket file was found! Cannot pack WAD.") ticket_file = pathlib.Path(ticket_list[0]) # And one more time for all .cert files. cert_list = list(input_path.glob('*.[cC][eE][rR][tT]')) if len(cert_list) > 1: - raise FileExistsError("More than one certificate file was found! Only one certificate can be packed into a " - "WAD.") + fatal_error("More than one certificate file was found! Only one certificate can be packed into a WAD.") elif len(cert_list) == 0: - raise FileNotFoundError("No certificate file found! Cannot pack WAD.") + fatal_error("No certificate file was found! Cannot pack WAD.") cert_file = pathlib.Path(cert_list[0]) # Make sure that there's at least one content to pack. content_files = list(input_path.glob("*.[aA][pP][pP]")) if not content_files: - raise FileNotFoundError("No contents found! Cannot pack WAD.") + fatal_error("No content files were found! Cannot pack WAD.") # Open the output file, and load all the component files that we've now verified we have into a libWiiPy Title() # object. @@ -222,7 +222,7 @@ def handle_wad_remove(args): output_path = pathlib.Path(args.input) if not input_path.exists(): - raise FileNotFoundError(input_path) + fatal_error(f"The specified WAD file \"{input_path}\" does not exist!") title = libWiiPy.title.Title() title.load_wad(input_path.read_bytes()) @@ -234,7 +234,7 @@ def handle_wad_remove(args): for record in title.content.content_records: valid_indices.append(record.index) if args.index not in valid_indices: - raise ValueError("The provided content index could not be found in this title!") + fatal_error("The specified content index could not be found in this title!") title.content.remove_content_by_index(args.index) # Auto fakesign because we've edited the title. title.fakesign() @@ -243,14 +243,14 @@ def handle_wad_remove(args): elif args.cid is not None: if len(args.cid) != 8: - raise ValueError("The provided Content ID is invalid!") + fatal_error("The specified Content ID is not valid!") target_cid = int(args.cid, 16) # List Contents IDs in the title, and ensure that the target Content ID exists. valid_ids = [] for record in title.content.content_records: valid_ids.append(record.content_id) if target_cid not in valid_ids: - raise ValueError("The provided Content ID could not be found in this title!") + fatal_error("The specified Content ID could not be found in this title!") title.content.remove_content_by_cid(target_cid) # Auto fakesign because we've edited the title. title.fakesign() @@ -267,9 +267,9 @@ def handle_wad_set(args): output_path = pathlib.Path(args.input) if not input_path.exists(): - raise FileNotFoundError(input_path) + fatal_error(f"The specified WAD file \"{input_path}\" does not exist!") if not content_path.exists(): - raise FileNotFoundError(content_path) + fatal_error(f"The specified content file \"{content_path}\" does not exist!") title = libWiiPy.title.Title() title.load_wad(input_path.read_bytes()) @@ -285,7 +285,7 @@ def handle_wad_set(args): case "dlc": target_type = libWiiPy.title.ContentType.DLC case _: - raise ValueError("The provided content type is invalid!") + fatal_error(f"The provided content type \"{args.type}\" is not valid!\"") else: target_type = None @@ -295,7 +295,7 @@ def handle_wad_set(args): for record in title.content.content_records: existing_indices.append(record.index) if args.index not in existing_indices: - raise ValueError("The provided index could not be found in this title!") + fatal_error("The specified index could not be found in this title!") if target_type: title.set_content(content_data, args.index, content_type=target_type) else: @@ -309,13 +309,13 @@ def handle_wad_set(args): elif args.cid is not None: # If we're replacing based on the CID, then make sure the specified CID is valid and exists. if len(args.cid) != 8: - raise ValueError("The provided Content ID is invalid!") + fatal_error("The specified Content ID is not valid!") target_cid = int(args.cid, 16) existing_cids = [] for record in title.content.content_records: existing_cids.append(record.content_id) if target_cid not in existing_cids: - raise ValueError("The provided Content ID could not be found in this title!") + fatal_error("The specified Content ID could not be found in this title!") target_index = title.content.get_index_from_cid(target_cid) if target_type: title.set_content(content_data, target_index, content_type=target_type) @@ -332,11 +332,11 @@ def handle_wad_unpack(args): output_path = pathlib.Path(args.output) if not input_path.exists(): - raise FileNotFoundError(input_path) + fatal_error(f"The specified WAD file \"{input_path}\" does not exist!") # Check if the output path already exists, and if it does, ensure that it is both a directory and empty. if output_path.exists(): if output_path.is_file(): - raise ValueError("A file already exists with the provided directory name!") + fatal_error(f"A file already exists with the provided output directory name!") else: output_path.mkdir() diff --git a/modules/core.py b/modules/core.py new file mode 100644 index 0000000..c956c29 --- /dev/null +++ b/modules/core.py @@ -0,0 +1,9 @@ +# "modules/core.py" from WiiPy by NinjaCheetah +# https://github.com/NinjaCheetah/WiiPy + +import sys + + +def fatal_error(msg: str) -> None: + print(f"\033[31mError:\033[0m {msg}") + sys.exit(-1) diff --git a/wiipy.py b/wiipy.py index 5aacf8f..4da891b 100644 --- a/wiipy.py +++ b/wiipy.py @@ -65,7 +65,7 @@ if __name__ == "__main__": cios_parser.add_argument("output", metavar="OUT", type=str, help="file to output the cIOS to") cios_parser.add_argument("-c", "--cios-ver", metavar="CIOS", type=str, help="cIOS version from the map to build", required=True) - cios_parser.add_argument("-m", "--commands", metavar="MODULES", type=str, + cios_parser.add_argument("-m", "--modules", metavar="MODULES", type=str, help="directory to look for cIOS commands in (optional, defaults to current directory)") cios_parser.add_argument("-s", "--slot", metavar="SLOT", type=int, help="slot that this cIOS will install to (optional, defaults to 249)", default=249)