NUSGet/NUSD-Py.py

274 lines
11 KiB
Python

import sys
import os
import json
import pathlib
import libWiiPy
from PySide6.QtWidgets import QApplication, QMainWindow, QMessageBox, QTreeWidgetItem
from PySide6.QtCore import QRunnable, Slot, QThreadPool, Signal, QObject, Qt
from qt.py.ui_MainMenu import Ui_MainWindow
regions = [["USA", "USA/NTSC", "45"], ["JAP", "Japan", "4A"], ["EUR", "Europe/PAL", "50"], ["KOR", "Korea", "4B"]]
class WorkerSignals(QObject):
result = Signal(int)
progress = Signal(str)
class Worker(QRunnable):
def __init__(self, fn, **kwargs):
super(Worker, self).__init__()
self.fn = fn
self.kwargs = kwargs
self.signals = WorkerSignals()
self.kwargs['progress_callback'] = self.signals.progress
@Slot()
def run(self):
try:
self.fn(**self.kwargs)
# TODO: Handle errors better than this
except ValueError:
self.signals.result.emit(1)
else:
self.signals.result.emit(0)
class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.log_text = ""
self.threadpool = QThreadPool()
self.ui.download_btn.clicked.connect(self.download_btn_pressed)
self.ui.pack_wad_chkbox.clicked.connect(self.pack_wad_chkbox_toggled)
tree = self.ui.title_tree
self.tree_categories = []
global regions
for key in wii_database:
new_category = QTreeWidgetItem()
new_category.setText(0, key)
for title in wii_database[key]:
new_title = QTreeWidgetItem()
new_title.setText(0, title["TID"] + " - " + title["Name"])
for region in title["Versions"]:
new_region = QTreeWidgetItem()
region_title = ""
if region == "World":
region_title = "World"
else:
for entry in regions:
if entry[0] == region:
region_title = entry[1]
new_region.setText(0, region_title)
for version in title["Versions"][region]:
new_version = QTreeWidgetItem()
new_version.setText(0, "v" + str(version))
new_region.addChild(new_version)
new_title.addChild(new_region)
new_category.addChild(new_title)
self.tree_categories.append(new_category)
tree.insertTopLevelItems(0, self.tree_categories)
tree.itemDoubleClicked.connect(self.onItemClicked)
@Slot(QTreeWidgetItem, int)
def onItemClicked(self, item, col):
global regions
region_names = []
for region in regions:
region_names.append(region[1])
if item.parent() is not None and item.parent() not in self.tree_categories:
category = item.parent().parent().parent().text(0)
for title in wii_database[category]:
if item.parent().parent().text(0) == (title["TID"] + " - " + title["Name"]):
selected_title = title
selected_version = item.text(0)
selected_region = item.parent().text(0)
self.load_title_data(selected_title, selected_version, selected_region)
def update_log_text(self, new_text):
self.log_text += new_text + "\n"
self.ui.log_text_browser.setText(self.log_text)
# Always auto-scroll to the bottom of the log.
scrollBar = self.ui.log_text_browser.verticalScrollBar()
scrollBar.setValue(scrollBar.maximum())
def load_title_data(self, selected_title, selected_version, selected_region=None):
selected_version = selected_version[1:]
if selected_title["TID"][-2:] == "XX":
global regions
region_code = ""
for region in regions:
if region[1] == selected_region:
region_code = region[2]
tid = selected_title["TID"][:-2] + region_code
else:
tid = selected_title["TID"]
self.ui.tid_entry.setText(tid)
self.ui.version_entry.setText(selected_version)
wad_name = selected_title["WAD Name"] + "-v" + selected_version + ".wad"
self.ui.wad_file_entry.setText(wad_name)
danger_text = ""
try:
danger_text = selected_title["Danger"]
except KeyError:
pass
self.log_text = (tid + " - " + selected_title["Name"] + "\n" + "Version: " + selected_version + "\n\n" +
danger_text + "\n")
self.ui.log_text_browser.setText(self.log_text)
def download_btn_pressed(self):
self.ui.download_btn.setEnabled(False)
self.log_text = ""
self.ui.log_text_browser.setText(self.log_text)
worker = Worker(self.run_nus_download)
worker.signals.result.connect(self.check_download_result)
worker.signals.progress.connect(self.update_log_text)
self.threadpool.start(worker)
def check_download_result(self, result):
if result == 1:
msgBox = QMessageBox()
msgBox.setWindowTitle("Invalid Title ID")
msgBox.setIcon(QMessageBox.Icon.Critical)
msgBox.setText("No title with the provided Title ID could be found!")
msgBox.setInformativeText("Please make sure that you have entered a valid Title ID or selected one from the"
" title database.")
msgBox.setStandardButtons(QMessageBox.StandardButton.Ok)
msgBox.setDefaultButton(QMessageBox.StandardButton.Ok)
msgBox.exec()
self.ui.download_btn.setEnabled(True)
def run_nus_download(self, progress_callback):
tid = self.ui.tid_entry.text()
if tid == "":
raise ValueError
try:
version = int(self.ui.version_entry.text())
except ValueError:
version = None
title = libWiiPy.Title()
title_dir = pathlib.Path(os.path.join(out_folder, tid))
if not title_dir.is_dir():
title_dir.mkdir()
if version is not None:
progress_callback.emit("Downloading title " + tid + " v" + str(version) + ", please wait...")
else:
progress_callback.emit("Downloading title " + tid + " vLatest, please wait...")
progress_callback.emit(" - Downloading and parsing TMD...")
if version is not None:
title.load_tmd(libWiiPy.download_tmd(tid, version))
else:
title.load_tmd(libWiiPy.download_tmd(tid))
version = title.tmd.title_version
version_dir = pathlib.Path(os.path.join(title_dir, str(version)))
if not version_dir.is_dir():
version_dir.mkdir()
tmd_out = open(os.path.join(version_dir, "tmd." + str(version)), "wb")
tmd_out.write(title.tmd.dump())
tmd_out.close()
progress_callback.emit(" - Downloading and parsing Ticket...")
title.load_ticket(libWiiPy.download_ticket(tid))
ticket_out = open(os.path.join(version_dir, "tik"), "wb")
ticket_out.write(title.ticket.dump())
ticket_out.close()
title.load_content_records()
content_list = []
for content in range(len(title.tmd.content_records)):
content_id_hex = hex(title.tmd.content_records[content].content_id)[2:]
if len(content_id_hex) < 2:
content_id_hex = "0" + content_id_hex
content_file_name = "000000" + content_id_hex
if self.ui.use_local_chkbox.isChecked() 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())
else:
progress_callback.emit(" - Downloading content " + str(content + 1) + " of " +
str(len(title.tmd.content_records)) + " (" +
str(title.tmd.content_records[content].content_size) + " bytes)...")
content_list.append(libWiiPy.download_content(tid, title.tmd.content_records[content].content_id))
progress_callback.emit(" - Done!")
if self.ui.keep_enc_chkbox.isChecked() is True:
content_id_hex = hex(title.tmd.content_records[content].content_id)[2:]
if len(content_id_hex) < 2:
content_id_hex = "0" + content_id_hex
content_file_name = "000000" + content_id_hex
enc_content_out = open(os.path.join(version_dir, content_file_name), "wb")
enc_content_out.write(content_list[content])
enc_content_out.close()
title.content.content_list = content_list
if self.ui.create_dec_chkbox.isChecked() is True:
for content in range(len(title.tmd.content_records)):
progress_callback.emit(" - Decrypting content " + str(content + 1) + " of " +
str(len(title.tmd.content_records)) + "...")
dec_content = title.get_content_by_index(content)
content_id_hex = hex(title.tmd.content_records[content].content_id)[2:]
if len(content_id_hex) < 2:
content_id_hex = "0" + content_id_hex
content_file_name = "000000" + content_id_hex + ".app"
dec_content_out = open(os.path.join(version_dir, content_file_name), "wb")
dec_content_out.write(dec_content)
dec_content_out.close()
if self.ui.pack_wad_chkbox.isChecked() is True:
progress_callback.emit(" - Building certificate...")
title.wad.set_cert_data(libWiiPy.download_cert())
progress_callback.emit("Packing WAD...")
if self.ui.wad_file_entry.text() != "":
wad_file_name = self.ui.wad_file_entry.text()
if wad_file_name[-4:] != ".wad":
wad_file_name = wad_file_name + ".wad"
else:
wad_file_name = tid + "-v" + str(version) + ".wad"
file = open(os.path.join(version_dir, wad_file_name), "wb")
file.write(title.dump_wad())
file.close()
progress_callback.emit("Download complete!")
def pack_wad_chkbox_toggled(self):
if self.ui.pack_wad_chkbox.isChecked() is True:
self.ui.wad_file_entry.setEnabled(True)
else:
self.ui.wad_file_entry.setEnabled(False)
if __name__ == "__main__":
app = QApplication(sys.argv)
wii_database = json.load(open("wii-database.json", "r"))
out_folder = pathlib.Path("titles")
if not out_folder.is_dir():
out_folder.mkdir()
window = MainWindow()
window.setWindowTitle("NUSD-Py")
window.show()
sys.exit(app.exec())