finish all the features

This commit is contained in:
Arisotura
2021-08-24 16:01:23 +02:00
parent 5e5fb09b0e
commit 33123bfda1
6 changed files with 318 additions and 55 deletions

View File

@ -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,15 +751,18 @@ void GetTitleInfo(u32 category, u32 titleid, u32& version, u8* header, u8* banne
u32 nread;
f_read(&file, header, 0x1000, &nread);
u32 banneraddr = *(u32*)&header[0x68];
if (!banneraddr)
if (banner)
{
memset(banner, 0, 0x2400);
}
else
{
f_lseek(&file, banneraddr);
f_read(&file, banner, 0x2400, &nread);
u32 banneraddr = *(u32*)&header[0x68];
if (!banneraddr)
{
memset(banner, 0, 0x2400);
}
else
{
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);
}
}

View 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

View File

@ -94,6 +94,9 @@
</item>
<item row="6" column="0" colspan="3">
<widget class="QCheckBox" name="cbReadOnly">
<property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Makes the title executable and TMD read-only. Prevents DSi system utilities from deleting them.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Make title files read-only</string>
</property>

View File

@ -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.");
}
}

View File

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

View File

@ -44,7 +44,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Import data (save, banner...) for the selected title.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Import title data...</string>
<string>Import title data</string>
</property>
</widget>
</item>
@ -54,7 +54,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Export the data (save, banner...) associated with the selected title.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Export title data...</string>
<string>Export title data</string>
</property>
</widget>
</item>