diff --git a/modules/title/ciosbuild.py b/modules/title/ciosbuild.py index d95ec52..cb920ad 100644 --- a/modules/title/ciosbuild.py +++ b/modules/title/ciosbuild.py @@ -55,9 +55,11 @@ def build_cios(args): 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!") + print(f"Building cIOS \"{args.cios_ver}\" from base IOS{target_base.get('ios')} v{base_version}...") # We're ready to begin building the cIOS now. Find all the tags that have tags, and then apply # the patches listed in them to the content. + print("Patching existing modules...") for content in target_base.findall("content"): patches = content.findall("patch") if patches: @@ -90,6 +92,7 @@ def build_cios(args): 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. + print("Adding required additional modules...") for content in target_base.findall("content"): target_module = content.get("module") if target_module is not None: @@ -122,6 +125,7 @@ def build_cios(args): title.set_title_version(args.version) except ValueError: 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 # within Wii mode. @@ -134,8 +138,6 @@ def build_cios(args): title.fakesign() # Write the new cIOS to the specified output path. - out_file = open(output_path, "wb") - out_file.write(title.dump_wad()) - out_file.close() + output_path.write_bytes(title.dump_wad()) - print("success") + print(f"Successfully built cIOS \"{args.cios_ver}\"!") diff --git a/modules/title/info.py b/modules/title/info.py index 8a124ba..7bc3366 100644 --- a/modules/title/info.py +++ b/modules/title/info.py @@ -88,7 +88,10 @@ def _print_ticket_info(ticket: libWiiPy.title.Ticket): print(f" Certificate Info: {ticket.signature_issuer} (Unknown)") match ticket.common_key_index: case 0: - key = "Common" + if ticket.is_dev: + key = "Common (Development)" + else: + key = "Common (Retail)" case 1: key = "Korean" case 2: diff --git a/modules/title/wad.py b/modules/title/wad.py index b3858fe..394a6e5 100644 --- a/modules/title/wad.py +++ b/modules/title/wad.py @@ -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}\"!") +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): input_path = pathlib.Path(args.input) 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)) 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}\"!") diff --git a/wiipy.py b/wiipy.py index 792d7c4..782c38e 100644 --- a/wiipy.py +++ b/wiipy.py @@ -224,12 +224,26 @@ if __name__ == "__main__": "(optional, will default to \"Normal\" if not specified)") wad_add_parser.add_argument("-o", "--output", metavar="OUT", type=str, help="file to output the updated WAD to (optional)") - # dev2retail WAD subcommand. - wad_d2r_parser = wad_subparsers.add_parser("dev2retail", help="re-encrypt a dev WAD for retail consoles", - description="re-encrypt a dev WAD for retail consoles, and update" - "the certs to match; this also fakesigns the WAD") - wad_d2r_parser.set_defaults(func=handle_wad_d2r) - wad_d2r_parser.add_argument("input", metavar="IN", type=str, help="dev WAD file to re-encrypt") + # Convert WAD subcommand. + wad_convert_parser = wad_subparsers.add_parser("convert", help="re-encrypt a WAD file with a different key", + description="re-encrypt a WAD file with a different key, making it " + "possible to use the WAD in a different environment; " + "this fakesigns the WAD by default") + 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 '_.wad')") # Pack WAD subcommand. wad_pack_parser = wad_subparsers.add_parser("pack", help="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") wad_remove_parser.add_argument("-o", "--output", metavar="OUT", type=str, 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. 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 " @@ -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("-s", "--skip-hash", help="skips validating the hashes of decrypted " "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.