Merge major improvements from upstream

This commit is contained in:
2024-12-13 23:09:23 -05:00
16 changed files with 735 additions and 776 deletions

View File

@@ -1,7 +1,6 @@
# "modules/download_wii.py", licensed under the MIT license
# Copyright 2024 NinjaCheetah
import os
import pathlib
from typing import List, Tuple
from .tkey import find_tkey
@@ -12,7 +11,6 @@ from libWiiPy.title.ticket import _TitleLimit
def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_wad_chkbox: bool, keep_enc_chkbox: bool,
decrypt_contents_chkbox: bool, wiiu_nus_chkbox: bool, use_local_chkbox: bool,
repack_vwii_chkbox: bool, patch_ios: bool, wad_file_name: str, progress_callback=None):
#print(version)
# Actual NUS download function that runs in a separate thread.
# Immediately knock out any invalidly formatted Title IDs.
if len(tid) != 16:
@@ -31,14 +29,13 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_
# Create a new libWiiPy Title.
title = libWiiPy.title.Title()
# Make a directory for this title if it doesn't exist.
title_dir = pathlib.Path(os.path.join(out_folder, tid))
if not title_dir.is_dir():
title_dir.mkdir()
title_dir = out_folder.joinpath(tid)
title_dir.mkdir(exist_ok=True)
# Announce the title being downloaded, and the version if applicable.
if title_version is not None:
progress_callback.emit("Downloading title " + tid + " v" + str(title_version) + ", please wait...")
progress_callback.emit(f"Downloading title {tid} v{title_version}, please wait...")
else:
progress_callback.emit("Downloading title " + tid + " vLatest, please wait...")
progress_callback.emit(f"Downloading title {tid} vLatest, please wait...")
progress_callback.emit(" - Downloading and parsing TMD...")
# Download a specific TMD version if a version was specified, otherwise just download the latest TMD.
try:
@@ -51,26 +48,20 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_
except ValueError:
return -2
# Make a directory for this version if it doesn't exist.
version_dir = pathlib.Path(os.path.join(title_dir, str(title_version)))
if not version_dir.is_dir():
version_dir.mkdir()
version_dir = title_dir.joinpath(str(title_version))
version_dir.mkdir(exist_ok=True)
# Write out the TMD to a file.
tmd_out = open(os.path.join(version_dir, "tmd." + str(title_version)), "wb")
tmd_out.write(title.tmd.dump())
tmd_out.close()
version_dir.joinpath(f"tmd.{title_version}").write_bytes(title.tmd.dump())
# Use a local ticket, if one exists and "use local files" is enabled.
forge_ticket = False
if use_local_chkbox is True and os.path.exists(os.path.join(version_dir, "tik")):
if use_local_chkbox and version_dir.joinpath("tik").exists():
progress_callback.emit(" - Parsing local copy of Ticket...")
local_ticket = open(os.path.join(version_dir, "tik"), "rb")
title.load_ticket(local_ticket.read())
title.load_ticket(version_dir.joinpath("tik").read_bytes())
else:
progress_callback.emit(" - Downloading and parsing Ticket...")
try:
title.load_ticket(libWiiPy.title.download_ticket(tid, wiiu_endpoint=wiiu_nus_enabled))
ticket_out = open(os.path.join(version_dir, "tik"), "wb")
ticket_out.write(title.ticket.dump())
ticket_out.close()
version_dir.joinpath("tik").write_bytes(title.ticket.dump())
except ValueError:
# If libWiiPy returns an error, then no ticket is available. Try to forge a ticket after we download the
# content.
@@ -80,31 +71,22 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_
title.load_content_records()
content_list = []
for content in range(len(title.tmd.content_records)):
# Generate the correct file name by converting the content ID into hex, minus the 0x, and then appending
# that to the end of 000000. I refuse to believe there isn't a better way to do this here and in libWiiPy.
content_file_name = hex(title.tmd.content_records[content].content_id)[2:]
while len(content_file_name) < 8:
content_file_name = "0" + content_file_name
# Generate the correct file name by converting the content ID into hex.
content_file_name = f"{title.tmd.content_records[content].content_id:08X}"
# Check for a local copy of the current content if "use local files" is enabled, and use it.
if use_local_chkbox is True and os.path.exists(os.path.join(version_dir,
content_file_name)):
progress_callback.emit(" - Using local copy of content " + str(content + 1) + " of " +
str(len(title.tmd.content_records)))
local_file = open(os.path.join(version_dir, content_file_name), "rb")
content_list.append(local_file.read())
if use_local_chkbox is True and version_dir.joinpath(content_file_name).exists():
progress_callback.emit(f" - Using local copy of content {content + 1} of {len(title.tmd.content_records)}")
content_list.append(version_dir.joinpath(content_file_name).read_bytes())
else:
progress_callback.emit(" - Downloading content " + str(content + 1) + " of " +
str(len(title.tmd.content_records)) + " (Content ID: " +
str(title.tmd.content_records[content].content_id) + ", Size: " +
str(title.tmd.content_records[content].content_size) + " bytes)...")
progress_callback.emit(f" - Downloading content {content + 1} of {len(title.tmd.content_records)} "
f"(Content ID: {title.tmd.content_records[content].content_id}, Size: "
f"{title.tmd.content_records[content].content_size} bytes)...")
content_list.append(libWiiPy.title.download_content(tid, title.tmd.content_records[content].content_id,
wiiu_endpoint=wiiu_nus_enabled))
progress_callback.emit(" - Done!")
# If keep encrypted contents is on, write out each content after its downloaded.
if keep_enc_chkbox is True:
enc_content_out = open(os.path.join(version_dir, content_file_name), "wb")
enc_content_out.write(content_list[content])
enc_content_out.close()
version_dir.joinpath(content_file_name).write_bytes(content_list[content])
title.content.content_list = content_list
# Try to forge a Ticket, if a common one wasn't available.
if forge_ticket is True:
@@ -143,17 +125,11 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_
if decrypt_contents_enabled is True:
try:
for content in range(len(title.tmd.content_records)):
progress_callback.emit(" - Decrypting content " + str(content + 1) + " of " +
str(len(title.tmd.content_records)) + " (Content ID: " +
str(title.tmd.content_records[content].content_id) + ")...")
progress_callback.emit(f" - Decrypting content {content + 1} of {len(title.tmd.content_records)} "
f"(Content ID: {title.tmd.content_records[content].content_id})...")
dec_content = title.get_content_by_index(content)
content_file_name = hex(title.tmd.content_records[content].content_id)[2:]
while len(content_file_name) < 8:
content_file_name = "0" + content_file_name
content_file_name = content_file_name + ".app"
dec_content_out = open(os.path.join(version_dir, content_file_name), "wb")
dec_content_out.write(dec_content)
dec_content_out.close()
content_file_name = f"{title.tmd.content_records[content].content_id:08X}.app"
version_dir.joinpath(content_file_name).write_bytes(dec_content)
except ValueError:
# If libWiiPy throws an error during decryption, return code -3. This should only be possible if using
# local encrypted contents that have been altered at present.
@@ -165,8 +141,7 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_
# vWii mode. (vWii mode does not have access to the vWii key, only Wii U mode has that.)
if repack_vwii_chkbox is True and (tid[3] == "7" or tid[7] == "7"):
progress_callback.emit(" - Re-encrypting Title Key with the common key...")
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_key_common = libWiiPy.title.encrypt_title_key(title.ticket.get_title_key(), 0, title.tmd.title_id)
title.ticket.common_key_index = 0
title.ticket.title_key_enc = title_key_common
# Get the WAD certificate chain, courtesy of libWiiPy.
@@ -175,10 +150,10 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_
# Use a typed WAD name if there is one, and auto generate one based on the TID and version if there isn't.
progress_callback.emit(" - Packing WAD...")
if wad_file_name != "" and wad_file_name is not None:
if wad_file_name[-4:] != ".wad":
wad_file_name = wad_file_name + ".wad"
if wad_file_name[-4:].lower() != ".wad":
wad_file_name += ".wad"
else:
wad_file_name = tid + "-v" + str(title_version) + ".wad"
wad_file_name = f"{tid}-v{title_version}.wad"
# If enabled (after we make sure it's an IOS), apply all main IOS patches.
if patch_ios and (tid[:8] == "00000001" and int(tid[-2:], 16) > 2):
progress_callback.emit(" - Patching IOS...")
@@ -191,9 +166,7 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_
progress_callback.emit(" - No patches could be applied! Is this a stub IOS?")
title = ios_patcher.dump()
# Have libWiiPy dump the WAD, and write that data out.
file = open(os.path.join(version_dir, wad_file_name), "wb")
file.write(title.dump_wad())
file.close()
version_dir.joinpath(wad_file_name).write_bytes(title.dump_wad())
progress_callback.emit("Download complete!")
# This is where the variables come in. If the state of these variables doesn't match the user's choice by this
# point, it means that they enabled decryption or WAD packing for a title that doesn't have a ticket. Return
@@ -202,13 +175,15 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_
return 1
return 0
def run_nus_download_wii_batch(out_folder: pathlib.Path, titles: List[Tuple[str, str, str]], pack_wad_chkbox: bool, keep_enc_chkbox: bool,
decrypt_contents_chkbox: bool, wiiu_nus_chkbox: bool, use_local_chkbox: bool,
repack_vwii_chkbox: bool, patch_ios: bool, progress_callback=None):
def run_nus_download_wii_batch(out_folder: pathlib.Path, titles: List[Tuple[str, str, str]], pack_wad_chkbox: bool,
keep_enc_chkbox: bool, decrypt_contents_chkbox: bool, wiiu_nus_chkbox: bool,
use_local_chkbox: bool, repack_vwii_chkbox: bool, patch_ios: bool,
progress_callback=None):
for title in titles:
result = run_nus_download_wii(out_folder, title[0], title[1], pack_wad_chkbox, keep_enc_chkbox, decrypt_contents_chkbox, wiiu_nus_chkbox, use_local_chkbox, repack_vwii_chkbox, patch_ios, f"{title[2]}-{title[1]}.wad", progress_callback)
result = run_nus_download_wii(out_folder, title[0], title[1], pack_wad_chkbox, keep_enc_chkbox,
decrypt_contents_chkbox, wiiu_nus_chkbox, use_local_chkbox, repack_vwii_chkbox,
patch_ios, f"{title[2]}-{title[1]}.wad", progress_callback)
if result != 0:
return result
progress_callback.emit(f"Batch download finished.")
return 0