Added vWii title support and began filling in database

This commit is contained in:
Campbell 2024-05-03 01:25:45 -04:00
parent 625131873b
commit 95dd93b85f
Signed by: NinjaCheetah
GPG Key ID: B547958AF96ED344
4 changed files with 234 additions and 76 deletions

View File

@ -57,7 +57,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.ui.download_btn.clicked.connect(self.download_btn_pressed)
self.ui.pack_wad_chkbox.clicked.connect(self.pack_wad_chkbox_toggled)
# noinspection PyUnresolvedReferences
self.ui.title_tree.header().setSectionResizeMode(QHeaderView.ResizeToContents)
self.ui.wii_title_tree.header().setSectionResizeMode(QHeaderView.ResizeToContents)
# noinspection PyUnresolvedReferences
self.ui.vwii_title_tree.header().setSectionResizeMode(QHeaderView.ResizeToContents)
# Basic intro text set to automatically show when the app loads. This may be changed in the future.
libwiipy_version = "v" + version("libWiiPy")
self.ui.log_text_browser.setText(f"NUSGet v1.0\nDeveloped by NinjaCheetah\nPowered by libWiiPy "
@ -66,46 +68,51 @@ class MainWindow(QMainWindow, Ui_MainWindow):
f"ticket available, and can be decrypted and packed into a WAD. Titles with an"
f" X do not have a ticket, and only their encrypted contents can be saved.")
# Title tree building code.
tree = self.ui.title_tree
self.tree_categories = []
global regions
# Iterate over each category in the database file.
for key in wii_database:
new_category = QTreeWidgetItem()
new_category.setText(0, key)
# Iterate over each title in the current category.
for title in wii_database[key]:
new_title = QTreeWidgetItem()
new_title.setText(0, title["TID"] + " - " + title["Name"])
# Build the list of regions and what versions are offered for each region.
for region in title["Versions"]:
new_region = QTreeWidgetItem()
new_region.setText(0, region)
for title_version in title["Versions"][region]:
new_version = QTreeWidgetItem()
new_version.setText(0, "v" + str(title_version))
new_region.addChild(new_version)
new_title.addChild(new_region)
# Set an indicator icon to show if a ticket is offered for this title or not.
if title["Ticket"] is True:
new_title.setIcon(0, self.style().standardIcon(QStyle.StandardPixmap.SP_DialogApplyButton))
else:
new_title.setIcon(0, self.style().standardIcon(QStyle.StandardPixmap.SP_DialogCancelButton))
new_category.addChild(new_title)
self.tree_categories.append(new_category)
tree.insertTopLevelItems(0, self.tree_categories)
# Connect the double click signal for handling when titles are selected.
tree.itemDoubleClicked.connect(self.onItemClicked)
wii_tree = self.ui.wii_title_tree
vwii_tree = self.ui.vwii_title_tree
self.trees = [[wii_tree, wii_database], [vwii_tree, vwii_database]]
for tree in self.trees:
self.tree_categories = []
global regions
# Iterate over each category in the database file.
for key in tree[1]:
new_category = QTreeWidgetItem()
new_category.setText(0, key)
# Iterate over each title in the current category.
for title in tree[1][key]:
new_title = QTreeWidgetItem()
new_title.setText(0, title["TID"] + " - " + title["Name"])
# Build the list of regions and what versions are offered for each region.
for region in title["Versions"]:
new_region = QTreeWidgetItem()
new_region.setText(0, region)
for title_version in title["Versions"][region]:
new_version = QTreeWidgetItem()
new_version.setText(0, "v" + str(title_version))
new_region.addChild(new_version)
new_title.addChild(new_region)
# Set an indicator icon to show if a ticket is offered for this title or not.
if title["Ticket"] is True:
new_title.setIcon(0, self.style().standardIcon(QStyle.StandardPixmap.SP_DialogApplyButton))
else:
new_title.setIcon(0, self.style().standardIcon(QStyle.StandardPixmap.SP_DialogCancelButton))
new_category.addChild(new_title)
self.tree_categories.append(new_category)
tree[0].insertTopLevelItems(0, self.tree_categories)
# Connect the double click signal for handling when titles are selected.
tree[0].itemDoubleClicked.connect(self.onItemClicked)
@Slot(QTreeWidgetItem, int)
def onItemClicked(self, item, col):
if self.ui.download_btn.isEnabled() is True:
# This is checking to make sure all category names, title names, and region names are not handled as
# valid choices. item.parent().parent().parent().text(0) is terrifying, I know.
if ((item.parent() is not None) and item.parent() not in self.tree_categories
and item.parent().parent() not in self.tree_categories):
# Check to make sure that this is a version and nothing higher. If you've doubled clicked on anything other
# than a version, this returns an AttributeError and the click can be ignored.
try:
category = item.parent().parent().parent().text(0)
for title in wii_database[category]:
except AttributeError:
return
for tree in self.trees:
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.
if item.parent().parent().text(0) == (title["TID"] + " - " + title["Name"]):
selected_title = title
@ -252,8 +259,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# 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()
decrypt_contents_enabled = self.ui.create_dec_chkbox.isChecked()
# Check whether we're going to be using the "fallback" (but faster) Wii U NUS or not.
fallback_enabled = self.ui.use_wiiu_nus_chkbox.isChecked()
# 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()
# Create a new libWiiPy Title.
title = libWiiPy.Title()
# Make a directory for this title if it doesn't exist.
@ -269,9 +276,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# 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(libWiiPy.download_tmd(tid, title_version, fallback_endpoint=fallback_enabled))
title.load_tmd(libWiiPy.download_tmd(tid, title_version, wiiu_endpoint=wiiu_nus_enabled))
else:
title.load_tmd(libWiiPy.download_tmd(tid, fallback_endpoint=fallback_enabled))
title.load_tmd(libWiiPy.download_tmd(tid, wiiu_endpoint=wiiu_nus_enabled))
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.
except ValueError:
@ -292,7 +299,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
else:
progress_callback.emit(" - Downloading and parsing Ticket...")
try:
title.load_ticket(libWiiPy.download_ticket(tid, fallback_endpoint=fallback_enabled))
title.load_ticket(libWiiPy.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()
@ -323,7 +330,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
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,
fallback_endpoint=fallback_enabled))
wiiu_endpoint=wiiu_nus_enabled))
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:
@ -353,7 +360,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
if pack_wad_enabled is True:
# Get the WAD certificate chain, courtesy of libWiiPy.
progress_callback.emit(" - Building certificate...")
title.wad.set_cert_data(libWiiPy.download_cert(fallback_endpoint=fallback_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.
progress_callback.emit("Packing WAD...")
if self.ui.wad_file_entry.text() != "":
@ -389,6 +396,8 @@ if __name__ == "__main__":
# Load the database file, 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"))
wii_database = json.load(database_file)
database_file = open(os.path.join(os.path.dirname(__file__), "data/vwii-database.json"))
vwii_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
# path here is for compatibility with macOS .app bundles, which require the use of absolute paths.
try:

71
data/vwii-database.json Normal file
View File

@ -0,0 +1,71 @@
{
"System": [
{
"Name": "BC-NAND",
"TID": "0000000700000200",
"Versions": {
"World": [6, 7]
},
"Ticket": true,
"WAD Name": "BC-NAND-NUS",
"Danger": "BC-NAND is required for the Wii U to be able to boot any vWii titles. DO NOT modify it, or your Wii U will no longer be able to load into the vWii without restoring it from Wii U mode."
},
{
"Name": "BC-WFS",
"TID": "0000000700000201",
"Versions": {
"World": [1]
},
"Ticket": true,
"WAD Name": "BC-WFS-NUS",
"Danger": "BC-WFS is required for the Wii U to be able to boot Dragon Quest X Online. While not generally essential, this is a system title and should not be modified."
},
{
"Name": "System Menu",
"TID": "0000000700000002",
"Versions": {
"USA/NTSC": [513, 545, 609],
"Europe/PAL": [514, 546, 610],
"Japan": [512, 544, 608]
},
"Ticket": true,
"WAD Name": "vWii-System-Menu",
"Danger": "The System Menu is a critical part of the vWii's operation, and should not be modified. If it is deleted, it will need to be restored from Wii U mode to be able to access the vWii again."
}
],
"System Channels": [
{
"Name": "Region Select",
"TID": "0007000848414CXX",
"Versions": {
"USA/NTSC": [2],
"Europe/PAL": [2],
"Japan": [2]
},
"Ticket": true,
"WAD Name": "Region-Select-NUS",
"Danger": "\"Region Select\" is a hidden channel used during the Wii's initial setup. If this title is damaged or missing, you will not be able to set up your Wii again after a factory reset. This title will need to be reinstalled to fix your console."
},
{
"Name": "Return to Wii U Menu",
"TID": "00070002484356XX",
"Versions": {
"World": [0]
},
"Ticket": true,
"WAD Name": "Return-to-Wii-U-Menu-NUS",
"Danger": "\"Return to Wii U Menu\" is the channel launched from the vWii menu to reboot your console back into Wii U mode. While it generally should not be modified, modifying or removing it will not prevent the vWii from working."
},
{
"Name": "Wii Electronic Manual",
"TID": "00070002484355XX",
"Versions": {
"USA/NTSC": [0, 1, 2, 3, 5],
"Europe/PAL": [0, 1, 2, 3, 4, 5],
"Japan": [0, 1, 2, 3, 5]
},
"Ticket": true,
"WAD Name": "Wii-Electronic-Manual-NUS"
}
]
}

View File

@ -17,8 +17,8 @@ from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QApplication, QCheckBox, QHBoxLayout, QHeaderView,
QLabel, QLineEdit, QMainWindow, QMenuBar,
QPushButton, QSizePolicy, QTextBrowser, QTreeWidget,
QTreeWidgetItem, QVBoxLayout, QWidget)
QPushButton, QSizePolicy, QTabWidget, QTextBrowser,
QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget)
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
@ -41,18 +41,46 @@ class Ui_MainWindow(object):
self.verticalLayout.addWidget(self.label_2)
self.title_tree = QTreeWidget(self.centralwidget)
self.platform_tabs = QTabWidget(self.centralwidget)
self.platform_tabs.setObjectName(u"platform_tabs")
self.wii_tab = QWidget()
self.wii_tab.setObjectName(u"wii_tab")
self.verticalLayout_2 = QVBoxLayout(self.wii_tab)
self.verticalLayout_2.setObjectName(u"verticalLayout_2")
self.wii_title_tree = QTreeWidget(self.wii_tab)
__qtreewidgetitem = QTreeWidgetItem()
__qtreewidgetitem.setText(0, u"1");
self.title_tree.setHeaderItem(__qtreewidgetitem)
self.title_tree.setObjectName(u"title_tree")
self.title_tree.setColumnCount(1)
self.title_tree.header().setVisible(False)
self.title_tree.header().setMinimumSectionSize(49)
self.title_tree.header().setDefaultSectionSize(100)
self.title_tree.header().setStretchLastSection(False)
self.wii_title_tree.setHeaderItem(__qtreewidgetitem)
self.wii_title_tree.setObjectName(u"wii_title_tree")
self.wii_title_tree.setColumnCount(1)
self.wii_title_tree.header().setVisible(False)
self.wii_title_tree.header().setMinimumSectionSize(49)
self.wii_title_tree.header().setDefaultSectionSize(100)
self.wii_title_tree.header().setStretchLastSection(False)
self.verticalLayout.addWidget(self.title_tree)
self.verticalLayout_2.addWidget(self.wii_title_tree)
self.platform_tabs.addTab(self.wii_tab, "")
self.vwii_tab = QWidget()
self.vwii_tab.setObjectName(u"vwii_tab")
self.verticalLayout_4 = QVBoxLayout(self.vwii_tab)
self.verticalLayout_4.setObjectName(u"verticalLayout_4")
self.vwii_title_tree = QTreeWidget(self.vwii_tab)
__qtreewidgetitem1 = QTreeWidgetItem()
__qtreewidgetitem1.setText(0, u"1");
self.vwii_title_tree.setHeaderItem(__qtreewidgetitem1)
self.vwii_title_tree.setObjectName(u"vwii_title_tree")
self.vwii_title_tree.setColumnCount(1)
self.vwii_title_tree.header().setVisible(False)
self.vwii_title_tree.header().setMinimumSectionSize(49)
self.vwii_title_tree.header().setDefaultSectionSize(100)
self.vwii_title_tree.header().setStretchLastSection(False)
self.verticalLayout_4.addWidget(self.vwii_title_tree)
self.platform_tabs.addTab(self.vwii_tab, "")
self.verticalLayout.addWidget(self.platform_tabs)
self.horizontalLayout_3.addLayout(self.verticalLayout)
@ -140,12 +168,17 @@ class Ui_MainWindow(object):
self.retranslateUi(MainWindow)
self.platform_tabs.setCurrentIndex(0)
QMetaObject.connectSlotsByName(MainWindow)
# setupUi
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MainWindow", 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.vwii_tab), QCoreApplication.translate("MainWindow", u"vWii", None))
self.tid_entry.setText("")
self.tid_entry.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Title ID", None))
self.label.setText(QCoreApplication.translate("MainWindow", u"v", None))
@ -162,6 +195,6 @@ class Ui_MainWindow(object):
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.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, NOT vWii titles)", None))
self.use_wiiu_nus_chkbox.setText(QCoreApplication.translate("MainWindow", u"Use the Wii U NUS (faster)", None))
# retranslateUi

View File

@ -43,27 +43,72 @@
</widget>
</item>
<item>
<widget class="QTreeWidget" name="title_tree">
<property name="columnCount">
<number>1</number>
<widget class="QTabWidget" name="platform_tabs">
<property name="currentIndex">
<number>0</number>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<attribute name="headerMinimumSectionSize">
<number>49</number>
</attribute>
<attribute name="headerDefaultSectionSize">
<number>100</number>
</attribute>
<attribute name="headerStretchLastSection">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
<widget class="QWidget" name="wii_tab">
<attribute name="title">
<string>Wii</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QTreeWidget" name="wii_title_tree">
<property name="columnCount">
<number>1</number>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<attribute name="headerMinimumSectionSize">
<number>49</number>
</attribute>
<attribute name="headerDefaultSectionSize">
<number>100</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 class="QWidget" name="vwii_tab">
<attribute name="title">
<string>vWii</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QTreeWidget" name="vwii_title_tree">
<property name="columnCount">
<number>1</number>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<attribute name="headerMinimumSectionSize">
<number>49</number>
</attribute>
<attribute name="headerDefaultSectionSize">
<number>100</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>
</item>
</layout>
@ -176,7 +221,7 @@ p, li { white-space: pre-wrap; }
<item>
<widget class="QCheckBox" name="use_wiiu_nus_chkbox">
<property name="text">
<string>Use the Wii U NUS (faster, NOT vWii titles)</string>
<string>Use the Wii U NUS (faster)</string>
</property>
<property name="checked">
<bool>true</bool>