Added DSi title support and began filling out database

This commit is contained in:
Campbell 2024-05-08 14:25:44 -04:00
parent 32af83f476
commit 93e21023ea
No known key found for this signature in database
GPG Key ID: E543751376940756
5 changed files with 448 additions and 52 deletions

195
NUSGet.py
View File

@ -7,6 +7,7 @@ import pathlib
from importlib.metadata import version from importlib.metadata import version
import libWiiPy import libWiiPy
import libTWLPy
from PySide6.QtWidgets import QApplication, QMainWindow, QMessageBox, QTreeWidgetItem, QHeaderView, QStyle from PySide6.QtWidgets import QApplication, QMainWindow, QMessageBox, QTreeWidgetItem, QHeaderView, QStyle
from PySide6.QtCore import QRunnable, Slot, QThreadPool, Signal, QObject from PySide6.QtCore import QRunnable, Slot, QThreadPool, Signal, QObject
@ -14,7 +15,8 @@ from PySide6.QtCore import QRunnable, Slot, QThreadPool, Signal, QObject
from qt.py.ui_MainMenu import Ui_MainWindow from qt.py.ui_MainMenu import Ui_MainWindow
regions = {"World": ["41"], "USA/NTSC": ["45"], "Europe/PAL": ["50"], "Japan": ["4A"], "Korea": ["4B"]} regions = {"World": ["41"], "USA/NTSC": ["45"], "Europe/PAL": ["50"], "Japan": ["4A"], "Korea": ["4B"], "China": ["43"],
"Australia/NZ": ["55"]}
# Signals needed for the worker used for threading the downloads. # Signals needed for the worker used for threading the downloads.
@ -54,11 +56,13 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.log_text = "" self.log_text = ""
self.threadpool = QThreadPool() self.threadpool = QThreadPool()
self.ui.download_btn.clicked.connect(self.download_btn_pressed) self.ui.download_btn.clicked.connect(self.download_btn_pressed)
self.ui.pack_wad_chkbox.clicked.connect(self.pack_wad_chkbox_toggled) self.ui.pack_archive_chkbox.clicked.connect(self.pack_wad_chkbox_toggled)
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
self.ui.wii_title_tree.header().setSectionResizeMode(QHeaderView.ResizeToContents) self.ui.wii_title_tree.header().setSectionResizeMode(QHeaderView.ResizeToContents)
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
self.ui.vwii_title_tree.header().setSectionResizeMode(QHeaderView.ResizeToContents) self.ui.vwii_title_tree.header().setSectionResizeMode(QHeaderView.ResizeToContents)
# noinspection PyUnresolvedReferences
self.ui.dsi_title_tree.header().setSectionResizeMode(QHeaderView.ResizeToContents)
# Basic intro text set to automatically show when the app loads. This may be changed in the future. # Basic intro text set to automatically show when the app loads. This may be changed in the future.
libwiipy_version = "v" + version("libWiiPy") libwiipy_version = "v" + version("libWiiPy")
self.ui.log_text_browser.setText(f"NUSGet v1.0\nDeveloped by NinjaCheetah\nPowered by libWiiPy " self.ui.log_text_browser.setText(f"NUSGet v1.0\nDeveloped by NinjaCheetah\nPowered by libWiiPy "
@ -69,11 +73,13 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# Add console entries to dropdown and attach on change signal. # Add console entries to dropdown and attach on change signal.
self.ui.console_select_dropdown.addItem("Wii") self.ui.console_select_dropdown.addItem("Wii")
self.ui.console_select_dropdown.addItem("vWii") self.ui.console_select_dropdown.addItem("vWii")
self.ui.console_select_dropdown.addItem("DSi")
self.ui.console_select_dropdown.currentIndexChanged.connect(self.selected_console_changed) self.ui.console_select_dropdown.currentIndexChanged.connect(self.selected_console_changed)
# Title tree building code. # Title tree building code.
wii_tree = self.ui.wii_title_tree wii_tree = self.ui.wii_title_tree
vwii_tree = self.ui.vwii_title_tree vwii_tree = self.ui.vwii_title_tree
self.trees = [[wii_tree, wii_database], [vwii_tree, vwii_database]] dsi_tree = self.ui.dsi_title_tree
self.trees = [[wii_tree, wii_database], [vwii_tree, vwii_database], [dsi_tree, dsi_database]]
for tree in self.trees: for tree in self.trees:
self.tree_categories = [] self.tree_categories = []
global regions global regions
@ -115,6 +121,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
except AttributeError: except AttributeError:
return return
for tree in self.trees: for tree in self.trees:
try:
for title in tree[1][category]: for title in tree[1][category]:
# Check to see if the current title matches the selected one, and if it does, pass that info on. # Check to see if the current title matches the selected one, and if it does, pass that info on.
if item.parent().parent().text(0) == (title["TID"] + " - " + title["Name"]): if item.parent().parent().text(0) == (title["TID"] + " - " + title["Name"]):
@ -123,6 +130,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
selected_region = item.parent().text(0) selected_region = item.parent().text(0)
self.ui.console_select_dropdown.setCurrentIndex(self.ui.platform_tabs.currentIndex()) self.ui.console_select_dropdown.setCurrentIndex(self.ui.platform_tabs.currentIndex())
self.load_title_data(selected_title, selected_version, selected_region) self.load_title_data(selected_title, selected_version, selected_region)
except KeyError:
pass
def update_log_text(self, new_text): def update_log_text(self, new_text):
# This function primarily exists to be the handler for the progress signal emitted by the worker thread. # This function primarily exists to be the handler for the progress signal emitted by the worker thread.
@ -149,8 +158,11 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# Load the WAD name, assuming it exists. This shouldn't ever be able to fail as the database has a WAD name # Load the WAD name, assuming it exists. This shouldn't ever be able to fail as the database has a WAD name
# for every single title, regardless of whether it can be packed or not. # for every single title, regardless of whether it can be packed or not.
try: try:
wad_name = selected_title["WAD Name"] + "-v" + selected_version + ".wad" if self.ui.console_select_dropdown.currentText() == "DSi":
self.ui.wad_file_entry.setText(wad_name) archive_name = selected_title["TAD Name"] + "-v" + selected_version + ".tad"
else:
archive_name = selected_title["WAD Name"] + "-v" + selected_version + ".wad"
self.ui.archive_file_entry.setText(archive_name)
except KeyError: except KeyError:
pass pass
# Same idea for the danger string, however this only exists for certain titles and will frequently be an error. # Same idea for the danger string, however this only exists for certain titles and will frequently be an error.
@ -170,7 +182,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
def download_btn_pressed(self): def download_btn_pressed(self):
# Throw an error and make a message box appear if you haven't selected any options to output the title. # Throw an error and make a message box appear if you haven't selected any options to output the title.
if (self.ui.pack_wad_chkbox.isChecked() is False and self.ui.keep_enc_chkbox.isChecked() is False and if (self.ui.pack_archive_chkbox.isChecked() is False and self.ui.keep_enc_chkbox.isChecked() is False and
self.ui.create_dec_chkbox.isChecked() is False): self.ui.create_dec_chkbox.isChecked() is False):
msg_box = QMessageBox() msg_box = QMessageBox()
msg_box.setIcon(QMessageBox.Icon.Critical) msg_box.setIcon(QMessageBox.Icon.Critical)
@ -186,18 +198,21 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.ui.tid_entry.setEnabled(False) self.ui.tid_entry.setEnabled(False)
self.ui.version_entry.setEnabled(False) self.ui.version_entry.setEnabled(False)
self.ui.download_btn.setEnabled(False) self.ui.download_btn.setEnabled(False)
self.ui.pack_wad_chkbox.setEnabled(False) self.ui.pack_archive_chkbox.setEnabled(False)
self.ui.keep_enc_chkbox.setEnabled(False) self.ui.keep_enc_chkbox.setEnabled(False)
self.ui.create_dec_chkbox.setEnabled(False) self.ui.create_dec_chkbox.setEnabled(False)
self.ui.use_local_chkbox.setEnabled(False) self.ui.use_local_chkbox.setEnabled(False)
self.ui.use_wiiu_nus_chkbox.setEnabled(False) self.ui.use_wiiu_nus_chkbox.setEnabled(False)
self.ui.pack_vwii_mode_chkbox.setEnabled(False) self.ui.pack_vwii_mode_chkbox.setEnabled(False)
self.ui.wad_file_entry.setEnabled(False) self.ui.archive_file_entry.setEnabled(False)
self.ui.console_select_dropdown.setEnabled(False) self.ui.console_select_dropdown.setEnabled(False)
self.log_text = "" self.log_text = ""
self.ui.log_text_browser.setText(self.log_text) self.ui.log_text_browser.setText(self.log_text)
# Create a new worker object to handle the download in a new thread. # Create a new worker object to handle the download in a new thread.
worker = Worker(self.run_nus_download) if self.ui.console_select_dropdown.currentText() == "DSi":
worker = Worker(self.run_nus_download_dsi)
else:
worker = Worker(self.run_nus_download_wii)
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.update_log_text)
self.threadpool.start(worker) self.threadpool.start(worker)
@ -234,27 +249,27 @@ class MainWindow(QMainWindow, Ui_MainWindow):
msg_box.setWindowTitle("Ticket Not Available") msg_box.setWindowTitle("Ticket Not Available")
msg_box.setText("No Ticket is Available for the Requested Title!") msg_box.setText("No Ticket is Available for the Requested Title!")
msg_box.setInformativeText( msg_box.setInformativeText(
"A ticket could not be downloaded for the requested title, but you have selected " "A ticket could not be downloaded for the requested title, but you have selected \"Pack installable "
"\"Pack WAD\" or \"Create Decrypted Contents\". These options are not available " " archive\" or \"Create decrypted contents\". These options are not available for titles without a "
"for titles without a ticket. Only encrypted contents have been saved.") " ticket. Only encrypted contents have been saved.")
msg_box.exec() msg_box.exec()
# Now that the thread has closed, unlock the UI to allow for the next download. # Now that the thread has closed, unlock the UI to allow for the next download.
self.ui.tid_entry.setEnabled(True) self.ui.tid_entry.setEnabled(True)
self.ui.version_entry.setEnabled(True) self.ui.version_entry.setEnabled(True)
self.ui.download_btn.setEnabled(True) self.ui.download_btn.setEnabled(True)
self.ui.pack_wad_chkbox.setEnabled(True) self.ui.pack_archive_chkbox.setEnabled(True)
self.ui.keep_enc_chkbox.setEnabled(True) self.ui.keep_enc_chkbox.setEnabled(True)
self.ui.create_dec_chkbox.setEnabled(True) self.ui.create_dec_chkbox.setEnabled(True)
self.ui.use_local_chkbox.setEnabled(True) self.ui.use_local_chkbox.setEnabled(True)
self.ui.use_wiiu_nus_chkbox.setEnabled(True) self.ui.use_wiiu_nus_chkbox.setEnabled(True)
self.ui.console_select_dropdown.setEnabled(True) self.ui.console_select_dropdown.setEnabled(True)
if self.ui.pack_wad_chkbox.isChecked() is True: if self.ui.pack_archive_chkbox.isChecked() is True:
self.ui.wad_file_entry.setEnabled(True) self.ui.archive_file_entry.setEnabled(True)
# Call the dropdown callback because this will automagically handle setting console-specific settings based # Call the dropdown callback because this will automagically handle setting console-specific settings based
# on the currently selected console, and saves on duplicate code. # on the currently selected console, and saves on duplicate code.
self.selected_console_changed() self.selected_console_changed()
def run_nus_download(self, progress_callback): def run_nus_download_wii(self, progress_callback):
# Actual NUS download function that runs in a separate thread. # Actual NUS download function that runs in a separate thread.
tid = self.ui.tid_entry.text() tid = self.ui.tid_entry.text()
# Immediately knock out any invalidly formatted Title IDs. # Immediately knock out any invalidly formatted Title IDs.
@ -267,7 +282,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
except ValueError: except ValueError:
title_version = None title_version = None
# Set variables for these two options so that their state can be compared against the user's choices later. # Set variables for these two options so that their state can be compared against the user's choices later.
pack_wad_enabled = self.ui.pack_wad_chkbox.isChecked() pack_wad_enabled = self.ui.pack_archive_chkbox.isChecked()
decrypt_contents_enabled = self.ui.create_dec_chkbox.isChecked() decrypt_contents_enabled = self.ui.create_dec_chkbox.isChecked()
# Check whether we're going to be using the (faster) Wii U NUS or not. # Check whether we're going to be using the (faster) Wii U NUS or not.
wiiu_nus_enabled = self.ui.use_wiiu_nus_chkbox.isChecked() wiiu_nus_enabled = self.ui.use_wiiu_nus_chkbox.isChecked()
@ -384,8 +399,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
title.wad.set_cert_data(libWiiPy.download_cert(wiiu_endpoint=wiiu_nus_enabled)) title.wad.set_cert_data(libWiiPy.download_cert(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("Packing WAD...")
if self.ui.wad_file_entry.text() != "": if self.ui.archive_file_entry.text() != "":
wad_file_name = self.ui.wad_file_entry.text() wad_file_name = self.ui.archive_file_entry.text()
if wad_file_name[-4:] != ".wad": if wad_file_name[-4:] != ".wad":
wad_file_name = wad_file_name + ".wad" wad_file_name = wad_file_name + ".wad"
else: else:
@ -398,18 +413,146 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# 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.
if ((not pack_wad_enabled and self.ui.pack_wad_chkbox.isChecked()) or if ((not pack_wad_enabled and self.ui.pack_archive_chkbox.isChecked()) or
(not decrypt_contents_enabled and self.ui.create_dec_chkbox.isChecked())):
return 1
return 0
def run_nus_download_dsi(self, progress_callback):
# Actual NUS download function that runs in a separate thread, but DSi flavored.
tid = self.ui.tid_entry.text()
# Immediately knock out any invalidly formatted Title IDs.
if len(tid) != 16:
return -1
# An error here is acceptable, because it may just mean the box is empty. Or the version string is nonsense.
# Either way, just fall back on downloading the latest version of the title.
try:
title_version = int(self.ui.version_entry.text())
except ValueError:
title_version = None
# Set variables for these two options so that their state can be compared against the user's choices later.
pack_tad_enabled = self.ui.pack_archive_chkbox.isChecked()
decrypt_contents_enabled = self.ui.create_dec_chkbox.isChecked()
# Create a new libTWLPy Title.
title = libTWLPy.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()
# 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...")
else:
progress_callback.emit("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:
if title_version is not None:
title.load_tmd(libTWLPy.download_tmd(tid, title_version))
else:
title.load_tmd(libTWLPy.download_tmd(tid))
title_version = title.tmd.title_version
# If libTWLPy returns an error, that means that either the TID or version doesn't exist, so return code -2.
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()
# 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()
# Use a local ticket, if one exists and "use local files" is enabled.
if self.ui.use_local_chkbox.isChecked() is True and os.path.exists(os.path.join(version_dir, "tik")):
progress_callback.emit(" - Parsing local copy of Ticket...")
local_ticket = open(os.path.join(version_dir, "tik"), "rb")
title.load_ticket(local_ticket.read())
else:
progress_callback.emit(" - Downloading and parsing Ticket...")
try:
title.load_ticket(libTWLPy.download_ticket(tid))
ticket_out = open(os.path.join(version_dir, "tik"), "wb")
ticket_out.write(title.ticket.dump())
ticket_out.close()
except ValueError:
# 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.
progress_callback.emit(" - No Ticket is available!")
pack_tad_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.
title.load_content_records()
content_file_name = hex(title.tmd.content_record.content_id)[2:]
while len(content_file_name) < 8:
content_file_name = "0" + content_file_name
# Check for a local copy of the current content if "use local files" is enabled, and use it.
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")
local_file = open(os.path.join(version_dir, content_file_name), "rb")
content = local_file.read()
else:
progress_callback.emit(" - Downloading content (Content ID: " + str(title.tmd.content_record.content_id) +
", Size: " + str(title.tmd.content_record.content_size) + " bytes)...")
content = libTWLPy.download_content(tid, title.tmd.content_record.content_id)
progress_callback.emit(" - Done!")
# If keep encrypted contents is on, write out each content after its downloaded.
if self.ui.keep_enc_chkbox.isChecked() is True:
enc_content_out = open(os.path.join(version_dir, content_file_name), "wb")
enc_content_out.write(content)
enc_content_out.close()
title.content.content = content
# If decrypt local contents is still true, decrypt each content and write out the decrypted file.
if decrypt_contents_enabled is True:
try:
progress_callback.emit(" - Decrypting content (Content ID: " + str(title.tmd.content_record.content_id)
+ ")...")
dec_content = title.get_content()
content_file_name = hex(title.tmd.content_record.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()
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.
return -3
# If pack TAD is still true, pack the TMD, ticket, and content into a TAD.
if pack_tad_enabled is True:
# Get the TAD certificate chain, courtesy of libTWLPy.
progress_callback.emit(" - Building certificate...")
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.
progress_callback.emit("Packing TAD...")
if self.ui.archive_file_entry.text() != "":
tad_file_name = self.ui.archive_file_entry.text()
if tad_file_name[-4:] != ".tad":
tad_file_name = tad_file_name + ".tad"
else:
tad_file_name = tid + "-v" + str(title_version) + ".tad"
# Have libTWLPy dump the TAD, and write that data out.
file = open(os.path.join(version_dir, tad_file_name), "wb")
file.write(title.dump_tad())
file.close()
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 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.
if ((not pack_tad_enabled and self.ui.pack_archive_chkbox.isChecked()) or
(not decrypt_contents_enabled and self.ui.create_dec_chkbox.isChecked())): (not decrypt_contents_enabled and self.ui.create_dec_chkbox.isChecked())):
return 1 return 1
return 0 return 0
def pack_wad_chkbox_toggled(self): def pack_wad_chkbox_toggled(self):
# Simple function to catch when the WAD checkbox is toggled and enable/disable the file name entry box # Simple function to catch when the WAD/TAD checkbox is toggled and enable/disable the file name entry box
# accordingly. # accordingly.
if self.ui.pack_wad_chkbox.isChecked() is True: if self.ui.pack_archive_chkbox.isChecked() is True:
self.ui.wad_file_entry.setEnabled(True) self.ui.archive_file_entry.setEnabled(True)
else: else:
self.ui.wad_file_entry.setEnabled(False) self.ui.archive_file_entry.setEnabled(False)
def selected_console_changed(self): def selected_console_changed(self):
# Callback function to enable or disable console-specific settings based on the selected console. # Callback function to enable or disable console-specific settings based on the selected console.
@ -421,11 +564,13 @@ class MainWindow(QMainWindow, Ui_MainWindow):
if __name__ == "__main__": if __name__ == "__main__":
app = QApplication(sys.argv) app = QApplication(sys.argv)
# Load the database file, this will work for both the raw Python file and compiled standalone/onefile binaries. # Load the database files, this will work for both the raw Python file and compiled standalone/onefile binaries.
database_file = open(os.path.join(os.path.dirname(__file__), "data/wii-database.json")) database_file = open(os.path.join(os.path.dirname(__file__), "data/wii-database.json"))
wii_database = json.load(database_file) wii_database = json.load(database_file)
database_file = open(os.path.join(os.path.dirname(__file__), "data/vwii-database.json")) database_file = open(os.path.join(os.path.dirname(__file__), "data/vwii-database.json"))
vwii_database = json.load(database_file) vwii_database = json.load(database_file)
database_file = open(os.path.join(os.path.dirname(__file__), "data/dsi-database.json"))
dsi_database = json.load(database_file)
# If this is a compiled build, the path needs to be obtained differently than if it isn't. The use of an absolute # If this is a compiled build, the path needs to be obtained differently than if it isn't. The use of an absolute
# path here is for compatibility with macOS .app bundles, which require the use of absolute paths. # path here is for compatibility with macOS .app bundles, which require the use of absolute paths.
try: try:

208
data/dsi-database.json Normal file
View File

@ -0,0 +1,208 @@
{
"System": [
{
"Name": "Nintendo DS Cartridge Whitelist",
"TID": "0003000F484E4341",
"Versions": {
"World": [256, 512]
},
"Ticket": true,
"TAD Name": "Nintendo-DS-Cartridge-Whitelist-NUS"
},
{
"Name": "Launcher (System Menu)",
"TID": "00030017484E41XX",
"Versions": {
"USA/NTSC": [512, 768, 1024, 1280, 1536, 1792],
"Europe/PAL": [512, 768, 1024, 1280, 1536, 1792],
"Japan": [256, 512, 768, 1024, 1280, 1536, 1792],
"Korea": [768, 1024, 1280, 1536, 1792],
"China": [768, 1024, 1280, 1536, 1792],
"Australia/NZ": [512, 768, 1024, 1280, 1536, 1792]
},
"Ticket": true,
"TAD Name": "Launcher-NUS",
"Danger": "wip"
},
{
"Name": "Version Data",
"TID": "0003000F484E4CXX",
"Versions": {
"USA/NTSC": [3, 4, 5, 6, 7, 8, 9],
"Europe/PAL": [3, 4, 5, 6, 7, 8, 9],
"Japan": [1, 2, 3, 4, 5, 6, 7, 8, 9],
"Korea": [5, 6, 7, 8, 9],
"China": [4, 5, 6, 7, 8, 9],
"Australia/NZ": [3, 4, 5, 6, 7, 8, 9]
},
"Ticket": true,
"TAD Name": "Version-Data-NUS"
},
{
"Name": "WiFi Firmware",
"TID": "0003000F484E4341",
"Versions": {
"World": [256, 512]
},
"Ticket": true,
"TAD Name": "WiFi-Firmware-NUS",
"Danger": "wip"
}
],
"System Apps": [
{
"Name": "DS Download Play",
"TID": "00030005484E4441",
"Versions": {
"World": [256]
},
"Ticket": true,
"TAD Name": "DS-Download-Play-NUS",
"Danger": "wip"
},
{
"Name": "Nintendo 3DS Transfer Tool",
"TID": "00030015484E4FXX",
"Versions": {
"USA/NTSC": [0],
"Europe/PAL": [512, 768],
"Japan": [0],
"Korea": [256],
"Australia/NZ": [512, 768]
},
"Ticket": false,
"TAD Name": "Nintendo-3DS-Transfer-Tool-NUS",
"Danger": "wip"
},
{
"Name": "Nintendo DSi Camera",
"TID": "00030005484E49XX",
"Versions": {
"USA/NTSC": [768, 1024],
"Europe/PAL": [768, 1024],
"Japan": [256, 768, 1024],
"Australia/NZ": [768, 1024]
},
"Ticket": true,
"TAD Name": "Nintendo-DSi-Camera-NUS"
},
{
"Name": "Nintendo DSi Shop",
"TID": "00030015484E46XX",
"Versions": {
"USA/NTSC": [1536, 1792, 2048, 2304, 2560, 2816, 3072],
"Europe/PAL": [1536, 1792, 2048, 2304, 2560, 2816, 3072],
"Japan": [1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072],
"Korea": [768],
"China": [768],
"Australia/NZ": [1536, 1792, 2048, 2304, 2560, 2816, 3072]
},
"Ticket": true,
"TAD Name": "Nintendo-DSi-Shop-NUS",
"Danger": "wip"
},
{
"Name": "Nintendo DSi Sound",
"TID": "00030005484E4BXX",
"Versions": {
"USA/NTSC": [256, 512],
"Europe/PAL": [256, 512],
"Japan": [256, 512],
"Australia/NZ": [256, 512]
},
"Ticket": true,
"TAD Name": "Nintendo-DSi-Sound-NUS"
},
{
"Name": "Nintendo Zone",
"TID": "00030005484E4AXX",
"Versions": {
"USA/NTSC": [512, 768],
"Europe/PAL": [512, 768],
"Japan": [512, 768],
"Australia/NZ": [512, 768]
},
"Ticket": true,
"TAD Name": "Nintendo-DSi-Sound-NUS"
},
{
"Name": "System Settings",
"TID": "00030015484E42XX",
"Versions": {
"USA/NTSC": [512, 768],
"Europe/PAL": [512, 768],
"Japan": [512, 768],
"Korea": [768],
"China": [768],
"Australia/NZ": [512, 768]
},
"Ticket": true,
"TAD Name": "System-Settings-NUS"
}
],
"DSiWare": [
{
"Name": "A Little Bit of... Brain Training™: Maths Edition",
"TID": "000300044B4E5256",
"Versions": {
"Europe/PAL": [256]
},
"Ticket": false
},
{
"Name": "A Little Bit of... Dr. Mario™",
"TID": "000300044B443956",
"Versions": {
"Europe/PAL": [0]
},
"Ticket": false
},
{
"Name": "A Little Bit of... Magic Made Fun™: Deep Psyche",
"TID": "000300044B4D39XX",
"Versions": {
"Europe/PAL": [1]
},
"Ticket": false
},
{
"Name": "A Little Bit of... Magic Made Fun™: Funny Face",
"TID": "000300044B4D46XX",
"Versions": {
"Europe/PAL": [1],
"Japan": [0]
},
"Ticket": false
},
{
"Name": "A Little Bit of... Magic Made Fun™: Shuffle Games",
"TID": "000300044B4D53XX",
"Versions": {
"Europe/PAL": [1]
},
"Ticket": false
},
{
"Name": "Nintendo DSi Browser",
"TID": "00030004484E47XX",
"Versions": {
"USA/NTSC": [768],
"Europe/PAL": [768],
"Japan": [768]
},
"Ticket": true,
"TAD Name": "Nintendo-DSi-Browser-NUS"
},
{
"Name": "Flipnote Studio",
"TID": "000300044B4755XX",
"Versions": {
"USA/NTSC": [0],
"Europe/PAL": [0],
"Japan": [512]
},
"Ticket": true,
"TAD Name": "Flipnote-Studio-NUS"
}
]
}

View File

@ -87,6 +87,22 @@ class Ui_MainWindow(object):
self.verticalLayout_4.addWidget(self.vwii_title_tree) self.verticalLayout_4.addWidget(self.vwii_title_tree)
self.platform_tabs.addTab(self.vwii_tab, "") self.platform_tabs.addTab(self.vwii_tab, "")
self.dsi_tab = QWidget()
self.dsi_tab.setObjectName(u"dsi_tab")
self.verticalLayout = QVBoxLayout(self.dsi_tab)
self.verticalLayout.setObjectName(u"verticalLayout")
self.dsi_title_tree = QTreeWidget(self.dsi_tab)
__qtreewidgetitem2 = QTreeWidgetItem()
__qtreewidgetitem2.setText(0, u"1");
self.dsi_title_tree.setHeaderItem(__qtreewidgetitem2)
self.dsi_title_tree.setObjectName(u"dsi_title_tree")
self.dsi_title_tree.setHeaderHidden(True)
self.dsi_title_tree.header().setMinimumSectionSize(49)
self.dsi_title_tree.header().setStretchLastSection(False)
self.verticalLayout.addWidget(self.dsi_title_tree)
self.platform_tabs.addTab(self.dsi_tab, "")
self.vertical_layout_trees.addWidget(self.platform_tabs) self.vertical_layout_trees.addWidget(self.platform_tabs)
@ -144,16 +160,16 @@ class Ui_MainWindow(object):
self.verticalLayout_7.addWidget(self.label_3) self.verticalLayout_7.addWidget(self.label_3)
self.pack_wad_chkbox = QCheckBox(self.centralwidget) self.pack_archive_chkbox = QCheckBox(self.centralwidget)
self.pack_wad_chkbox.setObjectName(u"pack_wad_chkbox") self.pack_archive_chkbox.setObjectName(u"pack_archive_chkbox")
self.verticalLayout_7.addWidget(self.pack_wad_chkbox) self.verticalLayout_7.addWidget(self.pack_archive_chkbox)
self.wad_file_entry = QLineEdit(self.centralwidget) self.archive_file_entry = QLineEdit(self.centralwidget)
self.wad_file_entry.setObjectName(u"wad_file_entry") self.archive_file_entry.setObjectName(u"archive_file_entry")
self.wad_file_entry.setEnabled(False) self.archive_file_entry.setEnabled(False)
self.verticalLayout_7.addWidget(self.wad_file_entry) self.verticalLayout_7.addWidget(self.archive_file_entry)
self.keep_enc_chkbox = QCheckBox(self.centralwidget) self.keep_enc_chkbox = QCheckBox(self.centralwidget)
self.keep_enc_chkbox.setObjectName(u"keep_enc_chkbox") self.keep_enc_chkbox.setObjectName(u"keep_enc_chkbox")
@ -221,7 +237,7 @@ class Ui_MainWindow(object):
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, 30)) self.menubar.setGeometry(QRect(0, 0, 1010, 29))
MainWindow.setMenuBar(self.menubar) MainWindow.setMenuBar(self.menubar)
self.retranslateUi(MainWindow) self.retranslateUi(MainWindow)
@ -238,6 +254,7 @@ class Ui_MainWindow(object):
self.label_2.setText(QCoreApplication.translate("MainWindow", u"Available Titles", None)) self.label_2.setText(QCoreApplication.translate("MainWindow", u"Available Titles", None))
self.platform_tabs.setTabText(self.platform_tabs.indexOf(self.wii_tab), QCoreApplication.translate("MainWindow", u"Wii", None)) self.platform_tabs.setTabText(self.platform_tabs.indexOf(self.wii_tab), QCoreApplication.translate("MainWindow", u"Wii", None))
self.platform_tabs.setTabText(self.platform_tabs.indexOf(self.vwii_tab), QCoreApplication.translate("MainWindow", u"vWii", None)) self.platform_tabs.setTabText(self.platform_tabs.indexOf(self.vwii_tab), QCoreApplication.translate("MainWindow", u"vWii", None))
self.platform_tabs.setTabText(self.platform_tabs.indexOf(self.dsi_tab), QCoreApplication.translate("MainWindow", u"DSi", None))
self.tid_entry.setText("") self.tid_entry.setText("")
self.tid_entry.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Title ID", None)) self.tid_entry.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Title ID", None))
self.label.setText(QCoreApplication.translate("MainWindow", u"v", None)) self.label.setText(QCoreApplication.translate("MainWindow", u"v", None))
@ -246,19 +263,19 @@ class Ui_MainWindow(object):
self.console_select_dropdown.setCurrentText("") self.console_select_dropdown.setCurrentText("")
self.download_btn.setText(QCoreApplication.translate("MainWindow", u"Start Download", None)) self.download_btn.setText(QCoreApplication.translate("MainWindow", u"Start Download", None))
self.label_3.setText(QCoreApplication.translate("MainWindow", u"General Settings", None)) self.label_3.setText(QCoreApplication.translate("MainWindow", u"General Settings", None))
self.pack_wad_chkbox.setText(QCoreApplication.translate("MainWindow", u"Pack installable archive (WAD/TAD)", None)) self.pack_archive_chkbox.setText(QCoreApplication.translate("MainWindow", u"Pack installable archive (WAD/TAD)", None))
self.wad_file_entry.setPlaceholderText(QCoreApplication.translate("MainWindow", u"File Name", None)) self.archive_file_entry.setPlaceholderText(QCoreApplication.translate("MainWindow", u"File Name", None))
self.keep_enc_chkbox.setText(QCoreApplication.translate("MainWindow", u"Keep encrypted contents", None)) self.keep_enc_chkbox.setText(QCoreApplication.translate("MainWindow", u"Keep encrypted contents", None))
self.create_dec_chkbox.setText(QCoreApplication.translate("MainWindow", u"Create decrypted contents (*.app)", None)) self.create_dec_chkbox.setText(QCoreApplication.translate("MainWindow", u"Create decrypted contents (*.app)", None))
self.use_local_chkbox.setText(QCoreApplication.translate("MainWindow", u"Use local files, if they exist", None)) self.use_local_chkbox.setText(QCoreApplication.translate("MainWindow", u"Use local files, if they exist", None))
self.use_wiiu_nus_chkbox.setText(QCoreApplication.translate("MainWindow", u"Use the Wii U NUS (faster)", None)) self.use_wiiu_nus_chkbox.setText(QCoreApplication.translate("MainWindow", u"Use the Wii U NUS (faster, only effects Wii/vWii)", None))
self.label_4.setText(QCoreApplication.translate("MainWindow", u"vWii Title Settings", None)) self.label_4.setText(QCoreApplication.translate("MainWindow", u"vWii Title Settings", None))
self.pack_vwii_mode_chkbox.setText(QCoreApplication.translate("MainWindow", u"Pack for vWii mode instead of Wii U mode", None)) self.pack_vwii_mode_chkbox.setText(QCoreApplication.translate("MainWindow", u"Pack for vWii mode instead of Wii U mode", None))
self.log_text_browser.setMarkdown("") self.log_text_browser.setMarkdown("")
self.log_text_browser.setHtml(QCoreApplication.translate("MainWindow", u"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n" self.log_text_browser.setHtml(QCoreApplication.translate("MainWindow", u"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n" "<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n" "p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;\">\n" "</style></head><body style=\" font-family:'Sans Serif'; font-size:9pt; 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;\"><br /></p></body></html>", None))
# retranslateUi # retranslateUi

View File

@ -127,6 +127,31 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="dsi_tab">
<attribute name="title">
<string>DSi</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTreeWidget" name="dsi_title_tree">
<property name="headerHidden">
<bool>true</bool>
</property>
<attribute name="headerMinimumSectionSize">
<number>49</number>
</attribute>
<attribute name="headerStretchLastSection">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
</widget> </widget>
</item> </item>
</layout> </layout>
@ -221,14 +246,14 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="pack_wad_chkbox"> <widget class="QCheckBox" name="pack_archive_chkbox">
<property name="text"> <property name="text">
<string>Pack installable archive (WAD/TAD)</string> <string>Pack installable archive (WAD/TAD)</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLineEdit" name="wad_file_entry"> <widget class="QLineEdit" name="archive_file_entry">
<property name="enabled"> <property name="enabled">
<bool>false</bool> <bool>false</bool>
</property> </property>
@ -267,7 +292,7 @@
<item> <item>
<widget class="QCheckBox" name="use_wiiu_nus_chkbox"> <widget class="QCheckBox" name="use_wiiu_nus_chkbox">
<property name="text"> <property name="text">
<string>Use the Wii U NUS (faster)</string> <string>Use the Wii U NUS (faster, only effects Wii/vWii)</string>
</property> </property>
<property name="checked"> <property name="checked">
<bool>true</bool> <bool>true</bool>
@ -349,8 +374,8 @@
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt; <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt; &lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; } p, li { white-space: pre-wrap; }
&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;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Sans Serif'; font-size:9pt; 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;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -364,7 +389,7 @@ p, li { white-space: pre-wrap; }
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1010</width> <width>1010</width>
<height>30</height> <height>29</height>
</rect> </rect>
</property> </property>
</widget> </widget>

View File

@ -1,4 +1,5 @@
pyside6 pyside6
nuitka nuitka
git+https://github.com/NinjaCheetah/libWiiPy git+https://github.com/NinjaCheetah/libWiiPy
libTWLPy
zstandard zstandard