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

This commit is contained in:
Campbell 2024-11-10 19:58:31 -05:00
parent 19dc956d25
commit 6336791be0
Signed by: NinjaCheetah
GPG Key ID: 670C282B3291D63D
14 changed files with 115 additions and 102 deletions

View File

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

View File

@ -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("/"):

View File

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

View File

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

View File

@ -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 = ""

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

9
modules/core.py Normal file
View File

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

View File

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