mirror of
https://github.com/melonDS-emu/melonDS.git
synced 2025-07-27 00:00:07 -06:00
finish all the features
This commit is contained in:
159
src/DSi_NAND.cpp
159
src/DSi_NAND.cpp
@ -509,22 +509,25 @@ void debug_listfiles(const char* path)
|
||||
f_closedir(&dir);
|
||||
}
|
||||
|
||||
void DumpFile(const char* path, const char* out)
|
||||
bool ImportFile(const char* path, const char* in)
|
||||
{
|
||||
FIL file;
|
||||
FILE* fout;
|
||||
FILE* fin;
|
||||
FRESULT res;
|
||||
|
||||
res = f_open(&file, path, FA_OPEN_EXISTING | FA_READ);
|
||||
if (res != FR_OK) return;
|
||||
fin = fopen(in, "rb");
|
||||
if (!fin)
|
||||
return false;
|
||||
|
||||
u32 len = f_size(&file);
|
||||
fseek(fin, 0, SEEK_END);
|
||||
u32 len = (u32)ftell(fin);
|
||||
fseek(fin, 0, SEEK_SET);
|
||||
|
||||
fout = fopen(out, "wb");
|
||||
if (!fout)
|
||||
res = f_open(&file, path, FA_CREATE_ALWAYS | FA_WRITE);
|
||||
if (res != FR_OK)
|
||||
{
|
||||
f_close(&file);
|
||||
return;
|
||||
fclose(fin);
|
||||
return false;
|
||||
}
|
||||
|
||||
u8 buf[0x200];
|
||||
@ -536,13 +539,54 @@ void DumpFile(const char* path, const char* out)
|
||||
else
|
||||
blocklen = 0x200;
|
||||
|
||||
u32 burp;
|
||||
f_read(&file, buf, blocklen, &burp);
|
||||
u32 nwrite;
|
||||
fread(buf, blocklen, 1, fin);
|
||||
f_write(&file, buf, blocklen, &nwrite);
|
||||
}
|
||||
|
||||
fclose(fin);
|
||||
f_close(&file);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ExportFile(const char* path, const char* out)
|
||||
{
|
||||
FIL file;
|
||||
FILE* fout;
|
||||
FRESULT res;
|
||||
|
||||
res = f_open(&file, path, FA_OPEN_EXISTING | FA_READ);
|
||||
if (res != FR_OK)
|
||||
return false;
|
||||
|
||||
u32 len = f_size(&file);
|
||||
|
||||
fout = fopen(out, "wb");
|
||||
if (!fout)
|
||||
{
|
||||
f_close(&file);
|
||||
return false;
|
||||
}
|
||||
|
||||
u8 buf[0x200];
|
||||
for (u32 i = 0; i < len; i += 0x200)
|
||||
{
|
||||
u32 blocklen;
|
||||
if ((i + 0x200) > len)
|
||||
blocklen = len - i;
|
||||
else
|
||||
blocklen = 0x200;
|
||||
|
||||
u32 nread;
|
||||
f_read(&file, buf, blocklen, &nread);
|
||||
fwrite(buf, blocklen, 1, fout);
|
||||
}
|
||||
|
||||
fclose(fout);
|
||||
f_close(&file);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RemoveFile(const char* path)
|
||||
@ -692,6 +736,9 @@ bool TitleExists(u32 category, u32 titleid)
|
||||
void GetTitleInfo(u32 category, u32 titleid, u32& version, u8* header, u8* banner)
|
||||
{
|
||||
version = GetTitleVersion(category, titleid);
|
||||
if (version == 0xFFFFFFFF)
|
||||
return;
|
||||
|
||||
FRESULT res;
|
||||
|
||||
char path[256];
|
||||
@ -704,6 +751,8 @@ void GetTitleInfo(u32 category, u32 titleid, u32& version, u8* header, u8* banne
|
||||
u32 nread;
|
||||
f_read(&file, header, 0x1000, &nread);
|
||||
|
||||
if (banner)
|
||||
{
|
||||
u32 banneraddr = *(u32*)&header[0x68];
|
||||
if (!banneraddr)
|
||||
{
|
||||
@ -714,6 +763,7 @@ void GetTitleInfo(u32 category, u32 titleid, u32& version, u8* header, u8* banne
|
||||
f_lseek(&file, banneraddr);
|
||||
f_read(&file, banner, 0x2400, &nread);
|
||||
}
|
||||
}
|
||||
|
||||
f_close(&file);
|
||||
}
|
||||
@ -916,29 +966,12 @@ bool ImportTitle(const char* appfile, u8* tmd, bool readonly)
|
||||
// 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)
|
||||
if (!ImportFile(fname, appfile))
|
||||
{
|
||||
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;
|
||||
@ -955,4 +988,72 @@ void DeleteTitle(u32 category, u32 titleid)
|
||||
RemoveDir(fname);
|
||||
}
|
||||
|
||||
u32 GetTitleDataMask(u32 category, u32 titleid)
|
||||
{
|
||||
u32 version;
|
||||
u8 header[0x1000];
|
||||
|
||||
GetTitleInfo(category, titleid, version, header, nullptr);
|
||||
if (version == 0xFFFFFFFF)
|
||||
return 0;
|
||||
|
||||
u32 ret = 0;
|
||||
if (*(u32*)&header[0x238] != 0) ret |= (1 << TitleData_PublicSav);
|
||||
if (*(u32*)&header[0x23C] != 0) ret |= (1 << TitleData_PrivateSav);
|
||||
if (header[0x1BF] & 0x04) ret |= (1 << TitleData_BannerSav);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool ImportTitleData(u32 category, u32 titleid, int type, const char* file)
|
||||
{
|
||||
char fname[128];
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case TitleData_PublicSav:
|
||||
sprintf(fname, "0:/title/%08x/%08x/data/public.sav", category, titleid);
|
||||
break;
|
||||
|
||||
case TitleData_PrivateSav:
|
||||
sprintf(fname, "0:/title/%08x/%08x/data/private.sav", category, titleid);
|
||||
break;
|
||||
|
||||
case TitleData_BannerSav:
|
||||
sprintf(fname, "0:/title/%08x/%08x/data/banner.sav", category, titleid);
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
RemoveFile(fname);
|
||||
return ImportFile(fname, file);
|
||||
}
|
||||
|
||||
bool ExportTitleData(u32 category, u32 titleid, int type, const char* file)
|
||||
{
|
||||
char fname[128];
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case TitleData_PublicSav:
|
||||
sprintf(fname, "0:/title/%08x/%08x/data/public.sav", category, titleid);
|
||||
break;
|
||||
|
||||
case TitleData_PrivateSav:
|
||||
sprintf(fname, "0:/title/%08x/%08x/data/private.sav", category, titleid);
|
||||
break;
|
||||
|
||||
case TitleData_BannerSav:
|
||||
sprintf(fname, "0:/title/%08x/%08x/data/banner.sav", category, titleid);
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return ExportFile(fname, file);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -26,6 +26,13 @@
|
||||
namespace DSi_NAND
|
||||
{
|
||||
|
||||
enum
|
||||
{
|
||||
TitleData_PublicSav,
|
||||
TitleData_PrivateSav,
|
||||
TitleData_BannerSav,
|
||||
};
|
||||
|
||||
bool Init(FILE* nand, u8* es_keyY);
|
||||
void DeInit();
|
||||
|
||||
@ -39,6 +46,10 @@ void GetTitleInfo(u32 category, u32 titleid, u32& version, u8* header, u8* banne
|
||||
bool ImportTitle(const char* appfile, u8* tmd, bool readonly);
|
||||
void DeleteTitle(u32 category, u32 titleid);
|
||||
|
||||
u32 GetTitleDataMask(u32 category, u32 titleid);
|
||||
bool ImportTitleData(u32 category, u32 titleid, int type, const char* file);
|
||||
bool ExportTitleData(u32 category, u32 titleid, int type, const char* file);
|
||||
|
||||
}
|
||||
|
||||
#endif // DSI_NAND_H
|
||||
|
@ -94,6 +94,9 @@
|
||||
</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>
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <QFileDialog>
|
||||
#include <QMenu>
|
||||
|
||||
#include "types.h"
|
||||
#include "Platform.h"
|
||||
@ -59,6 +60,42 @@ TitleManagerDialog::TitleManagerDialog(QWidget* parent) : QDialog(parent), ui(ne
|
||||
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()
|
||||
@ -99,6 +136,9 @@ void TitleManagerDialog::createTitleItem(u32 category, u32 titleid)
|
||||
QListWidgetItem* item = new QListWidgetItem(title + QString(extra));
|
||||
item->setIcon(icon);
|
||||
item->setData(Qt::UserRole, (((u64)category<<32) | (u64)titleid));
|
||||
item->setData(Qt::UserRole+1, *(u32*)&header[0x238]); // public.sav size
|
||||
item->setData(Qt::UserRole+2, *(u32*)&header[0x23C]); // private.sav size
|
||||
item->setData(Qt::UserRole+3, (u32)((header[0x1BF] & 0x04) ? 0x4000 : 0)); // banner.sav size
|
||||
ui->lstTitleList->addItem(item);
|
||||
}
|
||||
|
||||
@ -183,16 +223,6 @@ void TitleManagerDialog::onImportTitleFinished(int res)
|
||||
}
|
||||
}
|
||||
|
||||
void TitleManagerDialog::on_btnImportTitleData_clicked()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
void TitleManagerDialog::on_btnExportTitleData_clicked()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
void TitleManagerDialog::on_btnDeleteTitle_clicked()
|
||||
{
|
||||
QListWidgetItem* cur = ui->lstTitleList->currentItem();
|
||||
@ -205,7 +235,7 @@ void TitleManagerDialog::on_btnDeleteTitle_clicked()
|
||||
QMessageBox::No) != QMessageBox::Yes)
|
||||
return;
|
||||
|
||||
u64 titleid = cur->data(Qt::UserRole).toLongLong();
|
||||
u64 titleid = cur->data(Qt::UserRole).toULongLong();
|
||||
DSi_NAND::DeleteTitle((u32)(titleid >> 32), (u32)titleid);
|
||||
|
||||
delete cur;
|
||||
@ -225,7 +255,125 @@ void TitleManagerDialog::on_lstTitleList_currentItemChanged(QListWidgetItem* cur
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,10 +81,10 @@ private slots:
|
||||
|
||||
void on_btnImportTitle_clicked();
|
||||
void onImportTitleFinished(int res);
|
||||
void on_btnImportTitleData_clicked();
|
||||
void on_btnExportTitleData_clicked();
|
||||
void on_btnDeleteTitle_clicked();
|
||||
void on_lstTitleList_currentItemChanged(QListWidgetItem* cur, QListWidgetItem* prev);
|
||||
void onImportTitleData();
|
||||
void onExportTitleData();
|
||||
|
||||
private:
|
||||
Ui::TitleManagerDialog* ui;
|
||||
@ -93,8 +93,8 @@ private:
|
||||
u8 importTmdData[0x208];
|
||||
bool importReadOnly;
|
||||
|
||||
QAction* importAction[3];
|
||||
QAction* exportAction[3];
|
||||
QAction* actImportTitleData[3];
|
||||
QAction* actExportTitleData[3];
|
||||
|
||||
void createTitleItem(u32 category, u32 titleid);
|
||||
};
|
||||
|
@ -44,7 +44,7 @@
|
||||
<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>
|
||||
<string>Import title data</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -54,7 +54,7 @@
|
||||
<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>
|
||||
<string>Export title data</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
Reference in New Issue
Block a user