Rewrote error output to be much clearer, no longer raises Python exceptions

This commit is contained in:
2024-11-10 19:58:31 -05:00
parent 19dc956d25
commit 6336791be0
14 changed files with 115 additions and 102 deletions

View File

@@ -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 <content> tags that have <patch> 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

View File

@@ -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!")

View File

@@ -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.")

View File

@@ -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.

View File

@@ -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)

View File

@@ -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.

View File

@@ -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()