forked from NinjaCheetah/NUSGet
Added vWii title support and began filling in database
This commit is contained in:
parent
625131873b
commit
95dd93b85f
95
NUSGet.py
95
NUSGet.py
@ -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
71
data/vwii-database.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
@ -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
|
||||
|
||||
|
@ -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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user