From 11d716394ec2eb67a6db9a2e4c947f83a785d7f5 Mon Sep 17 00:00:00 2001 From: Arisotura Date: Tue, 24 Aug 2021 04:24:45 +0200 Subject: [PATCH] here have a pile of code --- src/DSi_NAND.cpp | 394 +++++++++++---------- src/DSi_NAND.h | 5 +- src/fatfs/ffconf.h | 4 +- src/frontend/qt_sdl/CMakeLists.txt | 10 +- src/frontend/qt_sdl/TitleManagerDialog.cpp | 298 ++++++++++++++-- src/frontend/qt_sdl/TitleManagerDialog.h | 36 +- src/frontend/qt_sdl/TitleManagerDialog.ui | 16 +- 7 files changed, 531 insertions(+), 232 deletions(-) diff --git a/src/DSi_NAND.cpp b/src/DSi_NAND.cpp index ca5cb502..58a0d278 100644 --- a/src/DSi_NAND.cpp +++ b/src/DSi_NAND.cpp @@ -481,22 +481,22 @@ void PatchTSC() } -void debug_listfiles(char* path) +void debug_listfiles(const char* path) { DIR dir; FILINFO info; FRESULT res; res = f_opendir(&dir, path); - if (res) return; + if (res != FR_OK) return; for (;;) { res = f_readdir(&dir, &info); - if (res) return; + if (res != FR_OK) return; if (!info.fname[0]) return; - char fullname[1024]; + char fullname[512]; sprintf(fullname, "%s/%s", path, info.fname); printf("[%c] %s\n", (info.fattrib&AM_DIR)?'D':'F', fullname); @@ -505,21 +505,27 @@ void debug_listfiles(char* path) debug_listfiles(fullname); } } + + f_closedir(&dir); } -void debug_dumpfile(char* path, char* out) +void DumpFile(const char* path, const char* out) { FIL file; FILE* fout; FRESULT res; res = f_open(&file, path, FA_OPEN_EXISTING | FA_READ); - if (res) return; + if (res != FR_OK) return; u32 len = f_size(&file); - printf("%s: len=%d\n", path, len); fout = fopen(out, "wb"); + if (!fout) + { + f_close(&file); + return; + } u8 buf[0x200]; for (u32 i = 0; i < len; i += 0x200) @@ -539,6 +545,71 @@ void debug_dumpfile(char* path, char* out) f_close(&file); } +void RemoveFile(const char* path) +{ + FILINFO info; + FRESULT res = f_stat(path, &info); + if (res != FR_OK) return; + + if (info.fattrib & AM_RDO) + f_chmod(path, 0, AM_RDO); + + f_unlink(path); +} + +void RemoveDir(const char* path) +{ + DIR dir; + FILINFO info; + FRESULT res; + + res = f_stat(path, &info); + if (res != FR_OK) return; + + if (info.fattrib & AM_RDO) + f_chmod(path, 0, AM_RDO); + + res = f_opendir(&dir, path); + if (res != FR_OK) return; + + std::vector dirlist; + std::vector filelist; + + for (;;) + { + res = f_readdir(&dir, &info); + if (res != FR_OK) break; + if (!info.fname[0]) break; + + char fullname[512]; + sprintf(fullname, "%s/%s", path, info.fname); + + if (info.fattrib & AM_RDO) + f_chmod(path, 0, AM_RDO); + + if (info.fattrib & AM_DIR) + dirlist.push_back(fullname); + else + filelist.push_back(fullname); + } + + f_closedir(&dir); + + for (std::vector::iterator it = dirlist.begin(); it != dirlist.end(); it++) + { + const char* path = (*it).c_str(); + RemoveDir(path); + } + + for (std::vector::iterator it = filelist.begin(); it != filelist.end(); it++) + { + const char* path = (*it).c_str(); + f_unlink(path); + } + + f_unlink(path); +} + u32 GetTitleVersion(u32 category, u32 titleid) { @@ -609,6 +680,15 @@ void ListTitles(u32 category, std::vector& titlelist) f_closedir(&titledir); } +bool TitleExists(u32 category, u32 titleid) +{ + char path[256]; + sprintf(path, "0:/title/%08x/%08x/content/title.tmd", category, titleid); + + FRESULT res = f_stat(path, nullptr); + return (res == FR_OK); +} + void GetTitleInfo(u32 category, u32 titleid, u32& version, u8* header, u8* banner) { version = GetTitleVersion(category, titleid); @@ -639,17 +719,17 @@ void GetTitleInfo(u32 category, u32 titleid, u32& version, u8* header, u8* banne } -void CreateTicket(char* path, u32 titleid0, u32 titleid1, u8 version) +bool CreateTicket(const char* path, u32 titleid0, u32 titleid1, u8 version) { FIL file; FRESULT res; u32 nwrite; - res = f_open(&file, path, FA_CREATE_NEW | FA_WRITE); + res = f_open(&file, path, FA_CREATE_ALWAYS | FA_WRITE); if (res != FR_OK) { printf("CreateTicket: failed to create file (%d)\n", res); - return; + return false; } u8 ticket[0x2C4]; @@ -670,15 +750,20 @@ void CreateTicket(char* path, u32 titleid0, u32 titleid1, u8 version) f_write(&file, ticket, 0x2C4, &nwrite); f_close(&file); + + return true; } -void CreateSaveFile(char* path, u32 len) +bool CreateSaveFile(const char* path, u32 len) { - if (len < 0x200) return; - if (len > 0x8000000) return; + if (len == 0) return true; + if (len < 0x200) return false; + if (len > 0x8000000) return false; u32 clustersize, maxfiles, totsec16, fatsz16; + // CHECKME! + // code inspired from https://github.com/JeffRuLz/TMFH/blob/master/arm9/src/sav.c if (len < 573440) { clustersize = 512; @@ -706,11 +791,11 @@ void CreateSaveFile(char* path, u32 len) FRESULT res; u32 nwrite; - res = f_open(&file, path, FA_CREATE_NEW | FA_WRITE); + res = f_open(&file, path, FA_CREATE_ALWAYS | FA_WRITE); if (res != FR_OK) { printf("CreateSaveFile: failed to create file (%d)\n", res); - return; + return false; } u8* data = new u8[len]; @@ -738,43 +823,16 @@ void CreateSaveFile(char* path, u32 len) f_close(&file); delete[] data; + + return true; } -void ImportTest() +bool ImportTitle(const char* appfile, u8* tmd, bool readonly) { - char* tmdfile = "treasure.tmd"; - char* appfile = "treasure.nds"; - - FRESULT res; - - /*debug_listfiles("0:"); - - debug_dumpfile("0:/sys/log/sysmenu.log", "sysmenu.log"); - debug_dumpfile("0:/sys/log/product.log", "product.log"); - - f_unmount("0:"); - ff_disk_close(); - return;*/ - - //debug_dumpfile("0:/title/00030004/4b4e4448/content/00000002.app", "00000002.app"); - //debug_dumpfile("0:/title/00030004/4b4e4448/content/title.tmd", "title.tmd"); - //f_unlink("0:/title/00030004/4b475556/data/public.sav"); - //f_unlink("0:/ticket/00030004/4b475556.tik"); - //debug_dumpfile("0:/title/00030004/4b475556/content/title.tmd", "flipnote.tmd"); - //f_unlink("0:/title/00030004/4b475556/content/title.tmd"); - /*f_unmount("0:"); - ff_disk_close(); -return;*/ - u8 tmd[0x208]; - { - FILE* f = fopen(tmdfile, "rb"); - fread(tmd, 0x208, 1, f); - fclose(f); - } - u8 header[0x1000]; { FILE* f = fopen(appfile, "rb"); + if (!f) return false; fread(header, 0x1000, 1, f); fclose(f); } @@ -786,157 +844,115 @@ return;*/ u32 titleid1 = (tmd[0x190] << 24) | (tmd[0x191] << 16) | (tmd[0x192] << 8) | tmd[0x193]; printf("Title ID: %08x/%08x\n", titleid0, titleid1); + FRESULT res; + DIR ticketdir; + FILINFO info; + + char fname[128]; + FIL file; + u32 nwrite; + + // ticket + + sprintf(fname, "0:/ticket/%08x/%08x.tik", titleid0, titleid1); + if (!CreateTicket(fname, *(u32*)&tmd[0x18C], *(u32*)&tmd[0x190], header[0x1E])) + return false; + + if (readonly) f_chmod(fname, AM_RDO, AM_RDO); + + // folder + + sprintf(fname, "0:/title/%08x/%08x", titleid0, titleid1); + f_mkdir(fname); + sprintf(fname, "0:/title/%08x/%08x/content", titleid0, titleid1); + f_mkdir(fname); + sprintf(fname, "0:/title/%08x/%08x/data", titleid0, titleid1); + f_mkdir(fname); + + // data + + sprintf(fname, "0:/title/%08x/%08x/data/public.sav", titleid0, titleid1); + if (!CreateSaveFile(fname, *(u32*)&header[0x238])) + return false; + + sprintf(fname, "0:/title/%08x/%08x/data/private.sav", titleid0, titleid1); + if (!CreateSaveFile(fname, *(u32*)&header[0x23C])) + return false; + + if (header[0x1BF] & 0x04) { - DIR ticketdir; - FILINFO info; - - char fname[128]; - FIL file; - FRESULT res; - u32 nwrite; - - /*res = f_opendir(&ticketdir, "0:/ticket/00030004"); - printf("dir res: %d\n", res); - - res = f_readdir(&ticketdir, &info); - if (res) + // custom banner file + sprintf(fname, "0:/title/%08x/%08x/data/banner.sav", titleid0, titleid1); + res = f_open(&file, fname, FA_CREATE_ALWAYS | FA_WRITE); + if (res != FR_OK) { - printf("bad read res: %d\n", res); + printf("ImportTitle: failed to create banner.sav (%d)\n", res); + return false; } - if (!info.fname[0]) - { - printf("VERY BAD!! DIR IS EMPTY\n"); - // TODO ERROR MANAGEMENT!! - } + u8 bannersav[0x4000]; + memset(bannersav, 0, 0x4000); + f_write(&file, bannersav, 0x4000, &nwrite); - printf("- %s\n", info.fname); - char fname[128]; - sprintf(fname, "0:/ticket/00030004/%s", info.fname); - - f_closedir(&ticketdir); - - FIL file; - res = f_open(&file, fname, FA_OPEN_EXISTING | FA_READ); - // - - u8 ticket[0x2C4]; - u32 nread; - f_read(&file, ticket, 0x2C4, &nread); - // - - f_close(&file); - - ESDecrypt(ticket, 0x2A4); - - // change title ID - *(u32*)&ticket[0x1DC] = *(u32*)&tmd[0x18C]; - *(u32*)&ticket[0x1E0] = *(u32*)&tmd[0x190]; - - printf("ass console ID: %08X\n", *(u32*)&tmd[0x1D8]); - *(u32*)&tmd[0x1D8] = 0; - - ESEncrypt(ticket, 0x2A4);*/ - - // insert shit! - - // ticket - - sprintf(fname, "0:/ticket/%08x/%08x.tik", titleid0, titleid1); - printf("TICKET: %s\n", fname); - /*res = f_open(&file, fname, FA_CREATE_NEW | FA_WRITE); - printf("TICKET: %d\n", res); - - f_write(&file, ticket, 0x2C4, &nread); - // - - f_close(&file);*/ - - CreateTicket(fname, *(u32*)&tmd[0x18C], *(u32*)&tmd[0x190], header[0x1E]); - - printf("----- POST TICKET:\n"); - debug_listfiles("0:"); - - // folder - - sprintf(fname, "0:/title/%08x/%08x", titleid0, titleid1); - res = f_mkdir(fname); - printf("DIR0 RES=%d\n", res); - sprintf(fname, "0:/title/%08x/%08x/content", titleid0, titleid1); - res = f_mkdir(fname); - printf("DIR1 RES=%d\n", res); - sprintf(fname, "0:/title/%08x/%08x/data", titleid0, titleid1); - res = f_mkdir(fname); - printf("DIR2 RES=%d\n", res); - - printf("----- POST DIRS:\n"); - debug_listfiles("0:"); - - // data - - sprintf(fname, "0:/title/%08x/%08x/data/public.sav", titleid0, titleid1); - CreateSaveFile(fname, *(u32*)&header[0x238]); - - sprintf(fname, "0:/title/%08x/%08x/data/private.sav", titleid0, titleid1); - CreateSaveFile(fname, *(u32*)&header[0x23C]); - - if (header[0x1BF] & 0x04) - { - // custom banner file - sprintf(fname, "0:/title/%08x/%08x/data/banner.sav", titleid0, titleid1); - res = f_open(&file, fname, FA_CREATE_NEW | FA_WRITE); - - u8 bannersav[0x4000]; - memset(bannersav, 0, 0x4000); - f_write(&file, bannersav, 0x4000, &nwrite); - - f_close(&file); - } - - printf("----- POST SAVE:\n"); - debug_listfiles("0:"); - - // TMD - - sprintf(fname, "0:/title/%08x/%08x/content/title.tmd", titleid0, titleid1); - printf("TMD: %s\n", fname); - res = f_open(&file, fname, FA_CREATE_NEW | FA_WRITE); - printf("TMD: %d\n", res); - - f_write(&file, tmd, 0x208, &nwrite); - // - - f_close(&file); - - printf("----- POST TMD:\n"); - debug_listfiles("0:"); - - // executable - - sprintf(fname, "0:/title/%08x/%08x/content/%08x.app", titleid0, titleid1, version); - printf("APP: %s\n", fname); - res = f_open(&file, fname, FA_CREATE_NEW | FA_WRITE); - printf("APP: %d\n", res); - - FILE* app = fopen(appfile, "rb"); - fseek(app, 0, SEEK_END); - u32 applen = (u32)ftell(app); - fseek(app, 0, SEEK_SET); - - for (u32 i = 0; i < applen; i += 0x200) - { - u8 data[0x200]; - - u32 lenread = fread(data, 1, 0x200, app); - f_write(&file, data, lenread, &nwrite); - } - - fclose(app); f_close(&file); } - printf("----- POST INSERTION:\n"); - debug_listfiles("0:"); + // TMD + + sprintf(fname, "0:/title/%08x/%08x/content/title.tmd", titleid0, titleid1); + res = f_open(&file, fname, FA_CREATE_ALWAYS | FA_WRITE); + if (res != FR_OK) + { + printf("ImportTitle: failed to create TMD (%d)\n", res); + return false; + } + + f_write(&file, tmd, 0x208, &nwrite); + + f_close(&file); + + if (readonly) f_chmod(fname, AM_RDO, AM_RDO); + + // executable + + sprintf(fname, "0:/title/%08x/%08x/content/%08x.app", titleid0, titleid1, version); + res = f_open(&file, fname, FA_CREATE_ALWAYS | FA_WRITE); + if (res != FR_OK) + { + printf("ImportTitle: failed to create executable (%d)\n", res); + return false; + } + + FILE* app = fopen(appfile, "rb"); + fseek(app, 0, SEEK_END); + u32 applen = (u32)ftell(app); + fseek(app, 0, SEEK_SET); + + for (u32 i = 0; i < applen; i += 0x200) + { + u8 data[0x200]; + + u32 lenread = fread(data, 1, 0x200, app); + f_write(&file, data, lenread, &nwrite); + } + + fclose(app); + f_close(&file); + + if (readonly) f_chmod(fname, AM_RDO, AM_RDO); + + return true; +} + +void DeleteTitle(u32 category, u32 titleid) +{ + char fname[128]; + + sprintf(fname, "0:/ticket/%08x/%08x.tik", category, titleid); + RemoveFile(fname); + + sprintf(fname, "0:/title/%08x/%08x", category, titleid); + RemoveDir(fname); } } diff --git a/src/DSi_NAND.h b/src/DSi_NAND.h index 6f57a72d..41898928 100644 --- a/src/DSi_NAND.h +++ b/src/DSi_NAND.h @@ -21,6 +21,7 @@ #include "types.h" #include +#include namespace DSi_NAND { @@ -33,8 +34,10 @@ void GetIDs(u8* emmc_cid, u64& consoleid); void PatchTSC(); void ListTitles(u32 category, std::vector& titlelist); +bool TitleExists(u32 category, u32 titleid); void GetTitleInfo(u32 category, u32 titleid, u32& version, u8* header, u8* banner); -void ImportTest(); +bool ImportTitle(const char* appfile, u8* tmd, bool readonly); +void DeleteTitle(u32 category, u32 titleid); } diff --git a/src/fatfs/ffconf.h b/src/fatfs/ffconf.h index cb04d229..ead5b7c3 100644 --- a/src/fatfs/ffconf.h +++ b/src/fatfs/ffconf.h @@ -30,7 +30,7 @@ / f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */ -#define FF_USE_MKFS 0 +#define FF_USE_MKFS 1 /* This option switches f_mkfs() function. (0:Disable or 1:Enable) */ @@ -42,7 +42,7 @@ /* This option switches f_expand function. (0:Disable or 1:Enable) */ -#define FF_USE_CHMOD 0 +#define FF_USE_CHMOD 1 /* This option switches attribute manipulation functions, f_chmod() and f_utime(). / (0:Disable or 1:Enable) Also FF_FS_READONLY needs to be 0 to enable this option. */ diff --git a/src/frontend/qt_sdl/CMakeLists.txt b/src/frontend/qt_sdl/CMakeLists.txt index 1c17bad7..3de536c0 100644 --- a/src/frontend/qt_sdl/CMakeLists.txt +++ b/src/frontend/qt_sdl/CMakeLists.txt @@ -47,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) @@ -59,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/TitleManagerDialog.cpp b/src/frontend/qt_sdl/TitleManagerDialog.cpp index fa7750a5..6be080b7 100644 --- a/src/frontend/qt_sdl/TitleManagerDialog.cpp +++ b/src/frontend/qt_sdl/TitleManagerDialog.cpp @@ -51,39 +51,12 @@ TitleManagerDialog::TitleManagerDialog(QWidget* parent) : QDialog(parent), ui(ne for (std::vector::iterator it = titlelist.begin(); it != titlelist.end(); it++) { u32 titleid = *it; - - 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); - ui->lstTitleList->addItem(item); + createTitleItem(category, titleid); } + + ui->lstTitleList->sortItems(); + + ui->btnDeleteTitle->setEnabled(false); } TitleManagerDialog::~TitleManagerDialog() @@ -91,6 +64,42 @@ 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, (((u64)category<<32) | (u64)titleid)); + ui->lstTitleList->addItem(item); +} + bool TitleManagerDialog::openNAND() { FILE* bios7i = Platform::OpenLocalFile(Config::DSiBIOS7Path, "rb"); @@ -136,12 +145,77 @@ void TitleManagerDialog::done(int r) void TitleManagerDialog::on_btnImportTitle_clicked() { - TitleImportDialog* importdlg = new TitleImportDialog(this); + 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]; + + 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_btnExportTitle_clicked() +{ + // +} + +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).toLongLong(); + DSi_NAND::DeleteTitle((u32)(titleid >> 32), (u32)titleid); + + delete cur; +} + +void TitleManagerDialog::on_lstTitleList_currentItemChanged(QListWidgetItem* cur, QListWidgetItem* prev) +{ + if (!cur) + { + ui->btnDeleteTitle->setEnabled(false); + } + else + { + ui->btnDeleteTitle->setEnabled(true); + } } -TitleImportDialog::TitleImportDialog(QWidget* parent) : QDialog(parent), ui(new Ui::TitleImportDialog) +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); @@ -149,6 +223,8 @@ TitleImportDialog::TitleImportDialog(QWidget* parent) : QDialog(parent), ui(new 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() @@ -156,6 +232,152 @@ 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; + + DSi_NAND::DeleteTitle(titleid[1], titleid[0]); + } + + 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); + } +} + +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_TitleImportDialog_accepted() +{ + setEnabled(false); +} + +void TitleImportDialog::on_TitleImportDialog_rejected() +{ + printf("rejected\n"); +} + +void TitleImportDialog::on_TitleImportDialog_reject() +{ + printf("reject\n"); +} + void TitleImportDialog::on_btnAppBrowse_clicked() { QString file = QFileDialog::getOpenFileName(this, @@ -179,3 +401,11 @@ void TitleImportDialog::on_btnTmdBrowse_clicked() 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 index d8863e5a..8cfca1c2 100644 --- a/src/frontend/qt_sdl/TitleManagerDialog.h +++ b/src/frontend/qt_sdl/TitleManagerDialog.h @@ -21,8 +21,14 @@ #include #include +#include #include +#include +#include +#include +#include + namespace Ui { class TitleManagerDialog; @@ -74,9 +80,20 @@ private slots: void done(int r); void on_btnImportTitle_clicked(); + void onImportTitleFinished(int res); + + void on_btnExportTitle_clicked(); + void on_btnDeleteTitle_clicked(); + void on_lstTitleList_currentItemChanged(QListWidgetItem* cur, QListWidgetItem* prev); private: Ui::TitleManagerDialog* ui; + + QString importAppPath; + u8 importTmdData[0x208]; + bool importReadOnly; + + void createTitleItem(u32 category, u32 titleid); }; class TitleImportDialog : public QDialog @@ -84,17 +101,34 @@ class TitleImportDialog : public QDialog Q_OBJECT public: - explicit TitleImportDialog(QWidget* parent); + explicit TitleImportDialog(QWidget* parent, QString& apppath, u8* tmd, bool& readonly); ~TitleImportDialog(); private slots: + void accept() override; + void tmdDownloaded(); + + void on_TitleImportDialog_accepted(); + void on_TitleImportDialog_rejected(); + void on_TitleImportDialog_reject(); + 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 index 7c9f9236..9c80015b 100644 --- a/src/frontend/qt_sdl/TitleManagerDialog.ui +++ b/src/frontend/qt_sdl/TitleManagerDialog.ui @@ -31,7 +31,21 @@ - Import title + Import title... + + + + + + + Export title... + + + + + + + Delete title