here have a pile of code

This commit is contained in:
Arisotura
2021-08-24 04:24:45 +02:00
parent b31a2d5cbc
commit 11d716394e
7 changed files with 531 additions and 232 deletions

View File

@ -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<std::string> dirlist;
std::vector<std::string> 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<std::string>::iterator it = dirlist.begin(); it != dirlist.end(); it++)
{
const char* path = (*it).c_str();
RemoveDir(path);
}
for (std::vector<std::string>::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<u32>& 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);
}
}

View File

@ -21,6 +21,7 @@
#include "types.h"
#include <vector>
#include <string>
namespace DSi_NAND
{
@ -33,8 +34,10 @@ void GetIDs(u8* emmc_cid, u64& consoleid);
void PatchTSC();
void ListTitles(u32 category, std::vector<u32>& 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);
}

View File

@ -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. */

View File

@ -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)

View File

@ -51,39 +51,12 @@ TitleManagerDialog::TitleManagerDialog(QWidget* parent) : QDialog(parent), ui(ne
for (std::vector<u32>::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);
}

View File

@ -21,8 +21,14 @@
#include <QDialog>
#include <QMessageBox>
#include <QListWidget>
#include <QButtonGroup>
#include <QUrl>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QNetworkAccessManager>
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

View File

@ -31,7 +31,21 @@
<item>
<widget class="QPushButton" name="btnImportTitle">
<property name="text">
<string>Import title</string>
<string>Import title...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnExportTitle">
<property name="text">
<string>Export title...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnDeleteTitle">
<property name="text">
<string>Delete title</string>
</property>
</widget>
</item>