forked from Mirror/GodMode9
Properly handle TWL saves when installing
This commit is contained in:
parent
253cb2849c
commit
f7c229b424
@ -193,6 +193,44 @@ FRESULT fvx_qwrite (const TCHAR* path, const void* buff, FSIZE_t ofs, UINT btw,
|
||||
return res;
|
||||
}
|
||||
|
||||
FRESULT fvx_qcreate (const TCHAR* path, UINT btc) {
|
||||
FIL fp;
|
||||
FRESULT res;
|
||||
|
||||
res = fvx_open(&fp, path, FA_WRITE | FA_CREATE_ALWAYS);
|
||||
if (res != FR_OK) return res;
|
||||
|
||||
res = fvx_lseek(&fp, btc);
|
||||
fvx_close(&fp);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/* // untested / unused, might come in handy at a later point
|
||||
FRESULT fvx_qfill (const TCHAR* path, const void* buff, UINT btb) {
|
||||
FIL fp;
|
||||
FRESULT res;
|
||||
UINT bwtt = 0;
|
||||
UINT fsiz = 0;
|
||||
|
||||
res = fvx_open(&fp, path, FA_WRITE | FA_OPEN_EXISTING);
|
||||
if (res != FR_OK) return res;
|
||||
|
||||
fsiz = fvx_size(&fp);
|
||||
while (bwtt < fsiz) {
|
||||
UINT btw = ((fsiz - bwtt) >= btb) ? btb : (fsiz - bwtt);
|
||||
UINT bwt;
|
||||
|
||||
res = fvx_write(&fp, buff, btw, &bwt);
|
||||
if ((res == FR_OK) && (bwt != btw)) res = FR_DENIED;
|
||||
if (res != FR_OK) break;
|
||||
bwtt += bwt;
|
||||
}
|
||||
fvx_close(&fp);
|
||||
|
||||
return res;
|
||||
}*/
|
||||
|
||||
FSIZE_t fvx_qsize (const TCHAR* path) {
|
||||
FILINFO fno;
|
||||
return (fvx_stat(path, &fno) == FR_OK) ? fno.fsize : 0;
|
||||
|
@ -29,9 +29,10 @@ FRESULT fvx_opendir (DIR* dp, const TCHAR* path);
|
||||
FRESULT fvx_closedir (DIR* dp);
|
||||
FRESULT fvx_readdir (DIR* dp, FILINFO* fno);
|
||||
|
||||
// additional quick read / write functions
|
||||
// additional quick read / write / create functions
|
||||
FRESULT fvx_qread (const TCHAR* path, void* buff, FSIZE_t ofs, UINT btr, UINT* br);
|
||||
FRESULT fvx_qwrite (const TCHAR* path, const void* buff, FSIZE_t ofs, UINT btw, UINT* bw);
|
||||
FRESULT fvx_qcreate (const TCHAR* path, UINT btc);
|
||||
|
||||
// additional quick file info functions
|
||||
FSIZE_t fvx_qsize (const TCHAR* path);
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "nds.h"
|
||||
#include "fatmbr.h"
|
||||
#include "vff.h"
|
||||
#include "crc16.h"
|
||||
#include "utf.h"
|
||||
@ -26,6 +27,66 @@ u32 ValidateTwlHeader(TwlHeader* twl) {
|
||||
return (crc16_quick(twl->logo, sizeof(twl->logo)) == NDS_LOGO_CRC16) ? 0 : 1;
|
||||
}
|
||||
|
||||
u32 BuildTwlSaveHeader(void* sav, u32 size) {
|
||||
const u16 sct_size = 0x200;
|
||||
if (size / (u32) sct_size > 0xFFFF)
|
||||
return 1;
|
||||
|
||||
// fit max number of sectors into size
|
||||
// that's how Nintendo does it ¯\_(ツ)_/¯
|
||||
const u16 n_sct_max = size / sct_size;
|
||||
u16 n_sct = 1;
|
||||
u16 sct_track = 1;
|
||||
u16 sct_heads = 1;
|
||||
while (true) {
|
||||
if (sct_heads < sct_track) {
|
||||
u16 n_sct_next = sct_track * (sct_heads+1) * (sct_heads+1);
|
||||
if (n_sct_next < n_sct_max) {
|
||||
sct_heads++;
|
||||
n_sct = n_sct_next;
|
||||
} else break;
|
||||
} else {
|
||||
u16 n_sct_next = (sct_track+1) * sct_heads * sct_heads;
|
||||
if (n_sct_next < n_sct_max) {
|
||||
sct_track++;
|
||||
n_sct = n_sct_next;
|
||||
} else break;
|
||||
}
|
||||
}
|
||||
|
||||
// sectors per cluster (should be identical to Nintendo)
|
||||
u8 clr_size = (n_sct > 8 * 1024) ? 8 : (n_sct > 1024) ? 4 : 1;
|
||||
|
||||
// how many FAT sectors do we need?
|
||||
u16 tot_clr = align(n_sct, clr_size) / clr_size;
|
||||
u32 fat_byte = (align(tot_clr, 2) / 2) * 3; // 2 sectors -> 3 byte
|
||||
u16 fat_size = align(fat_byte, sct_size) / sct_size;
|
||||
|
||||
// build the FAT header
|
||||
Fat16Header* fat = sav;
|
||||
memset(fat, 0x00, sizeof(Fat16Header));
|
||||
fat->jmp[0] = 0xE9; // E9 00 00
|
||||
memcpy(fat->oemname, "MSWIN4.1", 8);
|
||||
fat->sct_size = sct_size; // 512 byte / sector
|
||||
fat->clr_size = clr_size; // sectors per cluster
|
||||
fat->sct_reserved = 0x0001; // 1 reserved sector
|
||||
fat->fat_n = 0x02; // 2 FATs
|
||||
fat->root_n = 0x0020; // 32 root dir entries (2 sectors)
|
||||
fat->reserved0 = n_sct; // sectors in filesystem
|
||||
fat->mediatype = 0xF8; // "hard disk"
|
||||
fat->fat_size = fat_size; // sectors per fat (1 sector)
|
||||
fat->sct_track = sct_track; // sectors per track (legacy? see above)
|
||||
fat->sct_heads = sct_heads; // sectors per head (legacy? see above)
|
||||
fat->ndrive = 0x05; // for whatever reason
|
||||
fat->boot_sig = 0x29; // "boot signature"
|
||||
fat->vol_id = 0x12345678; // volume id
|
||||
memcpy(fat->vol_label, "VOLUMELABEL", 11); // standard volume label
|
||||
memcpy(fat->fs_type, "FAT12 ", 8); // filesystem type
|
||||
fat->magic = 0xAA55;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 LoadTwlMetaData(const char* path, TwlHeader* hdr, TwlIconData* icon) {
|
||||
u8 ALIGN(32) ntr_header[0x200]; // we only need the NTR header (ignore TWL stuff)
|
||||
TwlHeader* twl = hdr ? hdr : (void*) ntr_header;
|
||||
|
@ -22,21 +22,32 @@ u32 BuildTitleInfoEntryTmd(TitleInfoEntry* tie, TitleMetaData* tmd, bool sd) {
|
||||
// align size: 0x4000 for TWL and CTRNAND, 0x8000 for SD
|
||||
u32 align_size = CMD_SIZE_ALIGN(sd);
|
||||
u32 content_count = getbe16(tmd->content_count);
|
||||
TmdContentChunk* chunk = (TmdContentChunk*) (tmd + 1);
|
||||
tie->title_size =
|
||||
(align_size * 3) + // base folder + 'content' + 'cmd'
|
||||
align(TMD_SIZE_N(content_count), align_size) + // TMD
|
||||
align_size; // CMD, placeholder (!!!)
|
||||
if (getle32(tmd->save_size) || getle32(tmd->twl_privsave_size) || (tmd->twl_flag & 0x2)) {
|
||||
tie->title_size +=
|
||||
align_size + // data folder
|
||||
align(getle32(tmd->save_size), align_size) +
|
||||
align(getle32(tmd->twl_privsave_size), align_size) +
|
||||
((tmd->twl_flag & 0x2) ? align(sizeof(TwlIconData), align_size) : 0);
|
||||
}
|
||||
|
||||
// contents title size + some additional stuff
|
||||
TmdContentChunk* chunk = (TmdContentChunk*) (tmd + 1);
|
||||
tie->content0_id = getbe32(chunk->id);
|
||||
for (u32 i = 0; (i < content_count) && (i < TMD_MAX_CONTENTS); i++, chunk++) {
|
||||
if (getbe16(chunk->index) == 1) has_idx1 = true; // will be useful later
|
||||
else if (getbe16(chunk->index) == 2) has_idx2 = true; // will be useful later
|
||||
tie->title_size += align(getbe64(chunk->size), align_size);
|
||||
}
|
||||
|
||||
// manual? dlp? (we need to properly check this later)
|
||||
// manual? dlp? save? (we need to properly check this later)
|
||||
if (((title_id >> 32) == 0x00040000) || ((title_id >> 32) == 0x00040010)) {
|
||||
if (has_idx1) tie->flags_0[0] = 0x1; // this may have a manual
|
||||
if (has_idx2) tie->title_version |= (0xFFFF << 16); // this may have dlp
|
||||
if (has_idx2) tie->title_version |= (0xFFFF << 16); // this may have a dlp
|
||||
if (getle32(tmd->save_size)) tie->flags_1[0] = 0x01; // this may have an sd save
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -62,7 +73,7 @@ u32 BuildTitleInfoEntryTwl(TitleInfoEntry* tie, TitleMetaData* tmd, TwlHeader* t
|
||||
tie->flags_2[0] = 0x01;
|
||||
tie->flags_2[4] = 0x01;
|
||||
tie->flags_2[5] = 0x01;
|
||||
}
|
||||
} else tie->content0_id = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -76,6 +87,9 @@ u32 BuildTitleInfoEntryNcch(TitleInfoEntry* tie, TitleMetaData* tmd, NcchHeader*
|
||||
// product code, extended title version
|
||||
memcpy(tie->product_code, ncch->productcode, 0x10);
|
||||
tie->title_version &= ((ncch->version << 16) | 0xFFFF);
|
||||
|
||||
// NCCH titles need no content0 ID
|
||||
tie->content0_id = 0;
|
||||
|
||||
// specific flags
|
||||
// see: http://3dbrew.org/wiki/Titles
|
||||
@ -84,18 +98,11 @@ u32 BuildTitleInfoEntryNcch(TitleInfoEntry* tie, TitleMetaData* tmd, NcchHeader*
|
||||
|
||||
// stuff from extheader
|
||||
if (exthdr) {
|
||||
// add save data size to title size
|
||||
if (exthdr->savedata_size) {
|
||||
u32 align_size = CMD_SIZE_ALIGN(sd);
|
||||
tie->title_size +=
|
||||
align_size + // 'data' folder
|
||||
align(exthdr->savedata_size, align_size); // savegame
|
||||
tie->flags_1[0] = 0x01; // has SD save
|
||||
};
|
||||
// extdata ID low (hacky, we navigate to storage info)
|
||||
tie->extdata_id_low = getle32(exthdr->aci_data + (0x30 - 0x0C));
|
||||
} else {
|
||||
tie->flags_0[0] = 0x00; // no manual
|
||||
tie->flags_1[0] = 0x00; // no sd save
|
||||
tie->title_version &= 0xFFFF; // no dlp
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,8 @@ typedef struct {
|
||||
u8 reserved1[4];
|
||||
u8 flags_2[8];
|
||||
char product_code[16];
|
||||
u8 reserved2[16];
|
||||
u8 reserved2[12];
|
||||
u32 content0_id; // only relevant for TWL?
|
||||
u8 unknown[4]; // appears to not matter what's here
|
||||
u8 reserved3[44];
|
||||
} __attribute__((packed)) TitleInfoEntry;
|
||||
|
@ -1332,8 +1332,12 @@ u32 GetInstallPath(char* path, const char* drv, u64 tid64, const u8* content_id,
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 GetInstallSavePath(char* path, const char* drv, u64 tid64) {
|
||||
u32 CreateSaveData(const char* drv, u64 tid64, const char* name, u32 save_size, bool overwrite) {
|
||||
bool is_twl = ((*drv == '2') || (*drv == '5'));
|
||||
char path_save[128];
|
||||
|
||||
// generate the save path (thanks ihaveamac for system path)
|
||||
// we use hardcoded names / numbers for CTR saves
|
||||
if ((*drv == '1') || (*drv == '4')) { // ooof, system save
|
||||
// get the id0
|
||||
u8 sd_keyy[16] __attribute__((aligned(4)));
|
||||
@ -1345,13 +1349,48 @@ u32 GetInstallSavePath(char* path, const char* drv, u64 tid64) {
|
||||
sha_quick(sha256sum, sd_keyy, 0x10, SHA256_MODE);
|
||||
// build path
|
||||
u32 tid_low = (u32) (tid64 & 0xFFFFFFFF);
|
||||
snprintf(path, 128, "%2.2s/data/%08lx%08lx%08lx%08lx/sysdata/%08lx/00000000",
|
||||
snprintf(path_save, 128, "%2.2s/data/%08lx%08lx%08lx%08lx/sysdata/%08lx%s",
|
||||
drv, sha256sum[0], sha256sum[1], sha256sum[2], sha256sum[3],
|
||||
tid_low | 0x00020000);
|
||||
tid_low | 0x00020000, name ? "/00000000" : "");
|
||||
return 0;
|
||||
} else { // SD save, simple
|
||||
return GetInstallPath(path, drv, tid64, NULL, "data/00000001.sav");
|
||||
} else if (!is_twl || !name) { // SD CTR save or no name, simple
|
||||
GetInstallPath(path_save, drv, tid64, NULL, name ? "data/00000001.sav" : "data");
|
||||
} else {
|
||||
char substr[64];
|
||||
snprintf(substr, 64, "data/%s", name);
|
||||
GetInstallPath(path_save, drv, tid64, NULL, substr);
|
||||
}
|
||||
|
||||
// if name is NULL, we remove instead of create
|
||||
if (!name) {
|
||||
fvx_runlink(path_save);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// generate the save file, first check if it already exists
|
||||
if (overwrite || (fvx_qsize(path_save) != save_size)) {
|
||||
fvx_rmkpath(path_save);
|
||||
if (fvx_qcreate(path_save, save_size) != FR_OK) return 1;
|
||||
|
||||
if (!is_twl) { // CTR save, simple case
|
||||
static const u8 zeroes[0x20] = { 0x00 };
|
||||
if (fvx_qwrite(path_save, zeroes, 0, 0x20, NULL) != FR_OK)
|
||||
return 1;
|
||||
} else if ((strncmp(name, "public.sav", 11) == 0) || // fat12 image
|
||||
(strncmp(name, "private.sav", 12) == 0)) {
|
||||
u8* fat16k = (u8*) malloc(0x4000); // 16kiB, that's enough
|
||||
if (!fat16k) return 1;
|
||||
memset(fat16k, 0x00, 0x4000);
|
||||
|
||||
if ((BuildTwlSaveHeader(fat16k, save_size) != 0) ||
|
||||
(fvx_qwrite(path_save, fat16k, 0, min(save_size, 0x4000), NULL) != FR_OK)) {
|
||||
free(fat16k);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 UninstallGameData(u64 tid64, bool remove_tie, bool remove_ticket, bool remove_save, bool from_emunand) {
|
||||
@ -1520,6 +1559,7 @@ u32 InstallCiaSystemData(CiaStub* cia, const char* drv) {
|
||||
TmdContentChunk* content_list = cia->content_list;
|
||||
u32 content_count = getbe16(tmd->content_count);
|
||||
u8* title_id = ticket->title_id;
|
||||
u64 tid64 = getbe64(title_id);
|
||||
|
||||
bool sdtie = ((*drv == 'A') || (*drv == 'B'));
|
||||
bool syscmd = (((*drv == '1') || (*drv == '4')) ||
|
||||
@ -1541,7 +1581,7 @@ u32 InstallCiaSystemData(CiaStub* cia, const char* drv) {
|
||||
u8 hdr_cnt0[0x600]; // we don't need more
|
||||
NcchHeader* ncch = NULL;
|
||||
NcchExtHeader* exthdr = NULL;
|
||||
GetInstallPath(path_cnt0, drv, getbe64(title_id), content_list->id, NULL);
|
||||
GetInstallPath(path_cnt0, drv, tid64, content_list->id, NULL);
|
||||
if (fvx_qread(path_cnt0, hdr_cnt0, 0, 0x600, NULL) != FR_OK)
|
||||
return 1;
|
||||
if (ValidateNcchHeader((void*) hdr_cnt0) == 0) {
|
||||
@ -1568,8 +1608,8 @@ u32 InstallCiaSystemData(CiaStub* cia, const char* drv) {
|
||||
snprintf(path_ticketdb, 32, "%2.2s/dbs/ticket.db",
|
||||
((*drv == 'A') || (*drv == '2')) ? "1:" :
|
||||
((*drv == 'B') || (*drv == '5')) ? "4:" : drv);
|
||||
GetInstallPath(path_tmd, drv, getbe64(title_id), NULL, "content/00000000.tmd");
|
||||
GetInstallPath(path_cmd, drv, getbe64(title_id), NULL, "content/cmd/00000001.cmd");
|
||||
GetInstallPath(path_tmd, drv, tid64, NULL, "content/00000000.tmd");
|
||||
GetInstallPath(path_cmd, drv, tid64, NULL, "content/cmd/00000001.cmd");
|
||||
|
||||
// copy tmd & cmd
|
||||
fvx_rmkpath(path_tmd);
|
||||
@ -1582,28 +1622,20 @@ u32 InstallCiaSystemData(CiaStub* cia, const char* drv) {
|
||||
free(cmd); // we don't need this anymore
|
||||
|
||||
// generate savedata
|
||||
if (exthdr && (exthdr->savedata_size)) {
|
||||
char path_save[128];
|
||||
|
||||
// generate the path
|
||||
GetInstallSavePath(path_save, drv, getbe64(title_id));
|
||||
|
||||
// generate the save file, first check if it already exists
|
||||
if (fvx_qsize(path_save) != exthdr->savedata_size) {
|
||||
static const u8 zeroes[0x20] = { 0x00 };
|
||||
UINT bw;
|
||||
FIL save;
|
||||
fvx_rmkpath(path_save);
|
||||
if (fvx_open(&save, path_save, FA_WRITE | FA_CREATE_ALWAYS) != FR_OK)
|
||||
return 1;
|
||||
if ((fvx_write(&save, zeroes, 0x20, &bw) != FR_OK) || (bw != 0x20))
|
||||
bw = 0;
|
||||
fvx_lseek(&save, exthdr->savedata_size);
|
||||
fvx_sync(&save);
|
||||
fvx_close(&save);
|
||||
if (bw != 0x20) return 1;
|
||||
}
|
||||
}
|
||||
u32 save_size = getle32(tmd->save_size);
|
||||
u32 twl_privsave_size = getle32(tmd->twl_privsave_size);
|
||||
if (exthdr && save_size && // NCCH
|
||||
(CreateSaveData(drv, tid64, "*", save_size, false) != 0))
|
||||
return 1;
|
||||
if (!ncch && save_size && // TWL public.sav
|
||||
(CreateSaveData(drv, tid64, "public.sav", save_size, false) != 0))
|
||||
return 1;
|
||||
if (!ncch && twl_privsave_size && // TWL private.sav
|
||||
(CreateSaveData(drv, tid64, "private.sav", twl_privsave_size, false) != 0))
|
||||
return 1;
|
||||
if ((tmd->twl_flag & 0x2) && // TWL banner.sav
|
||||
(CreateSaveData(drv, tid64, "banner.sav", sizeof(TwlIconData), false) != 0))
|
||||
return 1;
|
||||
|
||||
// write ticket and title databases
|
||||
// ensure remounting the old mount path
|
||||
|
Loading…
x
Reference in New Issue
Block a user