Compare commits

...

2 Commits

10 changed files with 143 additions and 60 deletions

View File

@ -36,7 +36,7 @@ from modules.download_batch import run_nus_download_batch
from modules.download_wii import run_nus_download_wii from modules.download_wii import run_nus_download_wii
from modules.download_dsi import run_nus_download_dsi from modules.download_dsi import run_nus_download_dsi
nusget_version = "1.4.0" nusget_version = "1.4.1"
regions = {"World": ["41"], "USA/NTSC": ["45"], "Europe/PAL": ["50"], "Japan": ["4A"], "Korea": ["4B"], "China": ["43"], regions = {"World": ["41"], "USA/NTSC": ["45"], "Europe/PAL": ["50"], "Japan": ["4A"], "Korea": ["4B"], "China": ["43"],
"Australia/NZ": ["55"]} "Australia/NZ": ["55"]}
@ -45,7 +45,7 @@ regions = {"World": ["41"], "USA/NTSC": ["45"], "Europe/PAL": ["50"], "Japan": [
# Signals needed for the worker used for threading the downloads. # Signals needed for the worker used for threading the downloads.
class WorkerSignals(QObject): class WorkerSignals(QObject):
result = Signal(object) result = Signal(object)
progress = Signal(str) progress = Signal(int, int, str)
# Worker class used to thread the downloads. # Worker class used to thread the downloads.
@ -230,6 +230,18 @@ class MainWindow(QMainWindow, Ui_MainWindow):
return return
self.ui.patch_ios_checkbox.setEnabled(False) self.ui.patch_ios_checkbox.setEnabled(False)
def download_progress_update(self, done, total, log_text):
if done == 0 and total == 0:
self.ui.progress_bar.setRange(0, 0)
elif done == -1 and total == -1:
pass
else:
self.ui.progress_bar.setRange(0, total)
self.ui.progress_bar.setValue(done)
# Pass the text on to the log text updater, if it was provided.
if log_text:
self.update_log_text(log_text)
def update_log_text(self, new_text): def update_log_text(self, new_text):
# This method primarily exists to be the handler for the progress signal emitted by the worker thread. # This method primarily exists to be the handler for the progress signal emitted by the worker thread.
self.log_text += new_text + "\n" self.log_text += new_text + "\n"
@ -385,7 +397,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.ui.pack_vwii_mode_checkbox.isChecked(), self.ui.patch_ios_checkbox.isChecked(), self.ui.pack_vwii_mode_checkbox.isChecked(), self.ui.patch_ios_checkbox.isChecked(),
self.ui.archive_file_entry.text()) self.ui.archive_file_entry.text())
worker.signals.result.connect(self.check_download_result) worker.signals.result.connect(self.check_download_result)
worker.signals.progress.connect(self.update_log_text) worker.signals.progress.connect(self.download_progress_update)
self.threadpool.start(worker) self.threadpool.start(worker)
def check_download_result(self, result): def check_download_result(self, result):
@ -552,7 +564,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.ui.use_wiiu_nus_checkbox.isChecked(), self.ui.use_local_checkbox.isChecked(), self.ui.use_wiiu_nus_checkbox.isChecked(), self.ui.use_local_checkbox.isChecked(),
self.ui.pack_vwii_mode_checkbox.isChecked(), self.ui.patch_ios_checkbox.isChecked()) self.ui.pack_vwii_mode_checkbox.isChecked(), self.ui.patch_ios_checkbox.isChecked())
worker.signals.result.connect(self.check_batch_result) worker.signals.result.connect(self.check_batch_result)
worker.signals.progress.connect(self.update_log_text) worker.signals.progress.connect(self.download_progress_update)
self.threadpool.start(worker) self.threadpool.start(worker)
def choose_output_dir(self): def choose_output_dir(self):
@ -657,16 +669,10 @@ if __name__ == "__main__":
# NUSGet look nice and pretty. # NUSGet look nice and pretty.
app.setStyle("fusion") app.setStyle("fusion")
theme_sheet = "style_dark.qss" theme_sheet = "style_dark.qss"
try: if is_dark_theme():
# Check for an environment variable overriding the theme. This is mostly for theme testing but would also allow theme_sheet = "style_dark.qss"
# you to force a theme. else:
if os.environ["THEME"].lower() == "light": theme_sheet = "style_light.qss"
theme_sheet = "style_light.qss"
except KeyError:
if is_dark_theme():
theme_sheet = "style_dark.qss"
else:
theme_sheet = "style_light.qss"
stylesheet = open(os.path.join(os.path.dirname(__file__), "resources", theme_sheet)).read() stylesheet = open(os.path.join(os.path.dirname(__file__), "resources", theme_sheet)).read()
image_path_prefix = pathlib.Path(os.path.join(os.path.dirname(__file__), "resources")).resolve().as_posix() image_path_prefix = pathlib.Path(os.path.join(os.path.dirname(__file__), "resources")).resolve().as_posix()
stylesheet = stylesheet.replace("{IMAGE_PREFIX}", image_path_prefix) stylesheet = stylesheet.replace("{IMAGE_PREFIX}", image_path_prefix)

View File

@ -53,5 +53,5 @@ def run_nus_download_batch(out_folder: pathlib.Path, titles: List[BatchTitleData
# failed title. # failed title.
result = 1 result = 1
failed_titles.append(title.tid) failed_titles.append(title.tid)
progress_callback.emit(f"Batch download finished.") progress_callback.emit(0, 1, f"Batch download finished.")
return BatchResults(result, warning_titles, failed_titles) return BatchResults(result, warning_titles, failed_titles)

View File

@ -29,10 +29,10 @@ def run_nus_download_dsi(out_folder: pathlib.Path, tid: str, version: str, pack_
title_dir.mkdir(exist_ok=True) title_dir.mkdir(exist_ok=True)
# Announce the title being downloaded, and the version if applicable. # Announce the title being downloaded, and the version if applicable.
if title_version is not None: if title_version is not None:
progress_callback.emit(f"Downloading title {tid} v{title_version}, please wait...") progress_callback.emit(0, 0, f"Downloading title {tid} v{title_version}, please wait...")
else: else:
progress_callback.emit(f"Downloading title {tid} vLatest, please wait...") progress_callback.emit(0, 0, f"Downloading title {tid} vLatest, please wait...")
progress_callback.emit(" - Downloading and parsing TMD...") progress_callback.emit(-1, -1, " - Downloading and parsing TMD...")
# Download a specific TMD version if a version was specified, otherwise just download the latest TMD. # Download a specific TMD version if a version was specified, otherwise just download the latest TMD.
try: try:
if title_version is not None: if title_version is not None:
@ -50,17 +50,17 @@ def run_nus_download_dsi(out_folder: pathlib.Path, tid: str, version: str, pack_
version_dir.joinpath(f"tmd.{title_version}").write_bytes(title.tmd.dump()) 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. # Use a local ticket, if one exists and "use local files" is enabled.
if use_local_chkbox and version_dir.joinpath("tik").exists(): if use_local_chkbox and version_dir.joinpath("tik").exists():
progress_callback.emit(" - Parsing local copy of Ticket...") progress_callback.emit(-1, -1, " - Parsing local copy of Ticket...")
title.load_ticket(version_dir.joinpath("tik").read_bytes()) title.load_ticket(version_dir.joinpath("tik").read_bytes())
else: else:
progress_callback.emit(" - Downloading and parsing Ticket...") progress_callback.emit(-1, -1, " - Downloading and parsing Ticket...")
try: try:
title.load_ticket(libTWLPy.download_ticket(tid)) title.load_ticket(libTWLPy.download_ticket(tid))
version_dir.joinpath("tik").write_bytes(title.ticket.dump()) version_dir.joinpath("tik").write_bytes(title.ticket.dump())
except ValueError: except ValueError:
# If libTWLPy returns an error, then no ticket is available. Log this, and disable options requiring a # If libTWLPy returns an error, then no ticket is available. Log this, and disable options requiring a
# ticket so that they aren't attempted later. # ticket so that they aren't attempted later.
progress_callback.emit(" - No Ticket is available!") progress_callback.emit(-1, -1, " - No Ticket is available!")
pack_tad_enabled = False pack_tad_enabled = False
decrypt_contents_enabled = False decrypt_contents_enabled = False
# Load the content record from the TMD, and download the content it lists. DSi titles only have one content. # Load the content record from the TMD, and download the content it lists. DSi titles only have one content.
@ -68,13 +68,13 @@ def run_nus_download_dsi(out_folder: pathlib.Path, tid: str, version: str, pack_
content_file_name = f"{title.tmd.content_record.content_id:08X}" content_file_name = f"{title.tmd.content_record.content_id:08X}"
# Check for a local copy of the current content if "use local files" is enabled, and use it. # Check for a local copy of the current content if "use local files" is enabled, and use it.
if use_local_chkbox and version_dir.joinpath(content_file_name).exists(): if use_local_chkbox and version_dir.joinpath(content_file_name).exists():
progress_callback.emit(" - Using local copy of content") progress_callback.emit(-1, -1, " - Using local copy of content")
content = version_dir.joinpath(content_file_name).read_bytes() content = version_dir.joinpath(content_file_name).read_bytes()
else: else:
progress_callback.emit(f" - Downloading content (Content ID: {title.tmd.content_record.content_id}, Size: " progress_callback.emit(-1, -1, f" - Downloading content (Content ID: {title.tmd.content_record.content_id}, Size: "
f"{title.tmd.content_record.content_size} bytes)...") f"{title.tmd.content_record.content_size} bytes)...")
content = libTWLPy.download_content(tid, title.tmd.content_record.content_id) content = libTWLPy.download_content(tid, title.tmd.content_record.content_id)
progress_callback.emit(" - Done!") progress_callback.emit(-1, -1, " - Done!")
# If keep encrypted contents is on, write out the content after its downloaded. # If keep encrypted contents is on, write out the content after its downloaded.
if keep_enc_chkbox is True: if keep_enc_chkbox is True:
version_dir.joinpath(content_file_name).write_bytes(content) version_dir.joinpath(content_file_name).write_bytes(content)
@ -82,7 +82,7 @@ def run_nus_download_dsi(out_folder: pathlib.Path, tid: str, version: str, pack_
# If decrypt local contents is still true, decrypt the content and write out the decrypted file. # If decrypt local contents is still true, decrypt the content and write out the decrypted file.
if decrypt_contents_enabled is True: if decrypt_contents_enabled is True:
try: try:
progress_callback.emit(f" - Decrypting content (Content ID: {title.tmd.content_record.content_id})...") progress_callback.emit(-1, -1, f" - Decrypting content (Content ID: {title.tmd.content_record.content_id})...")
dec_content = title.get_content() dec_content = title.get_content()
content_file_name = f"{title.tmd.content_record.content_id:08X}.app" content_file_name = f"{title.tmd.content_record.content_id:08X}.app"
version_dir.joinpath(content_file_name).write_bytes(dec_content) version_dir.joinpath(content_file_name).write_bytes(dec_content)
@ -93,10 +93,10 @@ def run_nus_download_dsi(out_folder: pathlib.Path, tid: str, version: str, pack_
# If pack TAD is still true, pack the TMD, ticket, and content into a TAD. # If pack TAD is still true, pack the TMD, ticket, and content into a TAD.
if pack_tad_enabled is True: if pack_tad_enabled is True:
# Get the TAD certificate chain, courtesy of libTWLPy. # Get the TAD certificate chain, courtesy of libTWLPy.
progress_callback.emit(" - Building certificate...") progress_callback.emit(-1, -1, " - Building certificate...")
title.tad.set_cert_data(libTWLPy.download_cert()) title.tad.set_cert_data(libTWLPy.download_cert())
# Use a typed TAD name if there is one, and auto generate one based on the TID and version if there isn't. # Use a typed TAD name if there is one, and auto generate one based on the TID and version if there isn't.
progress_callback.emit("Packing TAD...") progress_callback.emit(-1, -1, "Packing TAD...")
if tad_file_name != "" and tad_file_name is not None: if tad_file_name != "" and tad_file_name is not None:
# Batch downloads may insert -vLatest, so if it did we can fill in the real number now. # Batch downloads may insert -vLatest, so if it did we can fill in the real number now.
tad_file_name = tad_file_name.replace("-vLatest", f"-v{title_version}") tad_file_name = tad_file_name.replace("-vLatest", f"-v{title_version}")
@ -110,7 +110,7 @@ def run_nus_download_dsi(out_folder: pathlib.Path, tid: str, version: str, pack_
tad_file_name = tad_file_name.translate({ord(c): None for c in '/\\:*"?<>|'}) tad_file_name = tad_file_name.translate({ord(c): None for c in '/\\:*"?<>|'})
# Have libTWLPy dump the TAD, and write that data out. # Have libTWLPy dump the TAD, and write that data out.
version_dir.joinpath(tad_file_name).write_bytes(title.dump_tad()) version_dir.joinpath(tad_file_name).write_bytes(title.dump_tad())
progress_callback.emit("Download complete!") progress_callback.emit(0, 1, "Download complete!")
# This is where the variables come in. If the state of these variables doesn't match the user's choice by this # 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 TAD packing for a title that doesn't have a ticket. Return # point, it means that they enabled decryption or TAD packing for a title that doesn't have a ticket. Return
# code 1 so that a warning popup is shown informing them of this. # code 1 so that a warning popup is shown informing them of this.

View File

@ -9,6 +9,8 @@ import libWiiPy
def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_wad_chkbox: bool, keep_enc_chkbox: bool, 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, 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): repack_vwii_chkbox: bool, patch_ios: bool, wad_file_name: str, progress_callback=None):
def progress_update(done, total):
progress_callback.emit(done, total, None)
# Actual NUS download function that runs in a separate thread. # Actual NUS download function that runs in a separate thread.
# Immediately knock out any invalidly formatted Title IDs. # Immediately knock out any invalidly formatted Title IDs.
if len(tid) != 16: if len(tid) != 16:
@ -31,16 +33,16 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_
title_dir.mkdir(exist_ok=True) title_dir.mkdir(exist_ok=True)
# Announce the title being downloaded, and the version if applicable. # Announce the title being downloaded, and the version if applicable.
if title_version is not None: if title_version is not None:
progress_callback.emit(f"Downloading title {tid} v{title_version}, please wait...") progress_callback.emit(0, 0, f"Downloading title {tid} v{title_version}, please wait...")
else: else:
progress_callback.emit(f"Downloading title {tid} vLatest, please wait...") progress_callback.emit(-1, -1, f"Downloading title {tid} vLatest, please wait...")
progress_callback.emit(" - Downloading and parsing TMD...") progress_callback.emit(-1, -1, " - Downloading and parsing TMD...")
# Download a specific TMD version if a version was specified, otherwise just download the latest TMD. # Download a specific TMD version if a version was specified, otherwise just download the latest TMD.
try: try:
if title_version is not None: if title_version is not None:
title.load_tmd(libWiiPy.title.download_tmd(tid, title_version, wiiu_endpoint=wiiu_nus_enabled)) title.load_tmd(libWiiPy.title.download_tmd(tid, title_version, wiiu_endpoint=wiiu_nus_enabled, progress=progress_update))
else: else:
title.load_tmd(libWiiPy.title.download_tmd(tid, wiiu_endpoint=wiiu_nus_enabled)) title.load_tmd(libWiiPy.title.download_tmd(tid, wiiu_endpoint=wiiu_nus_enabled, progress=progress_update))
title_version = title.tmd.title_version title_version = title.tmd.title_version
# If libWiiPy returns an error, that means that either the TID or version doesn't exist, so return code -2. # If libWiiPy returns an error, that means that either the TID or version doesn't exist, so return code -2.
except ValueError: except ValueError:
@ -52,17 +54,17 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_
version_dir.joinpath(f"tmd.{title_version}").write_bytes(title.tmd.dump()) 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. # Use a local ticket, if one exists and "use local files" is enabled.
if use_local_chkbox and version_dir.joinpath("tik").exists(): if use_local_chkbox and version_dir.joinpath("tik").exists():
progress_callback.emit(" - Parsing local copy of Ticket...") progress_callback.emit(-1, -1, " - Parsing local copy of Ticket...")
title.load_ticket(version_dir.joinpath("tik").read_bytes()) title.load_ticket(version_dir.joinpath("tik").read_bytes())
else: else:
progress_callback.emit(" - Downloading and parsing Ticket...") progress_callback.emit(-1, -1, " - Downloading and parsing Ticket...")
try: try:
title.load_ticket(libWiiPy.title.download_ticket(tid, wiiu_endpoint=wiiu_nus_enabled)) title.load_ticket(libWiiPy.title.download_ticket(tid, wiiu_endpoint=wiiu_nus_enabled, progress=progress_update))
version_dir.joinpath("tik").write_bytes(title.ticket.dump()) version_dir.joinpath("tik").write_bytes(title.ticket.dump())
except ValueError: except ValueError:
# If libWiiPy returns an error, then no ticket is available. Log this, and disable options requiring a # If libWiiPy returns an error, then no ticket is available. Log this, and disable options requiring a
# ticket so that they aren't attempted later. # ticket so that they aren't attempted later.
progress_callback.emit(" - No Ticket is available!") progress_callback.emit(0, 0, " - No Ticket is available!")
pack_wad_enabled = False pack_wad_enabled = False
decrypt_contents_enabled = False decrypt_contents_enabled = False
# 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.
@ -73,15 +75,15 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_
content_file_name = f"{title.tmd.content_records[content].content_id:08X}" 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. # 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 version_dir.joinpath(content_file_name).exists(): 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)}") progress_callback.emit(-1, -1, 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()) content_list.append(version_dir.joinpath(content_file_name).read_bytes())
else: else:
progress_callback.emit(f" - Downloading content {content + 1} of {len(title.tmd.content_records)} " progress_callback.emit(0, 0, f" - Downloading content {content + 1} of {len(title.tmd.content_records)} "
f"(Content ID: {title.tmd.content_records[content].content_id}, Size: " f"(Content ID: {title.tmd.content_records[content].content_id}, Size: "
f"{title.tmd.content_records[content].content_size} bytes)...") f"{title.tmd.content_records[content].content_size} bytes)...")
content_list.append(libWiiPy.title.download_content(tid, title.tmd.content_records[content].content_id, content_list.append(libWiiPy.title.download_content(tid, title.tmd.content_records[content].content_id,
wiiu_endpoint=wiiu_nus_enabled)) wiiu_endpoint=wiiu_nus_enabled, progress=progress_update))
progress_callback.emit(" - Done!") progress_callback.emit(-1, -1, " - Done!")
# If keep encrypted contents is on, write out each content after its downloaded. # If keep encrypted contents is on, write out each content after its downloaded.
if keep_enc_chkbox is True: if keep_enc_chkbox is True:
version_dir.joinpath(content_file_name).write_bytes(content_list[content]) version_dir.joinpath(content_file_name).write_bytes(content_list[content])
@ -90,7 +92,7 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_
if decrypt_contents_enabled is True: if decrypt_contents_enabled is True:
try: try:
for content in range(len(title.tmd.content_records)): for content in range(len(title.tmd.content_records)):
progress_callback.emit(f" - Decrypting content {content + 1} of {len(title.tmd.content_records)} " progress_callback.emit(-1, -1, f" - Decrypting content {content + 1} of {len(title.tmd.content_records)} "
f"(Content ID: {title.tmd.content_records[content].content_id})...") f"(Content ID: {title.tmd.content_records[content].content_id})...")
dec_content = title.get_content_by_index(content) dec_content = title.get_content_by_index(content)
content_file_name = f"{title.tmd.content_records[content].content_id:08X}.app" content_file_name = f"{title.tmd.content_records[content].content_id:08X}.app"
@ -105,15 +107,15 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_
# re-encrypted with the common key instead of the vWii key, so that the title can be installed from within # re-encrypted with the common key instead of the vWii key, so that the title can be installed from within
# vWii mode. (vWii mode does not have access to the vWii key, only Wii U mode has that.) # 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"): 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...") progress_callback.emit(-1, -1, " - Re-encrypting Title Key with the common key...")
title_key_common = libWiiPy.title.encrypt_title_key(title.ticket.get_title_key(), 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.common_key_index = 0
title.ticket.title_key_enc = title_key_common title.ticket.title_key_enc = title_key_common
# Get the WAD certificate chain, courtesy of libWiiPy. # Get the WAD certificate chain, courtesy of libWiiPy.
progress_callback.emit(" - Building certificate...") progress_callback.emit(-1, -1, " - Building certificate...")
title.load_cert_chain(libWiiPy.title.download_cert_chain(wiiu_endpoint=wiiu_nus_enabled)) title.load_cert_chain(libWiiPy.title.download_cert_chain(wiiu_endpoint=wiiu_nus_enabled))
# Use a typed WAD name if there is one, and auto generate one based on the TID and version if there isn't. # 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...") progress_callback.emit(-1, -1, " - Packing WAD...")
if wad_file_name != "" and wad_file_name is not None: if wad_file_name != "" and wad_file_name is not None:
# Batch downloads may insert -vLatest, so if it did we can fill in the real number now. # Batch downloads may insert -vLatest, so if it did we can fill in the real number now.
wad_file_name = wad_file_name.replace("-vLatest", f"-v{title_version}") wad_file_name = wad_file_name.replace("-vLatest", f"-v{title_version}")
@ -123,14 +125,14 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_
wad_file_name = f"{tid}-v{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 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): if patch_ios and (tid[:8] == "00000001" and int(tid[-2:], 16) > 2):
progress_callback.emit(" - Patching IOS...") progress_callback.emit(-1, -1, " - Patching IOS...")
ios_patcher = libWiiPy.title.IOSPatcher() ios_patcher = libWiiPy.title.IOSPatcher()
ios_patcher.load(title) ios_patcher.load(title)
patch_count = ios_patcher.patch_all() patch_count = ios_patcher.patch_all()
if patch_count > 0: if patch_count > 0:
progress_callback.emit(f" - Applied {patch_count} patches!") progress_callback.emit(-1, -1, f" - Applied {patch_count} patches!")
else: else:
progress_callback.emit(" - No patches could be applied! Is this a stub IOS?") progress_callback.emit(-1, -1, " - No patches could be applied! Is this a stub IOS?")
title = ios_patcher.dump() title = ios_patcher.dump()
# Append "-PATCHED" to the end of the WAD file name to make it clear that it was modified. # Append "-PATCHED" to the end of the WAD file name to make it clear that it was modified.
wad_file_name = wad_file_name[:-4] + "-PATCHED" + wad_file_name[-4:] wad_file_name = wad_file_name[:-4] + "-PATCHED" + wad_file_name[-4:]
@ -140,7 +142,7 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_
wad_file_name = wad_file_name.translate({ord(c): None for c in '/\\:*"?<>|'}) wad_file_name = wad_file_name.translate({ord(c): None for c in '/\\:*"?<>|'})
# Have libWiiPy dump the WAD, and write that data out. # Have libWiiPy dump the WAD, and write that data out.
version_dir.joinpath(wad_file_name).write_bytes(title.dump_wad()) version_dir.joinpath(wad_file_name).write_bytes(title.dump_wad())
progress_callback.emit("Download complete!") progress_callback.emit(0, 1, "Download complete!")
# This is where the variables come in. If the state of these variables doesn't match the user's choice by this # 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 # point, it means that they enabled decryption or WAD packing for a title that doesn't have a ticket. Return
# code 1 so that a warning popup is shown informing them of this. # code 1 so that a warning popup is shown informing them of this.

View File

@ -1,6 +1,7 @@
# "modules/theme.py", licensed under the MIT license # "modules/theme.py", licensed under the MIT license
# Copyright 2024-2025 NinjaCheetah & Contributors # Copyright 2024-2025 NinjaCheetah & Contributors
import os
import platform import platform
import subprocess import subprocess
@ -43,6 +44,17 @@ def is_dark_theme_linux():
return False return False
def is_dark_theme(): def is_dark_theme():
# First, check for an environment variable overriding the theme, and use that if it exists.
try:
if os.environ["THEME"].lower() == "light":
return False
elif os.environ["THEME"].lower() == "dark":
return True
else:
print(f"Unknown theme specified: \"{os.environ['THEME']}\"")
except KeyError:
pass
# If the theme wasn't overridden, then check the current system theme.
system = platform.system() system = platform.system()
if system == "Windows": if system == "Windows":
return is_dark_theme_windows() return is_dark_theme_windows()

View File

@ -18,9 +18,9 @@ from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient,
QTransform) QTransform)
from PySide6.QtWidgets import (QApplication, QComboBox, QHBoxLayout, QHeaderView, from PySide6.QtWidgets import (QApplication, QComboBox, QHBoxLayout, QHeaderView,
QLabel, QLayout, QLineEdit, QMainWindow, QLabel, QLayout, QLineEdit, QMainWindow,
QMenu, QMenuBar, QPushButton, QSizePolicy, QMenu, QMenuBar, QProgressBar, QPushButton,
QSpacerItem, QTabWidget, QTextBrowser, QTreeView, QSizePolicy, QSpacerItem, QTabWidget, QTextBrowser,
QVBoxLayout, QWidget) QTreeView, QVBoxLayout, QWidget)
from qt.py.ui_WrapCheckboxWidget import WrapCheckboxWidget from qt.py.ui_WrapCheckboxWidget import WrapCheckboxWidget
@ -308,17 +308,25 @@ class Ui_MainWindow(object):
self.log_text_browser = QTextBrowser(self.centralwidget) self.log_text_browser = QTextBrowser(self.centralwidget)
self.log_text_browser.setObjectName(u"log_text_browser") self.log_text_browser.setObjectName(u"log_text_browser")
self.log_text_browser.setMinimumSize(QSize(0, 247)) self.log_text_browser.setMinimumSize(QSize(0, 222))
self.vertical_layout_controls.addWidget(self.log_text_browser) self.vertical_layout_controls.addWidget(self.log_text_browser)
self.progress_bar = QProgressBar(self.centralwidget)
self.progress_bar.setObjectName(u"progress_bar")
self.progress_bar.setMinimumSize(QSize(0, 25))
self.progress_bar.setMaximumSize(QSize(16777215, 30))
self.progress_bar.setValue(0)
self.vertical_layout_controls.addWidget(self.progress_bar)
self.horizontalLayout_3.addLayout(self.vertical_layout_controls) self.horizontalLayout_3.addLayout(self.vertical_layout_controls)
MainWindow.setCentralWidget(self.centralwidget) MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QMenuBar(MainWindow) self.menubar = QMenuBar(MainWindow)
self.menubar.setObjectName(u"menubar") self.menubar.setObjectName(u"menubar")
self.menubar.setGeometry(QRect(0, 0, 1010, 21)) self.menubar.setGeometry(QRect(0, 0, 1010, 30))
self.menuHelp = QMenu(self.menubar) self.menuHelp = QMenu(self.menubar)
self.menuHelp.setObjectName(u"menuHelp") self.menuHelp.setObjectName(u"menuHelp")
MainWindow.setMenuBar(self.menubar) MainWindow.setMenuBar(self.menubar)
@ -367,7 +375,7 @@ class Ui_MainWindow(object):
"hr { height: 1px; border-width: 0; }\n" "hr { height: 1px; border-width: 0; }\n"
"li.unchecked::marker { content: \"\\2610\"; }\n" "li.unchecked::marker { content: \"\\2610\"; }\n"
"li.checked::marker { content: \"\\2612\"; }\n" "li.checked::marker { content: \"\\2612\"; }\n"
"</style></head><body style=\" font-family:'.AppleSystemUIFont'; font-size:13pt; font-weight:400; font-style:normal;\">\n" "</style></head><body style=\" font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;\">\n"
"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;\"><br /></p></body></html>", None)) "<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;\"><br /></p></body></html>", None))
self.menuHelp.setTitle(QCoreApplication.translate("MainWindow", u"Help", None)) self.menuHelp.setTitle(QCoreApplication.translate("MainWindow", u"Help", None))
# retranslateUi # retranslateUi

View File

@ -422,7 +422,7 @@
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>0</width> <width>0</width>
<height>247</height> <height>222</height>
</size> </size>
</property> </property>
<property name="markdown"> <property name="markdown">
@ -435,11 +435,30 @@ p, li { white-space: pre-wrap; }
hr { height: 1px; border-width: 0; } hr { height: 1px; border-width: 0; }
li.unchecked::marker { content: &quot;\2610&quot;; } li.unchecked::marker { content: &quot;\2610&quot;; }
li.checked::marker { content: &quot;\2612&quot;; } li.checked::marker { content: &quot;\2612&quot;; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'.AppleSystemUIFont'; font-size:13pt; font-weight:400; font-style:normal;&quot;&gt; &lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> &lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QProgressBar" name="progress_bar">
<property name="minimumSize">
<size>
<width>0</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>30</height>
</size>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
</layout> </layout>
@ -450,7 +469,7 @@ li.checked::marker { content: &quot;\2612&quot;; }
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1010</width> <width>1010</width>
<height>21</height> <height>30</height>
</rect> </rect>
</property> </property>
<widget class="QMenu" name="menuHelp"> <widget class="QMenu" name="menuHelp">

View File

@ -1,6 +1,6 @@
pyside6 pyside6
nuitka~=2.6.0 nuitka~=2.6.0
libWiiPy git+https://github.com/NinjaCheetah/libWiiPy
libTWLPy libTWLPy
zstandard zstandard
requests requests

View File

@ -369,6 +369,24 @@ QMessageBox QLabel {
color: white; color: white;
} }
QProgressBar {
border: 1px solid rgba(70, 70, 70, 1);
border-radius: 8px;
background-color: #1a1a1a;
text-align: center;
color: white;
padding-left: 1px;
}
QProgressBar::chunk {
background-color: qlineargradient(
x1: 0, y1: 0, x2: 1, y2: 0,
stop: 0 #1a73e8, stop: 1 #5596f4
);
border-radius: 5px;
margin: 0.5px;
}
WrapCheckboxWidget { WrapCheckboxWidget {
show-decoration-selected: 1; show-decoration-selected: 1;
outline: 0; outline: 0;

View File

@ -377,6 +377,24 @@ QMessageBox QLabel {
color: #000000; color: #000000;
} }
QProgressBar {
border: 1px solid rgb(163, 163, 163);
border-radius: 8px;
background-color: #ececec;
text-align: center;
padding: 1px;
color: black;
}
QProgressBar::chunk {
background-color: qlineargradient(
x1: 0, y1: 0, x2: 1, y2: 0,
stop: 0 #1a73e8, stop: 1 #5596f4
);
border-radius: 5px;
margin: 0.5px;
}
WrapCheckboxWidget { WrapCheckboxWidget {
show-decoration-selected: 1; show-decoration-selected: 1;
outline: 0; outline: 0;