diff options
author | Arisotura <thetotalworm@gmail.com> | 2021-08-24 17:46:20 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-24 17:46:20 +0200 |
commit | 235da420c8ac0bcec5a21db6f3cce4ae190fb7c5 (patch) | |
tree | 6aaacaa2b8c2f558601ea8321dd8141c8170f46c /src/frontend/qt_sdl | |
parent | 346e8c0b878541d987d3ee2bfe54b555f626f7bf (diff) |
having fun with fatfs (#1189)
* patch TSC coords in DSi mode
* DSiware importer and shit
Diffstat (limited to 'src/frontend/qt_sdl')
-rw-r--r-- | src/frontend/qt_sdl/CMakeLists.txt | 11 | ||||
-rw-r--r-- | src/frontend/qt_sdl/TitleImportDialog.ui | 142 | ||||
-rw-r--r-- | src/frontend/qt_sdl/TitleManagerDialog.cpp | 564 | ||||
-rw-r--r-- | src/frontend/qt_sdl/TitleManagerDialog.h | 133 | ||||
-rw-r--r-- | src/frontend/qt_sdl/TitleManagerDialog.ui | 133 | ||||
-rw-r--r-- | src/frontend/qt_sdl/main.cpp | 17 | ||||
-rw-r--r-- | src/frontend/qt_sdl/main.h | 2 |
7 files changed, 997 insertions, 5 deletions
diff --git a/src/frontend/qt_sdl/CMakeLists.txt b/src/frontend/qt_sdl/CMakeLists.txt index 1ec01b1..3de536c 100644 --- a/src/frontend/qt_sdl/CMakeLists.txt +++ b/src/frontend/qt_sdl/CMakeLists.txt @@ -11,6 +11,7 @@ SET(SOURCES_QT_SDL WifiSettingsDialog.cpp InterfaceSettingsDialog.cpp ROMInfoDialog.cpp + TitleManagerDialog.cpp Input.cpp LAN_PCap.cpp LAN_Socket.cpp @@ -46,11 +47,12 @@ if (USE_QT6) set(Qt6Core_DIR ${QT6_STATIC_BASE}Core) set(Qt6Gui_DIR ${QT6_STATIC_BASE}Gui) set(Qt6Widgets_DIR ${QT6_STATIC_BASE}Widgets) + set(Qt6Network_DIR ${QT6_STATIC_BASE}Network) set(Qt6OpenGL_DIR ${QT6_STATIC_BASE}OpenGL) set(Qt6OpenGLWidgets_DIR ${QT6_STATIC_BASE}OpenGLWidgets) endif() - find_package(Qt6 COMPONENTS Core Gui Widgets OpenGL OpenGLWidgets REQUIRED) - set(QT_LINK_LIBS Qt6::Core Qt6::Gui Qt6::Widgets Qt6::OpenGL Qt6::OpenGLWidgets) + find_package(Qt6 COMPONENTS Core Gui Widgets Network OpenGL OpenGLWidgets REQUIRED) + set(QT_LINK_LIBS Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Network Qt6::OpenGL Qt6::OpenGLWidgets) else() if (BUILD_STATIC AND QT5_STATIC_DIR) set(QT5_STATIC_BASE ${QT5_STATIC_DIR}/lib/cmake/Qt5) @@ -58,9 +60,10 @@ else() set(Qt5Core_DIR ${QT5_STATIC_BASE}Core) set(Qt5Gui_DIR ${QT5_STATIC_BASE}Gui) set(Qt5Widgets_DIR ${QT5_STATIC_BASE}Widgets) + set(Qt5Network_DIR ${QT5_STATIC_BASE}Network) endif() - find_package(Qt5 COMPONENTS Core Gui Widgets REQUIRED) - set(QT_LINK_LIBS Qt5::Core Qt5::Gui Qt5::Widgets) + find_package(Qt5 COMPONENTS Core Gui Widgets Network REQUIRED) + set(QT_LINK_LIBS Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Network) endif() set(CMAKE_AUTOMOC ON) diff --git a/src/frontend/qt_sdl/TitleImportDialog.ui b/src/frontend/qt_sdl/TitleImportDialog.ui new file mode 100644 index 0000000..ca2010b --- /dev/null +++ b/src/frontend/qt_sdl/TitleImportDialog.ui @@ -0,0 +1,142 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>TitleImportDialog</class> + <widget class="QDialog" name="TitleImportDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>495</width> + <height>202</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="windowTitle"> + <string>Import title - melonDS</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="3" column="2"> + <widget class="QPushButton" name="btnTmdBrowse"> + <property name="text"> + <string>Browse...</string> + </property> + </widget> + </item> + <item row="4" column="0" colspan="3"> + <widget class="QRadioButton" name="rbTmdFromNUS"> + <property name="text"> + <string>Download from NUS</string> + </property> + </widget> + </item> + <item row="2" column="0" colspan="3"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Metadata (TMD):</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QRadioButton" name="rbTmdFromFile"> + <property name="text"> + <string>From file:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="txtAppFile"/> + </item> + <item row="7" column="0" colspan="3"> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + <item row="1" column="0" colspan="3"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Executable:</string> + </property> + </widget> + </item> + <item row="5" column="0" colspan="3"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLineEdit" name="txtTmdFile"/> + </item> + <item row="0" column="2"> + <widget class="QPushButton" name="btnAppBrowse"> + <property name="text"> + <string>Browse...</string> + </property> + </widget> + </item> + <item row="6" column="0" colspan="3"> + <widget class="QCheckBox" name="cbReadOnly"> + <property name="whatsThis"> + <string><html><head/><body><p>Makes the title executable and TMD read-only. Prevents DSi system utilities from deleting them.</p></body></html></string> + </property> + <property name="text"> + <string>Make title files read-only</string> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>TitleImportDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>TitleImportDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/frontend/qt_sdl/TitleManagerDialog.cpp b/src/frontend/qt_sdl/TitleManagerDialog.cpp new file mode 100644 index 0000000..b51475f --- /dev/null +++ b/src/frontend/qt_sdl/TitleManagerDialog.cpp @@ -0,0 +1,564 @@ +/* + Copyright 2016-2021 Arisotura + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include <stdio.h> +#include <QFileDialog> +#include <QMenu> + +#include "types.h" +#include "Platform.h" +#include "Config.h" +#include "PlatformConfig.h" +#include "FrontendUtil.h" +#include "DSi_NAND.h" + +#include "TitleManagerDialog.h" +#include "ui_TitleManagerDialog.h" +#include "ui_TitleImportDialog.h" + + +FILE* TitleManagerDialog::curNAND = nullptr; +TitleManagerDialog* TitleManagerDialog::currentDlg = nullptr; + +extern char* EmuDirectory; + + +TitleManagerDialog::TitleManagerDialog(QWidget* parent) : QDialog(parent), ui(new Ui::TitleManagerDialog) +{ + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose); + + ui->lstTitleList->setIconSize(QSize(32, 32)); + + const u32 category = 0x00030004; + std::vector<u32> titlelist; + DSi_NAND::ListTitles(category, titlelist); + + for (std::vector<u32>::iterator it = titlelist.begin(); it != titlelist.end(); it++) + { + u32 titleid = *it; + createTitleItem(category, titleid); + } + + ui->lstTitleList->sortItems(); + + ui->btnImportTitleData->setEnabled(false); + ui->btnExportTitleData->setEnabled(false); + ui->btnDeleteTitle->setEnabled(false); + + { + QMenu* menu = new QMenu(ui->btnImportTitleData); + + actImportTitleData[0] = menu->addAction("public.sav"); + actImportTitleData[0]->setData(QVariant(DSi_NAND::TitleData_PublicSav)); + connect(actImportTitleData[0], &QAction::triggered, this, &TitleManagerDialog::onImportTitleData); + + actImportTitleData[1] = menu->addAction("private.sav"); + actImportTitleData[1]->setData(QVariant(DSi_NAND::TitleData_PrivateSav)); + connect(actImportTitleData[1], &QAction::triggered, this, &TitleManagerDialog::onImportTitleData); + + actImportTitleData[2] = menu->addAction("banner.sav"); + actImportTitleData[2]->setData(QVariant(DSi_NAND::TitleData_BannerSav)); + connect(actImportTitleData[2], &QAction::triggered, this, &TitleManagerDialog::onImportTitleData); + + ui->btnImportTitleData->setMenu(menu); + } + + { + QMenu* menu = new QMenu(ui->btnExportTitleData); + + actExportTitleData[0] = menu->addAction("public.sav"); + actExportTitleData[0]->setData(QVariant(DSi_NAND::TitleData_PublicSav)); + connect(actExportTitleData[0], &QAction::triggered, this, &TitleManagerDialog::onExportTitleData); + + actExportTitleData[1] = menu->addAction("private.sav"); + actExportTitleData[1]->setData(QVariant(DSi_NAND::TitleData_PrivateSav)); + connect(actExportTitleData[1], &QAction::triggered, this, &TitleManagerDialog::onExportTitleData); + + actExportTitleData[2] = menu->addAction("banner.sav"); + actExportTitleData[2]->setData(QVariant(DSi_NAND::TitleData_BannerSav)); + connect(actExportTitleData[2], &QAction::triggered, this, &TitleManagerDialog::onExportTitleData); + + ui->btnExportTitleData->setMenu(menu); + } +} + +TitleManagerDialog::~TitleManagerDialog() +{ + delete ui; +} + +void TitleManagerDialog::createTitleItem(u32 category, u32 titleid) +{ + u32 version; + u8 header[0x1000]; + u8 banner[0x2400]; + + DSi_NAND::GetTitleInfo(category, titleid, version, header, banner); + + u8 icongfx[512]; + u16 iconpal[16]; + memcpy(icongfx, &banner[0x20], 512); + memcpy(iconpal, &banner[0x220], 16*2); + u32 icondata[32*32]; + Frontend::ROMIcon(icongfx, iconpal, icondata); + QImage iconimg((const uchar*)icondata, 32, 32, QImage::Format_ARGB32); + QIcon icon(QPixmap::fromImage(iconimg.copy())); + + // TODO: make it possible to select other languages? + u16 titleraw[129]; + memcpy(titleraw, &banner[0x340], 128*sizeof(u16)); + titleraw[128] = '\0'; + QString title = QString::fromUtf16(titleraw); + title.replace("\n", " · "); + + char gamecode[5]; + *(u32*)&gamecode[0] = *(u32*)&header[0xC]; + gamecode[4] = '\0'; + char extra[128]; + sprintf(extra, "\n(title ID: %s · %08x/%08x · version %08x)", gamecode, category, titleid, version); + + QListWidgetItem* item = new QListWidgetItem(title + QString(extra)); + item->setIcon(icon); + item->setData(Qt::UserRole, QVariant((qulonglong)(((u64)category<<32) | (u64)titleid))); + item->setData(Qt::UserRole+1, QVariant(*(u32*)&header[0x238])); // public.sav size + item->setData(Qt::UserRole+2, QVariant(*(u32*)&header[0x23C])); // private.sav size + item->setData(Qt::UserRole+3, QVariant((u32)((header[0x1BF] & 0x04) ? 0x4000 : 0))); // banner.sav size + ui->lstTitleList->addItem(item); +} + +bool TitleManagerDialog::openNAND() +{ + FILE* bios7i = Platform::OpenLocalFile(Config::DSiBIOS7Path, "rb"); + if (!bios7i) + return false; + + u8 es_keyY[16]; + fseek(bios7i, 0x8308, SEEK_SET); + fread(es_keyY, 16, 1, bios7i); + fclose(bios7i); + + curNAND = Platform::OpenLocalFile(Config::DSiNANDPath, "r+b"); + if (!curNAND) + return false; + + if (!DSi_NAND::Init(curNAND, es_keyY)) + { + fclose(curNAND); + curNAND = nullptr; + return false; + } + + return true; +} + +void TitleManagerDialog::closeNAND() +{ + if (curNAND) + { + DSi_NAND::DeInit(); + + fclose(curNAND); + curNAND = nullptr; + } +} + +void TitleManagerDialog::done(int r) +{ + QDialog::done(r); + + closeDlg(); +} + +void TitleManagerDialog::on_btnImportTitle_clicked() +{ + TitleImportDialog* importdlg = new TitleImportDialog(this, importAppPath, importTmdData, importReadOnly); + importdlg->open(); + connect(importdlg, &TitleImportDialog::finished, this, &TitleManagerDialog::onImportTitleFinished); + + importdlg->show(); +} + +void TitleManagerDialog::onImportTitleFinished(int res) +{ + if (res != QDialog::Accepted) return; + + u32 titleid[2]; + titleid[0] = (importTmdData[0x18C] << 24) | (importTmdData[0x18D] << 16) | (importTmdData[0x18E] << 8) | importTmdData[0x18F]; + titleid[1] = (importTmdData[0x190] << 24) | (importTmdData[0x191] << 16) | (importTmdData[0x192] << 8) | importTmdData[0x193]; + + // remove anything that might hinder the install + DSi_NAND::DeleteTitle(titleid[0], titleid[1]); + + bool importres = DSi_NAND::ImportTitle(importAppPath.toStdString().c_str(), importTmdData, importReadOnly); + if (!importres) + { + // remove a potential half-completed install + DSi_NAND::DeleteTitle(titleid[0], titleid[1]); + + QMessageBox::critical(this, + "Import title - melonDS", + "An error occured while installing the title to the NAND.\nCheck that your NAND dump is valid."); + } + else + { + // it worked, wee! + createTitleItem(titleid[0], titleid[1]); + ui->lstTitleList->sortItems(); + } +} + +void TitleManagerDialog::on_btnDeleteTitle_clicked() +{ + QListWidgetItem* cur = ui->lstTitleList->currentItem(); + if (!cur) return; + + if (QMessageBox::question(this, + "Delete title - melonDS", + "The title and its associated data will be permanently deleted. Are you sure?", + QMessageBox::StandardButtons(QMessageBox::Yes|QMessageBox::No), + QMessageBox::No) != QMessageBox::Yes) + return; + + u64 titleid = cur->data(Qt::UserRole).toULongLong(); + DSi_NAND::DeleteTitle((u32)(titleid >> 32), (u32)titleid); + + delete cur; +} + +void TitleManagerDialog::on_lstTitleList_currentItemChanged(QListWidgetItem* cur, QListWidgetItem* prev) +{ + if (!cur) + { + ui->btnImportTitleData->setEnabled(false); + ui->btnExportTitleData->setEnabled(false); + ui->btnDeleteTitle->setEnabled(false); + } + else + { + ui->btnImportTitleData->setEnabled(true); + ui->btnExportTitleData->setEnabled(true); + ui->btnDeleteTitle->setEnabled(true); + + u32 val; + val = cur->data(Qt::UserRole+1).toUInt(); + actImportTitleData[0]->setEnabled(val != 0); + actExportTitleData[0]->setEnabled(val != 0); + val = cur->data(Qt::UserRole+2).toUInt(); + actImportTitleData[1]->setEnabled(val != 0); + actExportTitleData[1]->setEnabled(val != 0); + val = cur->data(Qt::UserRole+3).toUInt(); + actImportTitleData[2]->setEnabled(val != 0); + actExportTitleData[2]->setEnabled(val != 0); + } +} + +void TitleManagerDialog::onImportTitleData() +{ + int type = ((QAction*)sender())->data().toInt(); + + QListWidgetItem* cur = ui->lstTitleList->currentItem(); + if (!cur) + { + printf("what??\n"); + return; + } + + u32 wantedsize; + switch (type) + { + case DSi_NAND::TitleData_PublicSav: wantedsize = cur->data(Qt::UserRole+1).toUInt(); break; + case DSi_NAND::TitleData_PrivateSav: wantedsize = cur->data(Qt::UserRole+2).toUInt(); break; + case DSi_NAND::TitleData_BannerSav: wantedsize = cur->data(Qt::UserRole+3).toUInt(); break; + default: + printf("what??\n"); + return; + } + + QString file = QFileDialog::getOpenFileName(this, + "Select file to import...", + EmuDirectory, + "Title data files (*.sav);;Any file (*.*)"); + + if (file.isEmpty()) return; + + FILE* f = fopen(file.toStdString().c_str(), "rb"); + if (!f) + { + QMessageBox::critical(this, + "Import title data - melonDS", + "Could not open data file.\nCheck that the file is accessible."); + return; + } + + fseek(f, 0, SEEK_END); + u64 len = ftell(f); + fclose(f); + + if (len != wantedsize) + { + QMessageBox::critical(this, + "Import title data - melonDS", + QString("Cannot import this data file: size is incorrect (expected: %1 bytes).").arg(wantedsize)); + return; + } + + u64 titleid = cur->data(Qt::UserRole).toULongLong(); + bool res = DSi_NAND::ImportTitleData((u32)(titleid >> 32), (u32)titleid, type, file.toStdString().c_str()); + if (!res) + { + QMessageBox::critical(this, + "Import title data - melonDS", + "Failed to import the data file. Check that your NAND is accessible and valid."); + } +} + +void TitleManagerDialog::onExportTitleData() +{ + int type = ((QAction*)sender())->data().toInt(); + + QListWidgetItem* cur = ui->lstTitleList->currentItem(); + if (!cur) + { + printf("what??\n"); + return; + } + + QString exportname; + u32 wantedsize; + switch (type) + { + case DSi_NAND::TitleData_PublicSav: + exportname = "/public.sav"; + wantedsize = cur->data(Qt::UserRole+1).toUInt(); + break; + case DSi_NAND::TitleData_PrivateSav: + exportname = "/private.sav"; + wantedsize = cur->data(Qt::UserRole+2).toUInt(); + break; + case DSi_NAND::TitleData_BannerSav: + exportname = "/banner.sav"; + wantedsize = cur->data(Qt::UserRole+3).toUInt(); + break; + default: + printf("what??\n"); + return; + } + + QString file = QFileDialog::getSaveFileName(this, + "Select path to export to...", + QString(EmuDirectory) + exportname, + "Title data files (*.sav);;Any file (*.*)"); + + if (file.isEmpty()) return; + + u64 titleid = cur->data(Qt::UserRole).toULongLong(); + bool res = DSi_NAND::ExportTitleData((u32)(titleid >> 32), (u32)titleid, type, file.toStdString().c_str()); + if (!res) + { + QMessageBox::critical(this, + "Export title data - melonDS", + "Failed to Export the data file. Check that the destination directory is writable."); + } +} + + +TitleImportDialog::TitleImportDialog(QWidget* parent, QString& apppath, u8* tmd, bool& readonly) +: QDialog(parent), ui(new Ui::TitleImportDialog), appPath(apppath), tmdData(tmd), readOnly(readonly) +{ + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose); + + grpTmdSource = new QButtonGroup(this); + grpTmdSource->addButton(ui->rbTmdFromFile, 0); + grpTmdSource->addButton(ui->rbTmdFromNUS, 1); + connect(grpTmdSource, SIGNAL(buttonClicked(int)), this, SLOT(onChangeTmdSource(int))); + grpTmdSource->button(0)->setChecked(true); +} + +TitleImportDialog::~TitleImportDialog() +{ + delete ui; +} + +void TitleImportDialog::accept() +{ + QString path; + FILE* f; + + bool tmdfromfile = (grpTmdSource->checkedId() == 0); + + path = ui->txtAppFile->text(); + f = fopen(path.toStdString().c_str(), "rb"); + if (!f) + { + QMessageBox::critical(this, + "Import title - melonDS", + "Could not open executable file.\nCheck that the path is correct and that the file is accessible."); + return; + } + + fseek(f, 0x230, SEEK_SET); + fread(titleid, 8, 1, f); + fclose(f); + + if (titleid[1] != 0x00030004) + { + QMessageBox::critical(this, + "Import title - melonDS", + "Executable file is not a DSiware title."); + return; + } + + if (tmdfromfile) + { + path = ui->txtTmdFile->text(); + f = fopen(path.toStdString().c_str(), "rb"); + if (!f) + { + QMessageBox::critical(this, + "Import title - melonDS", + "Could not open metadata file.\nCheck that the path is correct and that the file is accessible."); + return; + } + + fread(tmdData, 0x208, 1, f); + fclose(f); + + u32 tmdtitleid[2]; + tmdtitleid[0] = (tmdData[0x18C] << 24) | (tmdData[0x18D] << 16) | (tmdData[0x18E] << 8) | tmdData[0x18F]; + tmdtitleid[1] = (tmdData[0x190] << 24) | (tmdData[0x191] << 16) | (tmdData[0x192] << 8) | tmdData[0x193]; + + if (tmdtitleid[1] != titleid[0] || tmdtitleid[0] != titleid[1]) + { + QMessageBox::critical(this, + "Import title - melonDS", + "Title ID in metadata file does not match executable file."); + return; + } + } + + if (DSi_NAND::TitleExists(titleid[1], titleid[0])) + { + if (QMessageBox::question(this, + "Import title - melonDS", + "The selected title is already installed. Overwrite it?", + QMessageBox::StandardButtons(QMessageBox::Yes|QMessageBox::No), + QMessageBox::No) != QMessageBox::Yes) + return; + } + + if (!tmdfromfile) + { + network = new QNetworkAccessManager(this); + + char url[256]; + sprintf(url, "http://nus.cdn.t.shop.nintendowifi.net/ccs/download/%08x%08x/tmd", titleid[1], titleid[0]); + + QNetworkRequest req; + req.setUrl(QUrl(url)); + + netreply = network->get(req); + connect(netreply, &QNetworkReply::finished, this, &TitleImportDialog::tmdDownloaded); + + setEnabled(false); + } + else + { + appPath = ui->txtAppFile->text(); + readOnly = ui->cbReadOnly->isChecked(); + QDialog::accept(); + } +} + +void TitleImportDialog::tmdDownloaded() +{ + bool good = false; + + if (netreply->error() != QNetworkReply::NoError) + { + QMessageBox::critical(this, + "Import title - melonDS", + QString("An error occurred while trying to download the metadata file:\n\n") + netreply->errorString()); + } + else if (netreply->bytesAvailable() < 2312) + { + QMessageBox::critical(this, + "Import title - melonDS", + "NUS returned a malformed metadata file."); + } + else + { + netreply->read((char*)tmdData, 520); + + u32 tmdtitleid[2]; + tmdtitleid[0] = (tmdData[0x18C] << 24) | (tmdData[0x18D] << 16) | (tmdData[0x18E] << 8) | tmdData[0x18F]; + tmdtitleid[1] = (tmdData[0x190] << 24) | (tmdData[0x191] << 16) | (tmdData[0x192] << 8) | tmdData[0x193]; + + if (tmdtitleid[1] != titleid[0] || tmdtitleid[0] != titleid[1]) + { + QMessageBox::critical(this, + "Import title - melonDS", + "NUS returned a malformed metadata file."); + } + else + good = true; + } + + netreply->deleteLater(); + setEnabled(true); + + if (good) + { + appPath = ui->txtAppFile->text(); + readOnly = ui->cbReadOnly->isChecked(); + QDialog::accept(); + } +} + +void TitleImportDialog::on_btnAppBrowse_clicked() +{ + QString file = QFileDialog::getOpenFileName(this, + "Select title executable...", + EmuDirectory, + "DSiware executables (*.app *.nds *.dsi *.srl);;Any file (*.*)"); + + if (file.isEmpty()) return; + + ui->txtAppFile->setText(file); +} + +void TitleImportDialog::on_btnTmdBrowse_clicked() +{ + QString file = QFileDialog::getOpenFileName(this, + "Select title metadata...", + EmuDirectory, + "DSiware metadata (*.tmd);;Any file (*.*)"); + + if (file.isEmpty()) return; + + ui->txtTmdFile->setText(file); +} + +void TitleImportDialog::onChangeTmdSource(int id) +{ + bool pathenable = (id==0); + + ui->txtTmdFile->setEnabled(pathenable); + ui->btnTmdBrowse->setEnabled(pathenable); +} diff --git a/src/frontend/qt_sdl/TitleManagerDialog.h b/src/frontend/qt_sdl/TitleManagerDialog.h new file mode 100644 index 0000000..682362a --- /dev/null +++ b/src/frontend/qt_sdl/TitleManagerDialog.h @@ -0,0 +1,133 @@ +/* + Copyright 2016-2021 Arisotura + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#ifndef TITLEMANAGERDIALOG_H +#define TITLEMANAGERDIALOG_H + +#include <QDialog> +#include <QMessageBox> +#include <QListWidget> +#include <QButtonGroup> + +#include <QUrl> +#include <QNetworkRequest> +#include <QNetworkReply> +#include <QNetworkAccessManager> + +namespace Ui +{ + class TitleManagerDialog; + class TitleImportDialog; +} +class TitleManagerDialog; +class TitleImportDialog; + +class TitleManagerDialog : public QDialog +{ + Q_OBJECT + +public: + explicit TitleManagerDialog(QWidget* parent); + ~TitleManagerDialog(); + + static FILE* curNAND; + static bool openNAND(); + static void closeNAND(); + + static TitleManagerDialog* currentDlg; + static TitleManagerDialog* openDlg(QWidget* parent) + { + if (currentDlg) + { + currentDlg->activateWindow(); + return currentDlg; + } + + if (!openNAND()) + { + QMessageBox::critical(parent, + "DSi title manager - melonDS", + "Failed to mount the DSi NAND. Check that your NAND dump is accessible and valid."); + return nullptr; + } + + currentDlg = new TitleManagerDialog(parent); + currentDlg->open(); + return currentDlg; + } + static void closeDlg() + { + currentDlg = nullptr; + closeNAND(); + } + +private slots: + void done(int r); + + void on_btnImportTitle_clicked(); + void onImportTitleFinished(int res); + void on_btnDeleteTitle_clicked(); + void on_lstTitleList_currentItemChanged(QListWidgetItem* cur, QListWidgetItem* prev); + void onImportTitleData(); + void onExportTitleData(); + +private: + Ui::TitleManagerDialog* ui; + + QString importAppPath; + u8 importTmdData[0x208]; + bool importReadOnly; + + QAction* actImportTitleData[3]; + QAction* actExportTitleData[3]; + + void createTitleItem(u32 category, u32 titleid); +}; + +class TitleImportDialog : public QDialog +{ + Q_OBJECT + +public: + explicit TitleImportDialog(QWidget* parent, QString& apppath, u8* tmd, bool& readonly); + ~TitleImportDialog(); + +private slots: + void accept() override; + void tmdDownloaded(); + + void on_btnAppBrowse_clicked(); + void on_btnTmdBrowse_clicked(); + void onChangeTmdSource(int id); + +private: + Ui::TitleImportDialog* ui; + + QButtonGroup* grpTmdSource; + + QNetworkAccessManager* network; + QNetworkReply* netreply; + + QString& appPath; + u8* tmdData; + bool& readOnly; + + u32 titleid[2]; +}; + +#endif // TITLEMANAGERDIALOG_H diff --git a/src/frontend/qt_sdl/TitleManagerDialog.ui b/src/frontend/qt_sdl/TitleManagerDialog.ui new file mode 100644 index 0000000..8e84681 --- /dev/null +++ b/src/frontend/qt_sdl/TitleManagerDialog.ui @@ -0,0 +1,133 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>TitleManagerDialog</class> + <widget class="QDialog" name="TitleManagerDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>608</width> + <height>466</height> + </rect> + </property> + <property name="windowTitle"> + <string>DSi title manager - melonDS</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <property name="leftMargin"> + <number>6</number> + </property> + <property name="topMargin"> + <number>6</number> + </property> + <property name="rightMargin"> + <number>6</number> + </property> + <property name="bottomMargin"> + <number>6</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QPushButton" name="btnImportTitle"> + <property name="whatsThis"> + <string><html><head/><body><p>Import a DSiware title to your emulated DSi system.</p></body></html></string> + </property> + <property name="text"> + <string>Import title...</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="btnImportTitleData"> + <property name="whatsThis"> + <string><html><head/><body><p>Import data (save, banner...) for the selected title.</p></body></html></string> + </property> + <property name="text"> + <string>Import title data</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="btnExportTitleData"> + <property name="whatsThis"> + <string><html><head/><body><p>Export the data (save, banner...) associated with the selected title.</p></body></html></string> + </property> + <property name="text"> + <string>Export title data</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="btnDeleteTitle"> + <property name="whatsThis"> + <string><html><head/><body><p>Permanently delete the selected title and its associated data from your emulated DSi.</p></body></html></string> + </property> + <property name="text"> + <string>Delete title</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <widget class="QListWidget" name="lstTitleList"/> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Close</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>TitleManagerDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>303</x> + <y>448</y> + </hint> + <hint type="destinationlabel"> + <x>303</x> + <y>232</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>TitleManagerDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>303</x> + <y>448</y> + </hint> + <hint type="destinationlabel"> + <x>303</x> + <y>232</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/frontend/qt_sdl/main.cpp b/src/frontend/qt_sdl/main.cpp index 1146a84..73fcf5a 100644 --- a/src/frontend/qt_sdl/main.cpp +++ b/src/frontend/qt_sdl/main.cpp @@ -58,6 +58,7 @@ #include "WifiSettingsDialog.h" #include "InterfaceSettingsDialog.h" #include "ROMInfoDialog.h" +#include "TitleManagerDialog.h" #include "types.h" #include "version.h" @@ -1377,8 +1378,11 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) connect(actSetupCheats, &QAction::triggered, this, &MainWindow::onSetupCheats); menu->addSeparator(); - actROMInfo = menu->addAction("ROM Info"); + actROMInfo = menu->addAction("ROM info"); connect(actROMInfo, &QAction::triggered, this, &MainWindow::onROMInfo); + + actTitleManager = menu->addAction("Manage DSi titles"); + connect(actTitleManager, &QAction::triggered, this, &MainWindow::onOpenTitleManager); } { QMenu* menu = menubar->addMenu("Config"); @@ -1580,6 +1584,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) actFrameStep->setEnabled(false); actSetupCheats->setEnabled(false); + actTitleManager->setEnabled(strlen(Config::DSiNANDPath) > 0); actEnableCheats->setChecked(Config::EnableCheats != 0); @@ -2391,6 +2396,11 @@ void MainWindow::onROMInfo() ROMInfoDialog* dlg = ROMInfoDialog::openDlg(this); } +void MainWindow::onOpenTitleManager() +{ + TitleManagerDialog* dlg = TitleManagerDialog::openDlg(this); +} + void MainWindow::onOpenEmuSettings() { emuThread->emuPause(); @@ -2405,6 +2415,9 @@ void MainWindow::onEmuSettingsDialogFinished(int res) if (EmuSettingsDialog::needsReset) onReset(); + + if (!RunningSomething) + actTitleManager->setEnabled(strlen(Config::DSiNANDPath) > 0); } void MainWindow::onOpenInputConfig() @@ -2661,6 +2674,7 @@ void MainWindow::onEmuStart() actImportSavefile->setEnabled(true); actSetupCheats->setEnabled(true); + actTitleManager->setEnabled(false); actROMInfo->setEnabled(true); } @@ -2683,6 +2697,7 @@ void MainWindow::onEmuStop() actFrameStep->setEnabled(false); actSetupCheats->setEnabled(false); + actTitleManager->setEnabled(strlen(Config::DSiNANDPath) > 0); actROMInfo->setEnabled(false); } diff --git a/src/frontend/qt_sdl/main.h b/src/frontend/qt_sdl/main.h index 2b93543..1378417 100644 --- a/src/frontend/qt_sdl/main.h +++ b/src/frontend/qt_sdl/main.h @@ -248,6 +248,7 @@ private slots: void onSetupCheats(); void onCheatsDialogFinished(int res); void onROMInfo(); + void onOpenTitleManager(); void onOpenEmuSettings(); void onEmuSettingsDialogFinished(int res); @@ -323,6 +324,7 @@ public: QAction* actEnableCheats; QAction* actSetupCheats; QAction* actROMInfo; + QAction* actTitleManager; QAction* actEmuSettings; QAction* actInputConfig; |