mirror of
https://github.com/melonDS-emu/melonDS.git
synced 2025-07-24 14:49:53 -06:00
having fun with fatfs (#1189)
* patch TSC coords in DSi mode * DSiware importer and shit
This commit is contained in:
@ -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)
|
||||
|
142
src/frontend/qt_sdl/TitleImportDialog.ui
Normal file
142
src/frontend/qt_sdl/TitleImportDialog.ui
Normal file
@ -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>
|
564
src/frontend/qt_sdl/TitleManagerDialog.cpp
Normal file
564
src/frontend/qt_sdl/TitleManagerDialog.cpp
Normal file
@ -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);
|
||||
}
|
133
src/frontend/qt_sdl/TitleManagerDialog.h
Normal file
133
src/frontend/qt_sdl/TitleManagerDialog.h
Normal file
@ -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
|
133
src/frontend/qt_sdl/TitleManagerDialog.ui
Normal file
133
src/frontend/qt_sdl/TitleManagerDialog.ui
Normal file
@ -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>
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
Reference in New Issue
Block a user