diff --git a/NUSGet.py b/NUSGet.py index 52dfd57..6d261f4 100644 --- a/NUSGet.py +++ b/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: diff --git a/data/vwii-database.json b/data/vwii-database.json new file mode 100644 index 0000000..50edc43 --- /dev/null +++ b/data/vwii-database.json @@ -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" + } + ] +} \ No newline at end of file diff --git a/qt/py/ui_MainMenu.py b/qt/py/ui_MainMenu.py index e3e982a..ebaa4b0 100644 --- a/qt/py/ui_MainMenu.py +++ b/qt/py/ui_MainMenu.py @@ -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 diff --git a/qt/ui/MainMenu.ui b/qt/ui/MainMenu.ui index 4e9f915..595e4d5 100644 --- a/qt/ui/MainMenu.ui +++ b/qt/ui/MainMenu.ui @@ -43,27 +43,72 @@ - - - 1 + + + 0 - - false - - - 49 - - - 100 - - - false - - - - 1 - - + + + Wii + + + + + + 1 + + + false + + + 49 + + + 100 + + + false + + + + 1 + + + + + + + + + vWii + + + + + + 1 + + + false + + + 49 + + + 100 + + + false + + + + 1 + + + + + + @@ -176,7 +221,7 @@ p, li { white-space: pre-wrap; } - Use the Wii U NUS (faster, NOT vWii titles) + Use the Wii U NUS (faster) true