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 pathlib
import libWiiPy import libWiiPy
from modules.core import fatal_error
def handle_ash_compress(args): def handle_ash_compress(args):
@ -21,7 +22,7 @@ def handle_ash_decompress(args):
dist_tree_bits = args.dist_bits dist_tree_bits = args.dist_bits
if not input_path.exists(): 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() ash_data = input_path.read_bytes()
# Decompress ASH file using the provided symbol/distance tree widths. # Decompress ASH file using the provided symbol/distance tree widths.

View File

@ -7,6 +7,7 @@ import shutil
import tempfile import tempfile
import zipfile import zipfile
import libWiiPy import libWiiPy
from modules.core import fatal_error
def handle_apply_mym(args): def handle_apply_mym(args):
@ -15,9 +16,9 @@ def handle_apply_mym(args):
output_path = pathlib.Path(args.output) output_path = pathlib.Path(args.output)
if not mym_path.exists(): 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(): 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": if output_path.suffix != ".csm":
output_path = output_path.with_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: with zipfile.ZipFile(mym_path) as mym:
mym.extractall(tmp_path.joinpath("mym_out")) mym.extractall(tmp_path.joinpath("mym_out"))
except zipfile.BadZipfile: except zipfile.BadZipfile:
print("Error: The provided MYM theme is not valid!") fatal_error("The provided MYM theme is not valid!")
exit(1)
mym_tmp_path = pathlib.Path(tmp_path.joinpath("mym_out")) mym_tmp_path = pathlib.Path(tmp_path.joinpath("mym_out"))
# Extract the asset archive into the temp directory. # Extract the asset archive into the temp directory.
try: try:
libWiiPy.archive.extract_u8(base_path.read_bytes(), str(tmp_path.joinpath("base_out"))) libWiiPy.archive.extract_u8(base_path.read_bytes(), str(tmp_path.joinpath("base_out")))
except ValueError: except ValueError:
print("Error: The provided base assets are not valid!") fatal_error("The provided base assets are not valid!")
exit(1)
base_temp_path = pathlib.Path(tmp_path.joinpath("base_out")) base_temp_path = pathlib.Path(tmp_path.joinpath("base_out"))
# Parse the mym.ini file in the root of the extracted MYM file. # Parse the mym.ini file in the root of the extracted MYM file.
mym_ini = configparser.ConfigParser() mym_ini = configparser.ConfigParser()
if not mym_tmp_path.joinpath("mym.ini").exists(): if not mym_tmp_path.joinpath("mym.ini").exists():
print("Error: mym.ini could not be found! The provided theme is not valid.") fatal_error("mym.ini could not be found in the theme! The provided theme is not valid.")
exit(1)
mym_ini.read(mym_tmp_path.joinpath("mym.ini")) 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. # 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(): for section in mym_ini.sections():
@ -53,9 +51,8 @@ def handle_apply_mym(args):
source_file = source_file.joinpath(piece) source_file = source_file.joinpath(piece)
# Check that this source file is actually valid, and error out if it isn't. # Check that this source file is actually valid, and error out if it isn't.
if not source_file.exists(): if not source_file.exists():
print(f"Error: A source file specified in mym.ini, \"{mym_ini[section]['source']}\", does not exist! " fatal_error(f"A source file specified in mym.ini, \"{mym_ini[section]['source']}\", does not exist! "
f"The provided theme is not valid.") f"The provided theme is not valid.")
exit(1)
# Build the target path the same way. # Build the target path the same way.
target_file = base_temp_path target_file = base_temp_path
for piece in mym_ini[section]["file"].replace("\\", "/").split("/"): for piece in mym_ini[section]["file"].replace("\\", "/").split("/"):

View File

@ -3,6 +3,7 @@
import pathlib import pathlib
import libWiiPy import libWiiPy
from modules.core import fatal_error
def handle_u8_pack(args): def handle_u8_pack(args):
@ -12,8 +13,7 @@ def handle_u8_pack(args):
try: try:
u8_data = libWiiPy.archive.pack_u8(input_path) u8_data = libWiiPy.archive.pack_u8(input_path)
except ValueError: except ValueError:
print("Error: Specified input file/folder does not exist!") fatal_error(f"The specified input file/folder \"{input_path}\" does not exist!")
return
out_file = open(output_path, "wb") out_file = open(output_path, "wb")
out_file.write(u8_data) out_file.write(u8_data)
@ -27,7 +27,7 @@ def handle_u8_unpack(args):
output_path = pathlib.Path(args.output) output_path = pathlib.Path(args.output)
if not input_path.exists(): 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() u8_data = open(input_path, "rb").read()

View File

@ -3,6 +3,7 @@
import pathlib import pathlib
import libWiiPy import libWiiPy
from modules.core import fatal_error
def handle_emunand_title(args): def handle_emunand_title(args):
@ -17,12 +18,12 @@ def handle_emunand_title(args):
input_path = pathlib.Path(args.install) input_path = pathlib.Path(args.install)
if not input_path.exists(): if not input_path.exists():
raise FileNotFoundError(input_path) fatal_error("The specified WAD file does not exist!")
if input_path.is_dir(): if input_path.is_dir():
wad_files = list(input_path.glob("*.[wW][aA][dD]")) wad_files = list(input_path.glob("*.[wW][aA][dD]"))
if not wad_files: 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 wad_count = 0
for wad in wad_files: for wad in wad_files:
title = libWiiPy.title.Title() title = libWiiPy.title.Title()
@ -50,7 +51,7 @@ def handle_emunand_title(args):
target_tid = input_str target_tid = input_str
if len(target_tid) != 16: 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) emunand.uninstall_title(target_tid)

View File

@ -3,6 +3,7 @@
import pathlib import pathlib
import libWiiPy import libWiiPy
from modules.core import fatal_error
def handle_setting_decrypt(args): 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) output_path = pathlib.Path(input_path.stem + "_dec" + input_path.suffix)
if not input_path.exists(): if not input_path.exists():
raise FileNotFoundError(input_path) fatal_error("The specified setting file does not exist!")
# Load and decrypt the provided file. # Load and decrypt the provided file.
setting = libWiiPy.nand.SettingTxt() setting = libWiiPy.nand.SettingTxt()
@ -31,7 +32,7 @@ def handle_setting_encrypt(args):
output_path = pathlib.Path("setting.txt") output_path = pathlib.Path("setting.txt")
if not input_path.exists(): if not input_path.exists():
raise FileNotFoundError(input_path) fatal_error("The specified setting file does not exist!")
# Load and encrypt the provided file. # Load and encrypt the provided file.
setting = libWiiPy.nand.SettingTxt() setting = libWiiPy.nand.SettingTxt()
@ -44,11 +45,11 @@ def handle_setting_encrypt(args):
def handle_setting_gen(args): def handle_setting_gen(args):
# Validate the provided SN. It should be 2 or 3 letters followed by 9 numbers. # 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: 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: try:
int(args.serno[-9:]) int(args.serno[-9:])
except ValueError: except ValueError:
raise ValueError("The provided Serial Number is not valid!") fatal_error("The provided Serial Number is not valid!")
prefix = args.serno[:-9] prefix = args.serno[:-9]
# Detect the console revision based on the SN. # Detect the console revision based on the SN.
match prefix[0].upper(): 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. # of 11 characters, while other regions have a three-letter prefix for a total length of 12 characters.
valid_regions = ["USA", "EUR", "JPN", "KOR"] valid_regions = ["USA", "EUR", "JPN", "KOR"]
if args.region not in valid_regions: 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": 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": 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. # Get the values for VIDEO and GAME.
video = "" video = ""
game = "" game = ""

View File

@ -6,6 +6,7 @@ import os
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
import pathlib import pathlib
import libWiiPy import libWiiPy
from modules.core import fatal_error
def build_cios(args): def build_cios(args):
@ -18,11 +19,11 @@ def build_cios(args):
output_path = pathlib.Path(args.output) output_path = pathlib.Path(args.output)
if not base_path.exists(): 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(): 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(): 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 = libWiiPy.title.Title()
title.load_wad(open(base_path, 'rb').read()) title.load_wad(open(base_path, 'rb').read())
@ -39,7 +40,7 @@ def build_cios(args):
target_cios = child target_cios = child
break break
if target_cios is None: 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 # 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. # 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 target_base = child
break break
if target_base is None: 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")) base_version = int(target_base.get("version"))
if title.tmd.title_version != base_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}...") 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 # 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. # the patches listed in them to the content.
print("Patching existing commands...") print("Patching existing modules...")
for content in target_base.findall("content"): for content in target_base.findall("content"):
patches = content.findall("patch") patches = content.findall("patch")
if patches: if patches:
@ -85,14 +87,14 @@ def build_cios(args):
content_data.seek(offset) content_data.seek(offset)
content_data.write(new_data) content_data.write(new_data)
else: 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) content_data.seek(0x0)
dec_content = content_data.read() dec_content = content_data.read()
# Set the content in the title to the newly-patched content, and set the type to normal. # 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) 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. # Next phase of cIOS building is to add the required extra modules.
print("Adding required additional commands...") print("Adding required additional modules...")
for content in target_base.findall("content"): for content in target_base.findall("content"):
target_module = content.get("module") target_module = content.get("module")
if target_module is not None: if target_module is not None:
@ -101,7 +103,7 @@ def build_cios(args):
cid = int(content.get("id"), 16) cid = int(content.get("id"), 16)
target_path = modules_path.joinpath(target_module + ".app") target_path = modules_path.joinpath(target_module + ".app")
if not target_path.exists(): 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 # 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. # to go at the index specified.
new_module = target_path.read_bytes() new_module = target_path.read_bytes()
@ -120,11 +122,11 @@ def build_cios(args):
tid = title.tmd.title_id[:-2] + f"{slot:02X}" tid = title.tmd.title_id[:-2] + f"{slot:02X}"
title.set_title_id(tid) title.set_title_id(tid)
else: else:
raise ValueError(f"The provided slot \"{slot}\" is not valid!") fatal_error(f"The specified slot \"{slot}\" is not valid!")
try: try:
title.set_title_version(args.version) title.set_title_version(args.version)
except ValueError: 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}\"!") 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 # 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 pathlib
import libWiiPy import libWiiPy
from modules.core import fatal_error
def handle_fakesign(args): def handle_fakesign(args):
@ -13,7 +14,7 @@ def handle_fakesign(args):
output_path = pathlib.Path(args.input) output_path = pathlib.Path(args.input)
if not input_path.exists(): 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": if input_path.suffix.lower() == ".tmd":
tmd = libWiiPy.title.TMD() tmd = libWiiPy.title.TMD()
@ -34,4 +35,4 @@ def handle_fakesign(args):
open(output_path, "wb").write(title.dump_wad()) open(output_path, "wb").write(title.dump_wad())
print("WAD fakesigned successfully!") print("WAD fakesigned successfully!")
else: 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 pathlib
import binascii import binascii
import libWiiPy import libWiiPy
from modules.core import fatal_error
def _print_tmd_info(tmd: libWiiPy.title.TMD): def _print_tmd_info(tmd: libWiiPy.title.TMD):
@ -136,7 +137,7 @@ def handle_info(args):
input_path = pathlib.Path(args.input) input_path = pathlib.Path(args.input)
if not input_path.exists(): 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": if input_path.suffix.lower() == ".tmd":
tmd = libWiiPy.title.TMD() tmd = libWiiPy.title.TMD()
@ -151,4 +152,4 @@ def handle_info(args):
title.load_wad(open(input_path, "rb").read()) title.load_wad(open(input_path, "rb").read())
_print_wad_info(title) _print_wad_info(title)
else: 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 pathlib
import libWiiPy import libWiiPy
from modules.core import fatal_error
def _patch_fakesigning(ios_patcher: libWiiPy.title.IOSPatcher) -> int: 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): def handle_iospatch(args):
input_path = pathlib.Path(args.input) input_path = pathlib.Path(args.input)
if not input_path.exists(): 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 = libWiiPy.title.Title()
title.load_wad(open(input_path, "rb").read()) title.load_wad(open(input_path, "rb").read())
tid = title.tmd.title_id tid = title.tmd.title_id
if tid[:8] != "00000001" or tid[8:] == "00000001" or tid[8:] == "00000002": 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 patch_count = 0
@ -105,7 +106,7 @@ def handle_iospatch(args):
print(f"\nTotal patches applied: {patch_count}") print(f"\nTotal patches applied: {patch_count}")
if patch_count == 0 and args.version is None and args.slot is None: 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" fatal_error("No patches were applied! Please specify patches to apply, and ensure that selected patches are "
"compatible with this IOS.") "compatible with this IOS.")
if patch_count > 0 or args.version is not None or args.slot is not None: if patch_count > 0 or args.version is not None or args.slot is not None:

View File

@ -5,6 +5,7 @@ import hashlib
import pathlib import pathlib
import binascii import binascii
import libWiiPy import libWiiPy
from modules.core import fatal_error
def handle_nus_title(args): def handle_nus_title(args):
@ -23,8 +24,7 @@ def handle_nus_title(args):
try: try:
title_version = int(args.version) title_version = int(args.version)
except ValueError: except ValueError:
print("Enter a valid integer for the Title Version.") fatal_error("The specified Title Version must be a valid integer!")
return
# If --wad was passed, check to make sure the path is okay. # If --wad was passed, check to make sure the path is okay.
if args.wad is not None: if args.wad is not None:
@ -37,7 +37,7 @@ def handle_nus_title(args):
output_dir = pathlib.Path(args.output) output_dir = pathlib.Path(args.output)
if output_dir.exists(): if output_dir.exists():
if output_dir.is_file(): 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: else:
output_dir.mkdir() output_dir.mkdir()
@ -73,8 +73,7 @@ def handle_nus_title(args):
# ticket so that they aren't attempted later. # ticket so that they aren't attempted later.
print(" - No Ticket is available!") print(" - No Ticket is available!")
if wad_file is not None and output_dir is None: if wad_file is not None and output_dir is None:
print("--wad was passed, but this title cannot be packed into a WAD!") fatal_error("--wad was passed, but this title has no common ticket and cannot be packed into a WAD!")
return
# Load the content records from the TMD, and begin iterating over the records. # Load the content records from the TMD, and begin iterating over the records.
title.load_content_records() title.load_content_records()
@ -137,7 +136,7 @@ def handle_nus_content(args):
try: try:
content_id = int.from_bytes(binascii.unhexlify(cid)) content_id = int.from_bytes(binascii.unhexlify(cid))
except binascii.Error: 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. # Use the supplied output path if one was specified, otherwise generate one using the Content ID.
if args.output is None: 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. # 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: if decrypt_content is True and version is None:
print("You must specify the version that the requested content belongs to for decryption!") fatal_error("You must specify the version that the requested content belongs to for decryption!")
return
# Try to download the content, and catch the ValueError libWiiPy will throw if it can't be found. # 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 + "...") print("Downloading content with Content ID " + cid + "...")
try: try:
content_data = libWiiPy.title.download_content(tid, content_id) content_data = libWiiPy.title.download_content(tid, content_id)
except ValueError: 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: if decrypt_content is True:
output_path = output_path.with_suffix(".app") output_path = output_path.with_suffix(".app")
@ -167,8 +165,7 @@ def handle_nus_content(args):
ticket = libWiiPy.title.Ticket() ticket = libWiiPy.title.Ticket()
ticket.load(libWiiPy.title.download_ticket(tid, wiiu_endpoint=True)) ticket.load(libWiiPy.title.download_ticket(tid, wiiu_endpoint=True))
except ValueError: except ValueError:
print("No Ticket is available! Content cannot be decrypted!") fatal_error("No Ticket is available! Content cannot be decrypted.")
return
content_hash = 'gggggggggggggggggggggggggggggggggggggggg' content_hash = 'gggggggggggggggggggggggggggggggggggggggg'
content_size = 0 content_size = 0
@ -182,15 +179,14 @@ def handle_nus_content(args):
# If the default hash never changed, then a content record matching the downloaded content couldn't be found, # 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. # which most likely means that the wrong version was specified.
if content_hash == 'gggggggggggggggggggggggggggggggggggggggg': if content_hash == 'gggggggggggggggggggggggggggggggggggggggg':
print("Content was not found in the TMD from the specified version! Content cannot be decrypted!") fatal_error("Content was not found in the TMD for the specified version! Content cannot be decrypted.")
return
# Manually decrypt the content and verify its hash, which is what libWiiPy's get_content() methods do. We just # 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. # can't really use that here because that require setting up a lot more of the title than is necessary.
content_dec = libWiiPy.title.decrypt_content(content_data, ticket.get_title_key(), content_index, content_size) content_dec = libWiiPy.title.decrypt_content(content_data, ticket.get_title_key(), content_index, content_size)
content_dec_hash = hashlib.sha1(content_dec).hexdigest() content_dec_hash = hashlib.sha1(content_dec).hexdigest()
if content_hash != content_dec_hash: if content_hash != content_dec_hash:
raise ValueError("The decrypted content provided does not match the record at the provided index. \n" fatal_error("The decrypted content provided does not match the record at the provided index. \n"
"Expected hash is: {}\n".format(content_hash) + "Expected hash is: {}\n".format(content_hash) +
"Actual hash is: {}".format(content_dec_hash)) "Actual hash is: {}".format(content_dec_hash))
output_path.write_bytes(content_dec) output_path.write_bytes(content_dec)
@ -208,7 +204,7 @@ def handle_nus_tmd(args):
try: try:
version = int(args.version) version = int(args.version)
except ValueError: except ValueError:
raise ValueError("Enter a valid integer for the TMD Version.") fatal_error("The specified TMD version must be a valid integer!")
else: else:
version = None version = None
@ -227,7 +223,7 @@ def handle_nus_tmd(args):
try: try:
tmd_data = libWiiPy.title.download_tmd(tid, version) tmd_data = libWiiPy.title.download_tmd(tid, version)
except ValueError: 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) output_path.write_bytes(tmd_data)

View File

@ -3,6 +3,7 @@
import pathlib import pathlib
import libWiiPy import libWiiPy
from modules.core import fatal_error
def handle_tmd_remove(args): def handle_tmd_remove(args):
@ -13,7 +14,7 @@ def handle_tmd_remove(args):
output_path = pathlib.Path(args.input) output_path = pathlib.Path(args.input)
if not input_path.exists(): if not input_path.exists():
raise FileNotFoundError(input_path) fatal_error("The specified TMD files does not exist!")
tmd = libWiiPy.title.TMD() tmd = libWiiPy.title.TMD()
tmd.load(input_path.read_bytes()) tmd.load(input_path.read_bytes())
@ -21,7 +22,7 @@ def handle_tmd_remove(args):
if args.index is not None: if args.index is not None:
# Make sure the target index exists, then remove it from the TMD. # Make sure the target index exists, then remove it from the TMD.
if args.index >= len(tmd.content_records): 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.content_records.pop(args.index)
tmd.num_contents -= 1 tmd.num_contents -= 1
# Auto fakesign because we've edited the TMD. # Auto fakesign because we've edited the TMD.
@ -31,14 +32,14 @@ def handle_tmd_remove(args):
elif args.cid is not None: elif args.cid is not None:
if len(args.cid) != 8: 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) target_cid = int(args.cid, 16)
# List Contents IDs in the title, and ensure that the target Content ID exists. # List Contents IDs in the title, and ensure that the target Content ID exists.
valid_ids = [] valid_ids = []
for record in tmd.content_records: for record in tmd.content_records:
valid_ids.append(record.content_id) valid_ids.append(record.content_id)
if target_cid not in valid_ids: 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.content_records.pop(valid_ids.index(target_cid))
tmd.num_contents -= 1 tmd.num_contents -= 1
# Auto fakesign because we've edited the TMD. # Auto fakesign because we've edited the TMD.

View File

@ -4,6 +4,7 @@
import pathlib import pathlib
from random import randint from random import randint
import libWiiPy import libWiiPy
from modules.core import fatal_error
def handle_wad_add(args): def handle_wad_add(args):
@ -15,9 +16,9 @@ def handle_wad_add(args):
output_path = pathlib.Path(args.input) output_path = pathlib.Path(args.input)
if not input_path.exists(): 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(): 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 = libWiiPy.title.Title()
title.load_wad(input_path.read_bytes()) 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. # 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 args.cid is not None:
if len(args.cid) != 8: 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) target_cid = int(args.cid, 16)
for record in title.content.content_records: for record in title.content.content_records:
if target_cid == record.content_id: 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}\".") 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. # If we weren't given a CID, then we need to randomly assign one, and ensure it isn't being used.
else: else:
@ -53,7 +54,7 @@ def handle_wad_add(args):
case "dlc": case "dlc":
target_type = libWiiPy.title.ContentType.DLC target_type = libWiiPy.title.ContentType.DLC
case _: case _:
raise ValueError("The provided content type is invalid!") fatal_error(f"The provided content type \"{args.type}\" is not valid!")
else: else:
target_type = libWiiPy.title.ContentType.NORMAL target_type = libWiiPy.title.ContentType.NORMAL
@ -76,7 +77,7 @@ def handle_wad_convert(args):
elif args.vwii: elif args.vwii:
target = "vWii" target = "vWii"
else: else:
raise ValueError("No valid target was provided!") fatal_error("No valid encryption target was specified!")
if args.output is None: if args.output is None:
match target: match target:
@ -87,22 +88,22 @@ def handle_wad_convert(args):
case "vWii": case "vWii":
output_path = pathlib.Path(input_path.stem + "_vWii" + input_path.suffix) output_path = pathlib.Path(input_path.stem + "_vWii" + input_path.suffix)
case _: case _:
raise ValueError("No valid target was provided!") fatal_error("No valid encryption target was specified!")
else: else:
output_path = pathlib.Path(args.output) output_path = pathlib.Path(args.output)
if not input_path.exists(): 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 = libWiiPy.title.Title()
title.load_wad(input_path.read_bytes()) title.load_wad(input_path.read_bytes())
# First, verify that this WAD isn't already the type we're trying to convert to. # First, verify that this WAD isn't already the type we're trying to convert to.
if title.ticket.is_dev and target == "development": 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": 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": 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. # Get the current type to display later.
if title.ticket.is_dev: if title.ticket.is_dev:
source = "development" 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 # 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. # errors than just a NotADirectoryError if the actual issue is that there's nothing at all.
if not input_path.exists(): 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(): 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 # 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. # 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]')) tmd_list = list(input_path.glob('*.[tT][mM][dD]'))
if len(tmd_list) > 1: 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: 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]) tmd_file = pathlib.Path(tmd_list[0])
# Repeat the same process as above for all .tik files. # Repeat the same process as above for all .tik files.
ticket_list = list(input_path.glob('*.[tT][iI][kK]')) ticket_list = list(input_path.glob('*.[tT][iI][kK]'))
if len(ticket_list) > 1: 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: 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]) ticket_file = pathlib.Path(ticket_list[0])
# And one more time for all .cert files. # And one more time for all .cert files.
cert_list = list(input_path.glob('*.[cC][eE][rR][tT]')) cert_list = list(input_path.glob('*.[cC][eE][rR][tT]'))
if len(cert_list) > 1: if len(cert_list) > 1:
raise FileExistsError("More than one certificate file was found! Only one certificate can be packed into a " fatal_error("More than one certificate file was found! Only one certificate can be packed into a WAD.")
"WAD.")
elif len(cert_list) == 0: 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]) cert_file = pathlib.Path(cert_list[0])
# Make sure that there's at least one content to pack. # Make sure that there's at least one content to pack.
content_files = list(input_path.glob("*.[aA][pP][pP]")) content_files = list(input_path.glob("*.[aA][pP][pP]"))
if not content_files: 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() # Open the output file, and load all the component files that we've now verified we have into a libWiiPy Title()
# object. # object.
@ -222,7 +222,7 @@ def handle_wad_remove(args):
output_path = pathlib.Path(args.input) output_path = pathlib.Path(args.input)
if not input_path.exists(): 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 = libWiiPy.title.Title()
title.load_wad(input_path.read_bytes()) title.load_wad(input_path.read_bytes())
@ -234,7 +234,7 @@ def handle_wad_remove(args):
for record in title.content.content_records: for record in title.content.content_records:
valid_indices.append(record.index) valid_indices.append(record.index)
if args.index not in valid_indices: 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) title.content.remove_content_by_index(args.index)
# Auto fakesign because we've edited the title. # Auto fakesign because we've edited the title.
title.fakesign() title.fakesign()
@ -243,14 +243,14 @@ def handle_wad_remove(args):
elif args.cid is not None: elif args.cid is not None:
if len(args.cid) != 8: 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) target_cid = int(args.cid, 16)
# List Contents IDs in the title, and ensure that the target Content ID exists. # List Contents IDs in the title, and ensure that the target Content ID exists.
valid_ids = [] valid_ids = []
for record in title.content.content_records: for record in title.content.content_records:
valid_ids.append(record.content_id) valid_ids.append(record.content_id)
if target_cid not in valid_ids: 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) title.content.remove_content_by_cid(target_cid)
# Auto fakesign because we've edited the title. # Auto fakesign because we've edited the title.
title.fakesign() title.fakesign()
@ -267,9 +267,9 @@ def handle_wad_set(args):
output_path = pathlib.Path(args.input) output_path = pathlib.Path(args.input)
if not input_path.exists(): 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(): 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 = libWiiPy.title.Title()
title.load_wad(input_path.read_bytes()) title.load_wad(input_path.read_bytes())
@ -285,7 +285,7 @@ def handle_wad_set(args):
case "dlc": case "dlc":
target_type = libWiiPy.title.ContentType.DLC target_type = libWiiPy.title.ContentType.DLC
case _: case _:
raise ValueError("The provided content type is invalid!") fatal_error(f"The provided content type \"{args.type}\" is not valid!\"")
else: else:
target_type = None target_type = None
@ -295,7 +295,7 @@ def handle_wad_set(args):
for record in title.content.content_records: for record in title.content.content_records:
existing_indices.append(record.index) existing_indices.append(record.index)
if args.index not in existing_indices: 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: if target_type:
title.set_content(content_data, args.index, content_type=target_type) title.set_content(content_data, args.index, content_type=target_type)
else: else:
@ -309,13 +309,13 @@ def handle_wad_set(args):
elif args.cid is not None: 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 we're replacing based on the CID, then make sure the specified CID is valid and exists.
if len(args.cid) != 8: 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) target_cid = int(args.cid, 16)
existing_cids = [] existing_cids = []
for record in title.content.content_records: for record in title.content.content_records:
existing_cids.append(record.content_id) existing_cids.append(record.content_id)
if target_cid not in existing_cids: 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) target_index = title.content.get_index_from_cid(target_cid)
if target_type: if target_type:
title.set_content(content_data, target_index, content_type=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) output_path = pathlib.Path(args.output)
if not input_path.exists(): 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. # 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.exists():
if output_path.is_file(): 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: else:
output_path.mkdir() 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("output", metavar="OUT", type=str, help="file to output the cIOS to")
cios_parser.add_argument("-c", "--cios-ver", metavar="CIOS", type=str, cios_parser.add_argument("-c", "--cios-ver", metavar="CIOS", type=str,
help="cIOS version from the map to build", required=True) 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)") help="directory to look for cIOS commands in (optional, defaults to current directory)")
cios_parser.add_argument("-s", "--slot", metavar="SLOT", type=int, cios_parser.add_argument("-s", "--slot", metavar="SLOT", type=int,
help="slot that this cIOS will install to (optional, defaults to 249)", default=249) help="slot that this cIOS will install to (optional, defaults to 249)", default=249)