Joined all WAD re-encryption commands into "wad convert"

This commit is contained in:
Campbell 2024-10-27 11:33:12 -04:00
parent a4c06cae36
commit 530afd4189
Signed by: NinjaCheetah
GPG Key ID: 670C282B3291D63D
4 changed files with 99 additions and 98 deletions

View File

@ -55,9 +55,11 @@ def build_cios(args):
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!") raise ValueError("The provided base IOS does not match the required version for this base!")
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 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:
@ -90,6 +92,7 @@ def build_cios(args):
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 modules. # Next phase of cIOS building is to add the required extra modules.
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:
@ -122,6 +125,7 @@ def build_cios(args):
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!") raise ValueError(f"The provided 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 # If this is a vWii cIOS, then we need to re-encrypt it with the Wii Common key so that it's installable from
# within Wii mode. # within Wii mode.
@ -134,8 +138,6 @@ def build_cios(args):
title.fakesign() title.fakesign()
# Write the new cIOS to the specified output path. # Write the new cIOS to the specified output path.
out_file = open(output_path, "wb") output_path.write_bytes(title.dump_wad())
out_file.write(title.dump_wad())
out_file.close()
print("success") print(f"Successfully built cIOS \"{args.cios_ver}\"!")

View File

@ -88,7 +88,10 @@ def _print_ticket_info(ticket: libWiiPy.title.Ticket):
print(f" Certificate Info: {ticket.signature_issuer} (Unknown)") print(f" Certificate Info: {ticket.signature_issuer} (Unknown)")
match ticket.common_key_index: match ticket.common_key_index:
case 0: case 0:
key = "Common" if ticket.is_dev:
key = "Common (Development)"
else:
key = "Common (Retail)"
case 1: case 1:
key = "Korean" key = "Korean"
case 2: case 2:

View File

@ -68,6 +68,75 @@ def handle_wad_add(args):
print(f"Successfully added new content with Content ID \"{target_cid:08X}\" and type \"{target_type.name}\"!") print(f"Successfully added new content with Content ID \"{target_cid:08X}\" and type \"{target_type.name}\"!")
def handle_wad_convert(args):
input_path = pathlib.Path(args.input)
if args.dev:
target = "development"
elif args.retail:
target = "retail"
elif args.vwii:
target = "vWii"
else:
raise ValueError("No valid target was provided!")
if args.output is None:
match target:
case "development":
output_path = pathlib.Path(input_path.stem + "_dev" + input_path.suffix)
case "retail":
output_path = pathlib.Path(input_path.stem + "_retail" + input_path.suffix)
case "vWii":
output_path = pathlib.Path(input_path.stem + "_vWii" + input_path.suffix)
case _:
raise ValueError("No valid target was provided!")
else:
output_path = pathlib.Path(args.output)
if not input_path.exists():
raise FileNotFoundError(input_path)
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!")
elif not title.ticket.is_dev and not title.tmd.vwii and target == "retail":
raise ValueError("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!")
# Get the current type to display later.
if title.ticket.is_dev:
source = "development"
elif title.tmd.vwii:
source = "vWii"
else:
source = "retail"
# Extract the Title Key so it can be re-encrypted with the correct key later.
title_key = title.ticket.get_title_key()
title_key_new = None
if target == "development":
# Set the development signature info.
title.ticket.signature_issuer = "Root-CA00000002-XS00000006" + title.ticket.signature_issuer[26:]
title.tmd.signature_issuer = "Root-CA00000002-CP00000007" + title.tmd.signature_issuer[26:]
# Re-encrypt the title key with the dev key, and set that in the Ticket.
title_key_new = libWiiPy.title.encrypt_title_key(title_key, 0, title.ticket.title_id, True)
title.ticket.common_key_index = 0
else:
# Set the retail signature info.
title.ticket.signature_issuer = "Root-CA00000001-XS00000003" + title.ticket.signature_issuer[26:]
title.tmd.signature_issuer = "Root-CA00000001-CP00000004" + title.tmd.signature_issuer[26:]
if target == "retail":
title_key_new = libWiiPy.title.encrypt_title_key(title_key, 0, title.ticket.title_id, False)
title.ticket.common_key_index = 0
elif target == "vWii":
title_key_new = libWiiPy.title.encrypt_title_key(title_key, 2, title.ticket.title_id, False)
title.ticket.common_key_index = 2
title.ticket.title_key_enc = title_key_new
title.fakesign()
output_path.write_bytes(title.dump_wad())
print(f"Successfully converted {source} WAD to {target} WAD \"{output_path.name}\"!")
def handle_wad_pack(args): def handle_wad_pack(args):
input_path = pathlib.Path(args.input) input_path = pathlib.Path(args.input)
output_path = pathlib.Path(args.output) output_path = pathlib.Path(args.output)
@ -299,77 +368,3 @@ def handle_wad_unpack(args):
output_path.joinpath(content_file_name).write_bytes(title.get_content_by_index(content_file, skip_hash)) output_path.joinpath(content_file_name).write_bytes(title.get_content_by_index(content_file, skip_hash))
print("WAD file unpacked!") print("WAD file unpacked!")
def handle_wad_d2r(args):
input_path = pathlib.Path(args.input)
output_path = pathlib.Path(input_path.stem + "_retail" + input_path.suffix)
if not input_path.exists():
raise FileNotFoundError(input_path)
title = libWiiPy.title.Title()
title.load_wad(input_path.read_bytes())
# First, verify that this IS actually a dev WAD.
if not title.ticket.is_dev:
raise ValueError("This is not a development WAD!")
# Now, extract the Title Key, change the certs in the TMD/tik, and set the new retail-encrypted Title Key. The certs
# need to be changed so that this WAD can be identified as retail later. The WAD will also be fakesigned, since
# making this change invalidates the signature, so there's no downside.
title_key = title.ticket.get_title_key()
title.ticket.signature_issuer = "Root-CA00000001-XS00000003" + title.ticket.signature_issuer[26:]
title_key_retail = libWiiPy.title.encrypt_title_key(title_key, 0, title.ticket.title_id, False)
title.ticket.title_key_enc = title_key_retail
title.tmd.signature_issuer = "Root-CA00000001-CP00000004" + title.tmd.signature_issuer[26:]
title.fakesign()
output_path.write_bytes(title.dump_wad())
print(f"Successfully converted development WAD to retail WAD \"{output_path.name}\"!")
def handle_wad_r2d(args):
input_path = pathlib.Path(args.input)
output_path = pathlib.Path(input_path.stem + "_dev" + input_path.suffix)
if not input_path.exists():
raise FileNotFoundError(input_path)
title = libWiiPy.title.Title()
title.load_wad(input_path.read_bytes())
# First, verify that this IS actually a retail WAD.
if title.ticket.is_dev:
raise ValueError("This is not a retail WAD!")
# Now, extract the Title Key, change the certs in the TMD/tik, and set the new dev-encrypted Title Key. The certs
# need to be changed so that this WAD can be identified as dev later. The WAD will also be fakesigned, since making
# this change invalidates the signature, so there's no downside.
title_key = title.ticket.get_title_key()
title.ticket.signature_issuer = "Root-CA00000002-XS00000006" + title.ticket.signature_issuer[26:]
title_key_dev = libWiiPy.title.encrypt_title_key(title_key, 0, title.ticket.title_id, True)
title.ticket.title_key_enc = title_key_dev
title.tmd.signature_issuer = "Root-CA00000002-CP00000007" + title.tmd.signature_issuer[26:]
title.fakesign()
output_path.write_bytes(title.dump_wad())
print(f"Successfully converted retail WAD to development WAD \"{output_path.name}\"!")
def handle_wad_v2w(args):
input_path = pathlib.Path(args.input)
output_path = pathlib.Path(input_path.stem + "_Wii" + input_path.suffix)
if not input_path.exists():
raise FileNotFoundError(input_path)
title = libWiiPy.title.Title()
title.load_wad(input_path.read_bytes())
# First, verify that this IS actually a vWii WAD.
if title.ticket.common_key_index != 2:
raise ValueError("This is not a vWii WAD!")
# Now, extract the Title Key, change the required flags in the TMD/tik, and set the new common key encrypted
# Title Key. The WAD will also be fakesigned, since making this change invalidates the signature, so there's no
# downside.
title_key_dec = title.ticket.get_title_key()
title_key_common = libWiiPy.title.encrypt_title_key(title_key_dec, 0, title.tmd.title_id)
title.ticket.title_key_enc = title_key_common
title.ticket.common_key_index = 0
title.fakesign()
output_path.write_bytes(title.dump_wad())
print(f"Successfully re-encrypted vWii WAD to regular WAD \"{output_path.name}\"!")

View File

@ -224,12 +224,26 @@ if __name__ == "__main__":
"(optional, will default to \"Normal\" if not specified)") "(optional, will default to \"Normal\" if not specified)")
wad_add_parser.add_argument("-o", "--output", metavar="OUT", type=str, wad_add_parser.add_argument("-o", "--output", metavar="OUT", type=str,
help="file to output the updated WAD to (optional)") help="file to output the updated WAD to (optional)")
# dev2retail WAD subcommand. # Convert WAD subcommand.
wad_d2r_parser = wad_subparsers.add_parser("dev2retail", help="re-encrypt a dev WAD for retail consoles", wad_convert_parser = wad_subparsers.add_parser("convert", help="re-encrypt a WAD file with a different key",
description="re-encrypt a dev WAD for retail consoles, and update" description="re-encrypt a WAD file with a different key, making it "
"the certs to match; this also fakesigns the WAD") "possible to use the WAD in a different environment; "
wad_d2r_parser.set_defaults(func=handle_wad_d2r) "this fakesigns the WAD by default")
wad_d2r_parser.add_argument("input", metavar="IN", type=str, help="dev WAD file to re-encrypt") wad_convert_parser.set_defaults(func=handle_wad_convert)
wad_convert_parser.add_argument("input", metavar="IN", type=str, help="WAD file to re-encrypt")
wad_convert_targets_lbl = wad_convert_parser.add_argument_group(title="target keys")
wad_convert_targets = wad_convert_targets_lbl.add_mutually_exclusive_group(required=True)
wad_convert_targets.add_argument("-d", "--dev", action="store_true",
help="re-encrypt the WAD with the development common key, allowing it to be "
"installed on development consoles")
wad_convert_targets.add_argument("-r", "--retail", action="store_true",
help="re-encrypt the WAD with the retail common key, allowing it to be installed "
"on retail consoles or inside of Dolphin")
wad_convert_targets.add_argument("-v", "--vwii", action="store_true",
help="re-encrypt the WAD with the vWii key, allowing it to theoretically be "
"installed from Wii U mode if a Wii U mode WAD installer is created")
wad_convert_parser.add_argument("-o", "--output", metavar="OUT", type=str,
help="file to output the new WAD to (optional, defaults to '<old_name>_<key>.wad')")
# Pack WAD subcommand. # Pack WAD subcommand.
wad_pack_parser = wad_subparsers.add_parser("pack", help="pack a directory to a WAD file", wad_pack_parser = wad_subparsers.add_parser("pack", help="pack a directory to a WAD file",
description="pack a directory to a WAD file") description="pack a directory to a WAD file")
@ -252,12 +266,6 @@ if __name__ == "__main__":
help="Content ID of the content to remove") help="Content ID of the content to remove")
wad_remove_parser.add_argument("-o", "--output", metavar="OUT", type=str, wad_remove_parser.add_argument("-o", "--output", metavar="OUT", type=str,
help="file to output the updated WAD to (optional)") help="file to output the updated WAD to (optional)")
# retail2dev WAD subcommand.
wad_r2d_parser = wad_subparsers.add_parser("retail2dev", help="re-encrypt a retail WAD for development consoles",
description="re-encrypt a retail WAD for development consoles, and "
"update the certs to match; this also fakesigns the WAD")
wad_r2d_parser.set_defaults(func=handle_wad_r2d)
wad_r2d_parser.add_argument("input", metavar="IN", type=str, help="retail WAD file to re-encrypt")
# Set WAD subcommand. # Set WAD subcommand.
wad_set_parser = wad_subparsers.add_parser("set", help="set content in a WAD file", wad_set_parser = wad_subparsers.add_parser("set", help="set content in a WAD file",
description="replace existing content in a WAD file with new decrypted " description="replace existing content in a WAD file with new decrypted "
@ -284,13 +292,6 @@ if __name__ == "__main__":
wad_unpack_parser.add_argument("output", metavar="OUT", type=str, help="output directory") wad_unpack_parser.add_argument("output", metavar="OUT", type=str, help="output directory")
wad_unpack_parser.add_argument("-s", "--skip-hash", help="skips validating the hashes of decrypted " wad_unpack_parser.add_argument("-s", "--skip-hash", help="skips validating the hashes of decrypted "
"content", action="store_true") "content", action="store_true")
# vwii2wii WAD subcommand.
wad_v2w_parser = wad_subparsers.add_parser("vwii2wii", help="re-encrypt a vWii WAD with the common key",
description="re-encrypt a vWii WAD with the common key, allowing it to "
"be installed in Dolphin and from within Wii mode on Wii U; "
"this also fakesigns the WAD")
wad_v2w_parser.set_defaults(func=handle_wad_v2w)
wad_v2w_parser.add_argument("input", metavar="IN", type=str, help="vWii WAD file to re-encrypt")
# Parse all the args, and call the appropriate function with all of those args if a valid subcommand was passed. # Parse all the args, and call the appropriate function with all of those args if a valid subcommand was passed.