mirror of
https://github.com/melonDS-emu/melonDS.git
synced 2025-07-25 15:19:53 -06:00
here have a pile of code
This commit is contained in:
394
src/DSi_NAND.cpp
394
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<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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
@ -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. */
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
Reference in New Issue
Block a user