mirror of
https://github.com/d0k3/GodMode9.git
synced 2026-07-15 12:22:06 +00:00
Implement savedata/extdata mounting & missing .db files creation (#964)
This commit is contained in:
parent
4058c9c5f3
commit
29023d7425
@ -49,7 +49,7 @@ int DriveType(const char* path) {
|
|||||||
type = DRV_VIRTUAL | DRV_SYSNAND;
|
type = DRV_VIRTUAL | DRV_SYSNAND;
|
||||||
} else if (vsrc == VRT_EMUNAND) {
|
} else if (vsrc == VRT_EMUNAND) {
|
||||||
type = DRV_VIRTUAL | DRV_EMUNAND;
|
type = DRV_VIRTUAL | DRV_EMUNAND;
|
||||||
} else if ((vsrc == VRT_IMGNAND) || (vsrc == VRT_DISADIFF) || (vsrc == VRT_BDRI)) {
|
} else if ((vsrc == VRT_IMGNAND) || (vsrc == VRT_DISADIFF) || (vsrc == VRT_BDRI) || (vsrc == VRT_SAVE)) {
|
||||||
type = DRV_VIRTUAL | DRV_IMAGE;
|
type = DRV_VIRTUAL | DRV_IMAGE;
|
||||||
} else if (vsrc == VRT_XORPAD) {
|
} else if (vsrc == VRT_XORPAD) {
|
||||||
type = DRV_VIRTUAL | DRV_XORPAD;
|
type = DRV_VIRTUAL | DRV_XORPAD;
|
||||||
@ -123,6 +123,10 @@ bool GetRootDirContentsWorker(DirStruct* contents) {
|
|||||||
snprintf(entry->name, 252, "[%s] %s (%s)", drvnum[i], drvname[i], carttype);
|
snprintf(entry->name, 252, "[%s] %s (%s)", drvnum[i], drvname[i], carttype);
|
||||||
else if (*(drvnum[i]) == '0') // SD card handling
|
else if (*(drvnum[i]) == '0') // SD card handling
|
||||||
snprintf(entry->name, 252, "[%s] %s (%s)", drvnum[i], drvname[i], sdlabel);
|
snprintf(entry->name, 252, "[%s] %s (%s)", drvnum[i], drvname[i], sdlabel);
|
||||||
|
else if (*(drvnum[i]) == 'F') // Save/Extdata handling
|
||||||
|
snprintf(entry->name, 252, "[%s] %s", drvnum[i],
|
||||||
|
(GetMountState() & SYS_DIFF) ? STR_LAB_EXTDATA_IMAGE :
|
||||||
|
(GetMountState() & SYS_DISA) ? STR_LAB_SAVE_FILE_IMAGE : "UNK");
|
||||||
else snprintf(entry->name, 252, "[%s] %s", drvnum[i], drvname[i]);
|
else snprintf(entry->name, 252, "[%s] %s", drvnum[i], drvname[i]);
|
||||||
entry->size = GetTotalSpace(entry->path);
|
entry->size = GetTotalSpace(entry->path);
|
||||||
entry->type = T_ROOT;
|
entry->type = T_ROOT;
|
||||||
|
|||||||
@ -37,7 +37,7 @@
|
|||||||
STR_LAB_EMUNAND_CTRNAND, STR_LAB_EMUNAND_TWLN, STR_LAB_EMUNAND_TWLP, STR_LAB_EMUNAND_SD, STR_LAB_EMUNAND_VIRTUAL, \
|
STR_LAB_EMUNAND_CTRNAND, STR_LAB_EMUNAND_TWLN, STR_LAB_EMUNAND_TWLP, STR_LAB_EMUNAND_SD, STR_LAB_EMUNAND_VIRTUAL, \
|
||||||
STR_LAB_IMGNAND_CTRNAND, STR_LAB_IMGNAND_TWLN, STR_LAB_IMGNAND_TWLP, STR_LAB_IMGNAND_VIRTUAL, \
|
STR_LAB_IMGNAND_CTRNAND, STR_LAB_IMGNAND_TWLN, STR_LAB_IMGNAND_TWLP, STR_LAB_IMGNAND_VIRTUAL, \
|
||||||
STR_LAB_GAMECART, \
|
STR_LAB_GAMECART, \
|
||||||
STR_LAB_GAME_IMAGE, STR_LAB_AESKEYDB_IMAGE, STR_LAB_BDRI_IMAGE, STR_LAB_DISA_DIFF_IMAGE, \
|
STR_LAB_GAME_IMAGE, STR_LAB_AESKEYDB_IMAGE, STR_LAB_BDRI_IMAGE, STR_LAB_DISA_DIFF_IMAGE, STR_LAB_SAVE_FILE_IMAGE, \
|
||||||
STR_LAB_MEMORY_VIRTUAL, \
|
STR_LAB_MEMORY_VIRTUAL, \
|
||||||
STR_LAB_VRAM_VIRTUAL, \
|
STR_LAB_VRAM_VIRTUAL, \
|
||||||
STR_LAB_TITLE_MANAGER, \
|
STR_LAB_TITLE_MANAGER, \
|
||||||
@ -45,7 +45,7 @@
|
|||||||
|
|
||||||
#define FS_DRVNUM \
|
#define FS_DRVNUM \
|
||||||
"0:", "1:", "2:", "3:", "A:", "S:", "4:", "5:", "6:", "B:", "E:", "7:", "8:", "9:", \
|
"0:", "1:", "2:", "3:", "A:", "S:", "4:", "5:", "6:", "B:", "E:", "7:", "8:", "9:", \
|
||||||
"I:", "C:", "G:", "K:", "T:", "D:", "M:", "V:", "Y:", "Z:"
|
"I:", "C:", "G:", "K:", "T:", "D:", "F:", "M:", "V:", "Y:", "Z:"
|
||||||
|
|
||||||
/** Function to identify the type of a drive **/
|
/** Function to identify the type of a drive **/
|
||||||
int DriveType(const char* path);
|
int DriveType(const char* path);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -16,3 +16,6 @@ u32 RemoveTitleInfoEntryFromDB(const char* path, const u8* title_id);
|
|||||||
u32 RemoveTicketFromDB(const char* path, const u8* title_id);
|
u32 RemoveTicketFromDB(const char* path, const u8* title_id);
|
||||||
u32 AddTitleInfoEntryToDB(const char* path, const u8* title_id, const TitleInfoEntry* tie, bool replace);
|
u32 AddTitleInfoEntryToDB(const char* path, const u8* title_id, const TitleInfoEntry* tie, bool replace);
|
||||||
u32 AddTicketToDB(const char* path, const u8* title_id, const Ticket* ticket, bool replace);
|
u32 AddTicketToDB(const char* path, const u8* title_id, const Ticket* ticket, bool replace);
|
||||||
|
|
||||||
|
u32 CreateBDRI(const char *path, u64 image_offset, u64 image_size, u32 blocksize, u32 num_files);
|
||||||
|
u32 CreateDbFilesForDrive(const char *destdrv, bool silent, bool force_overwrite);
|
||||||
@ -4,6 +4,8 @@
|
|||||||
#include "sha.h"
|
#include "sha.h"
|
||||||
|
|
||||||
#define GET_DPFS_BIT(b, lvl) (((((u32*) (void*) lvl)[b >> 5]) >> (31 - (b % 32))) & 1)
|
#define GET_DPFS_BIT(b, lvl) (((((u32*) (void*) lvl)[b >> 5]) >> (31 - (b % 32))) & 1)
|
||||||
|
#define LVL(h,n) ((h)->level[(n) - 1])
|
||||||
|
#define L(n) ((n) - 1)
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
u8 magic[8]; // "DISA" 0x00040000
|
u8 magic[8]; // "DISA" 0x00040000
|
||||||
@ -54,40 +56,27 @@ typedef struct {
|
|||||||
} PACKED_STRUCT DifiHeader;
|
} PACKED_STRUCT DifiHeader;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
u8 magic[8]; // "IVFC" 0x00020000
|
u64 offset;
|
||||||
u64 size_hash; // same as the one in DIFI, may include padding
|
u64 size;
|
||||||
u64 offset_lvl1;
|
u32 blocksize_log2;
|
||||||
u64 size_lvl1;
|
|
||||||
u32 log_lvl1;
|
|
||||||
u8 padding0[4];
|
u8 padding0[4];
|
||||||
u64 offset_lvl2;
|
} PACKED_STRUCT IvfcLevel;
|
||||||
u64 size_lvl2;
|
|
||||||
u32 log_lvl2;
|
|
||||||
u8 padding1[4];
|
|
||||||
u64 offset_lvl3;
|
|
||||||
u64 size_lvl3;
|
|
||||||
u32 log_lvl3;
|
|
||||||
u8 padding2[4];
|
|
||||||
u64 offset_lvl4;
|
|
||||||
u64 size_lvl4;
|
|
||||||
u64 log_lvl4;
|
|
||||||
u64 size_ivfc; // 0x78
|
|
||||||
} PACKED_STRUCT IvfcDescriptor;
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
u8 magic[8]; // "DPFS" 0x00010000
|
u8 magic[8]; // "IVFC" 0x00020000
|
||||||
u64 offset_lvl1;
|
u32 size_hash;// same as the one in DIFI
|
||||||
u64 size_lvl1;
|
u8 padding0[4];
|
||||||
u32 log_lvl1;
|
IvfcLevel level[4];
|
||||||
u8 padding0[4];
|
u32 size_ivfc;
|
||||||
u64 offset_lvl2;
|
u32 offset_tree;
|
||||||
u64 size_lvl2;
|
} PACKED_STRUCT IvfcDescriptor;
|
||||||
u32 log_lvl2;
|
|
||||||
u8 padding1[4];
|
// same layout
|
||||||
u64 offset_lvl3;
|
typedef IvfcLevel DpfsLevel;
|
||||||
u64 size_lvl3;
|
|
||||||
u32 log_lvl3;
|
typedef struct {
|
||||||
u8 padding2[4];
|
u8 magic[8]; // "DPFS" 0x00010000
|
||||||
|
DpfsLevel level[3];
|
||||||
} PACKED_STRUCT DpfsDescriptor;
|
} PACKED_STRUCT DpfsDescriptor;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@ -98,6 +87,52 @@ typedef struct {
|
|||||||
u8 padding[4]; // all zeroes when encrypted
|
u8 padding[4]; // all zeroes when encrypted
|
||||||
} PACKED_STRUCT DifiStruct;
|
} PACKED_STRUCT DifiStruct;
|
||||||
|
|
||||||
|
STATIC_ASSERT(sizeof(DifiHeader) == 0x44);
|
||||||
|
STATIC_ASSERT(sizeof(IvfcDescriptor) == 0x78);
|
||||||
|
STATIC_ASSERT(sizeof(DpfsDescriptor) == 0x50);
|
||||||
|
STATIC_ASSERT(sizeof(DiffHeader) == 0x100);
|
||||||
|
|
||||||
|
static const u8 disa_magic[] = { DISA_MAGIC };
|
||||||
|
static const u8 diff_magic[] = { DIFF_MAGIC };
|
||||||
|
static const u8 ivfc_magic[] = { IVFC_MAGIC };
|
||||||
|
static const u8 dpfs_magic[] = { DPFS_MAGIC };
|
||||||
|
static const u8 difi_magic[] = { DIFI_MAGIC };
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u32 ivfc_blocksizes[4];
|
||||||
|
u32 dpfs_lv2_blocksize;
|
||||||
|
u32 dpfs_lv3_blocksize;
|
||||||
|
} IvfcDpfsConfig;
|
||||||
|
|
||||||
|
static const IvfcDpfsConfig SaveExsvIvfcDpfsConfig = {
|
||||||
|
.ivfc_blocksizes = { 0x200, 0x200, 0x1000, 0x1000 },
|
||||||
|
.dpfs_lv2_blocksize = 0x80,
|
||||||
|
.dpfs_lv3_blocksize = 0x1000,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const IvfcDpfsConfig DbIvfcDpfsConfig = {
|
||||||
|
.ivfc_blocksizes = { 0x200, 0x200, 0x200, 0x200 },
|
||||||
|
.dpfs_lv2_blocksize = 0x80,
|
||||||
|
.dpfs_lv3_blocksize = 0x200,
|
||||||
|
};
|
||||||
|
|
||||||
|
const IvfcDpfsConfig *GetIvfcDpfsConfig(bool db) {
|
||||||
|
return db ? &DbIvfcDpfsConfig : &SaveExsvIvfcDpfsConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 CalcIvfcDpfsConfigBlockSize(const IvfcDpfsConfig *config) {
|
||||||
|
// calculates the largest block size across both IVFC and DPFS
|
||||||
|
u32 ret = 0;
|
||||||
|
|
||||||
|
for (int i = 1; i < 4 + 1; i++)
|
||||||
|
ret |= (config->ivfc_blocksizes[L(i)] - 1);
|
||||||
|
|
||||||
|
ret |= config->dpfs_lv2_blocksize - 1;
|
||||||
|
ret |= config->dpfs_lv3_blocksize - 1;
|
||||||
|
|
||||||
|
return ret + 1;
|
||||||
|
}
|
||||||
|
|
||||||
static FIL ddfile;
|
static FIL ddfile;
|
||||||
static FIL* ddfp = NULL;
|
static FIL* ddfp = NULL;
|
||||||
|
|
||||||
@ -159,12 +194,6 @@ inline static FRESULT DisaDiffQWrite(const TCHAR* path, const void* buf, UINT of
|
|||||||
}
|
}
|
||||||
|
|
||||||
u32 GetDisaDiffRWInfo(const char* path, DisaDiffRWInfo* info, bool partitionB) {
|
u32 GetDisaDiffRWInfo(const char* path, DisaDiffRWInfo* info, bool partitionB) {
|
||||||
static const u8 disa_magic[] = { DISA_MAGIC };
|
|
||||||
static const u8 diff_magic[] = { DIFF_MAGIC };
|
|
||||||
static const u8 ivfc_magic[] = { IVFC_MAGIC };
|
|
||||||
static const u8 dpfs_magic[] = { DPFS_MAGIC };
|
|
||||||
static const u8 difi_magic[] = { DIFI_MAGIC };
|
|
||||||
|
|
||||||
// reset reader info
|
// reset reader info
|
||||||
memset(info, 0x00, sizeof(DisaDiffRWInfo));
|
memset(info, 0x00, sizeof(DisaDiffRWInfo));
|
||||||
|
|
||||||
@ -200,6 +229,7 @@ u32 GetDisaDiffRWInfo(const char* path, DisaDiffRWInfo* info, bool partitionB) {
|
|||||||
DiffHeader* diff = (DiffHeader*) (void*) header;
|
DiffHeader* diff = (DiffHeader*) (void*) header;
|
||||||
offset_partition = (u32) diff->offset_partition;
|
offset_partition = (u32) diff->offset_partition;
|
||||||
size_partition = (u32) diff->size_partition;
|
size_partition = (u32) diff->size_partition;
|
||||||
|
info->unique_id = diff->unique_id;
|
||||||
info->offset_table = (diff->active_table) ? diff->offset_table1 : diff->offset_table0;
|
info->offset_table = (diff->active_table) ? diff->offset_table1 : diff->offset_table0;
|
||||||
info->size_table = diff->size_table;
|
info->size_table = diff->size_table;
|
||||||
offset_difi = info->offset_table;
|
offset_difi = info->offset_table;
|
||||||
@ -240,70 +270,82 @@ u32 GetDisaDiffRWInfo(const char* path, DisaDiffRWInfo* info, bool partitionB) {
|
|||||||
|
|
||||||
// check & get data from DPFS descriptor
|
// check & get data from DPFS descriptor
|
||||||
const DpfsDescriptor* dpfs = &(difis.dpfs);
|
const DpfsDescriptor* dpfs = &(difis.dpfs);
|
||||||
if ((dpfs->offset_lvl1 + dpfs->size_lvl1 > dpfs->offset_lvl2) ||
|
if ((LVL(dpfs, 1).offset + LVL(dpfs, 1).size > LVL(dpfs, 2).offset) ||
|
||||||
(dpfs->offset_lvl2 + dpfs->size_lvl2 > dpfs->offset_lvl3) ||
|
(LVL(dpfs, 2).offset + LVL(dpfs, 2).size > LVL(dpfs, 3).offset) ||
|
||||||
(dpfs->offset_lvl3 + dpfs->size_lvl3 > size_partition) ||
|
(LVL(dpfs, 3).offset + LVL(dpfs, 3).size > size_partition) ||
|
||||||
(2 > dpfs->log_lvl2) || (dpfs->log_lvl2 > dpfs->log_lvl3) ||
|
(2 > LVL(dpfs, 2).blocksize_log2) || (LVL(dpfs, 2).blocksize_log2 > LVL(dpfs, 3).blocksize_log2) ||
|
||||||
!dpfs->size_lvl1 || !dpfs->size_lvl2 || !dpfs->size_lvl3)
|
!LVL(dpfs, 1).size || !LVL(dpfs, 2).size || !LVL(dpfs, 3).size)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
info->offset_dpfs_lvl1 = (u32) (offset_partition + dpfs->offset_lvl1);
|
info->offset_dpfs_lvl1 = (u32) (offset_partition + LVL(dpfs, 1).offset);
|
||||||
info->offset_dpfs_lvl2 = (u32) (offset_partition + dpfs->offset_lvl2);
|
info->offset_dpfs_lvl2 = (u32) (offset_partition + LVL(dpfs, 2).offset);
|
||||||
info->offset_dpfs_lvl3 = (u32) (offset_partition + dpfs->offset_lvl3);
|
info->offset_dpfs_lvl3 = (u32) (offset_partition + LVL(dpfs, 3).offset);
|
||||||
info->size_dpfs_lvl1 = (u32) dpfs->size_lvl1;
|
info->size_dpfs_lvl1 = (u32) LVL(dpfs, 1).size;
|
||||||
info->size_dpfs_lvl2 = (u32) dpfs->size_lvl2;
|
info->size_dpfs_lvl2 = (u32) LVL(dpfs, 2).size;
|
||||||
info->size_dpfs_lvl3 = (u32) dpfs->size_lvl3;
|
info->size_dpfs_lvl3 = (u32) LVL(dpfs, 3).size;
|
||||||
info->log_dpfs_lvl2 = (u32) dpfs->log_lvl2;
|
info->log_dpfs_lvl2 = (u32) LVL(dpfs, 2).blocksize_log2;
|
||||||
info->log_dpfs_lvl3 = (u32) dpfs->log_lvl3;
|
info->log_dpfs_lvl3 = (u32) LVL(dpfs, 3).blocksize_log2;
|
||||||
|
|
||||||
// check & get data from IVFC descriptor
|
// check & get data from IVFC descriptor
|
||||||
const IvfcDescriptor* ivfc = &(difis.ivfc);
|
const IvfcDescriptor* ivfc = &(difis.ivfc);
|
||||||
if ((ivfc->size_hash != difi->size_hash) ||
|
if ((ivfc->size_hash != difi->size_hash) ||
|
||||||
(ivfc->size_ivfc != sizeof(IvfcDescriptor)) ||
|
(ivfc->size_ivfc != sizeof(IvfcDescriptor)) ||
|
||||||
(ivfc->offset_lvl1 + ivfc->size_lvl1 > ivfc->offset_lvl2) ||
|
(ivfc->offset_tree != 0) ||
|
||||||
(ivfc->offset_lvl2 + ivfc->size_lvl2 > ivfc->offset_lvl3) ||
|
(LVL(ivfc, 1).offset + LVL(ivfc, 1).size > LVL(ivfc, 2).offset) ||
|
||||||
(ivfc->offset_lvl3 + ivfc->size_lvl3 > dpfs->size_lvl3))
|
(LVL(ivfc, 2).offset + LVL(ivfc, 2).size > LVL(ivfc, 3).offset) ||
|
||||||
|
(LVL(ivfc, 3).offset + LVL(ivfc, 3).size > LVL(dpfs, 3).size))
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
if (!info->ivfc_use_extlvl4) {
|
if (!info->ivfc_use_extlvl4) {
|
||||||
if ((ivfc->offset_lvl3 + ivfc->size_lvl3 > ivfc->offset_lvl4) ||
|
if ((LVL(ivfc, 3).offset + LVL(ivfc, 3).size > LVL(ivfc, 4).offset) ||
|
||||||
(ivfc->offset_lvl4 + ivfc->size_lvl4 > dpfs->size_lvl3))
|
(LVL(ivfc, 4).offset + LVL(ivfc, 4).size > LVL(dpfs, 3).size))
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
info->offset_ivfc_lvl4 = (u32) ivfc->offset_lvl4;
|
info->offset_ivfc_lvl4 = (u32) LVL(ivfc, 4).offset;
|
||||||
} else if (info->offset_ivfc_lvl4 + ivfc->size_lvl4 > offset_partition + size_partition)
|
} else if (info->offset_ivfc_lvl4 + LVL(ivfc, 4).size > offset_partition + size_partition)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
info->log_ivfc_lvl1 = (u32) ivfc->log_lvl1;
|
info->log_ivfc_lvl1 = (u32) LVL(ivfc, 1).blocksize_log2;
|
||||||
info->log_ivfc_lvl2 = (u32) ivfc->log_lvl2;
|
info->log_ivfc_lvl2 = (u32) LVL(ivfc, 2).blocksize_log2;
|
||||||
info->log_ivfc_lvl3 = (u32) ivfc->log_lvl3;
|
info->log_ivfc_lvl3 = (u32) LVL(ivfc, 3).blocksize_log2;
|
||||||
info->log_ivfc_lvl4 = (u32) ivfc->log_lvl4;
|
info->log_ivfc_lvl4 = (u32) LVL(ivfc, 4).blocksize_log2;
|
||||||
info->offset_ivfc_lvl1 = (u32) ivfc->offset_lvl1;
|
info->offset_ivfc_lvl1 = (u32) LVL(ivfc, 1).offset;
|
||||||
info->offset_ivfc_lvl2 = (u32) ivfc->offset_lvl2;
|
info->offset_ivfc_lvl2 = (u32) LVL(ivfc, 2).offset;
|
||||||
info->offset_ivfc_lvl3 = (u32) ivfc->offset_lvl3;
|
info->offset_ivfc_lvl3 = (u32) LVL(ivfc, 3).offset;
|
||||||
info->size_ivfc_lvl1 = (u32) ivfc->size_lvl1;
|
info->size_ivfc_lvl1 = (u32) LVL(ivfc, 1).size;
|
||||||
info->size_ivfc_lvl2 = (u32) ivfc->size_lvl2;
|
info->size_ivfc_lvl2 = (u32) LVL(ivfc, 2).size;
|
||||||
info->size_ivfc_lvl3 = (u32) ivfc->size_lvl3;
|
info->size_ivfc_lvl3 = (u32) LVL(ivfc, 3).size;
|
||||||
info->size_ivfc_lvl4 = (u32) ivfc->size_lvl4;
|
info->size_ivfc_lvl4 = (u32) LVL(ivfc, 4).size;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 BuildDisaDiffDpfsLvl2Cache(const char* path, const DisaDiffRWInfo* info, u8* cache, u32 cache_size) {
|
u32 BuildDisaDiffDpfsLvl2Cache(const char* path, const DisaDiffRWInfo* info, u8* cache, u32 cache_size) {
|
||||||
const u32 min_cache_bits = (info->size_dpfs_lvl3 + (1 << info->log_dpfs_lvl3) - 1) >> info->log_dpfs_lvl3;
|
const u32 blocksize_lvl2 = 1u << info->log_dpfs_lvl2;
|
||||||
const u32 min_cache_size = ((min_cache_bits + 31) >> (3 + 2)) << 2;
|
const u32 blocksize_lvl3 = 1u << info->log_dpfs_lvl3;
|
||||||
|
|
||||||
|
// each lvl3 block maps to exactly one bit in lvl2
|
||||||
|
const u32 lv2_min_num_bits = ceil_div(info->size_dpfs_lvl3, blocksize_lvl3);
|
||||||
|
// the number of bits in lvl2 are rounded up to a byte-boundary, 8 bits
|
||||||
|
const u32 lv2_min_num_bytes = ceil_div(lv2_min_num_bits, 8);
|
||||||
|
// and the number of bytes lvl2 consists of is rounded up to the lvl2 blocksize
|
||||||
|
// so that each lvl2 block maps to exactly one bit of lvl1, respectively
|
||||||
|
const u32 lv2_min_num_blocks = ceil_div(lv2_min_num_bytes, blocksize_lvl2);
|
||||||
|
// thus, the minimum size of lvl2 is the lvl2-blockaligned number of lvl2 bytes (which itself are the 8-bit aligned number of lvl3 blocks)
|
||||||
|
const u32 lv2_min_size = lv2_min_num_blocks * blocksize_lvl2;
|
||||||
|
|
||||||
const u32 offset_lvl1 = info->offset_dpfs_lvl1 + ((info->dpfs_lvl1_selector) ? info->size_dpfs_lvl1 : 0);
|
const u32 offset_lvl1 = info->offset_dpfs_lvl1 + ((info->dpfs_lvl1_selector) ? info->size_dpfs_lvl1 : 0);
|
||||||
|
|
||||||
// safety (this still assumes all the checks from GetDisaDiffRWInfo())
|
// safety (this still assumes all the checks from GetDisaDiffRWInfo())
|
||||||
if ((cache_size < min_cache_size) ||
|
if ((cache_size < lv2_min_size) ||
|
||||||
(min_cache_size > info->size_dpfs_lvl2) ||
|
(lv2_min_size > info->size_dpfs_lvl2) ||
|
||||||
(min_cache_size > (info->size_dpfs_lvl1 << (3 + info->log_dpfs_lvl2)))) {
|
(lv2_min_size > (info->size_dpfs_lvl1 << (3 + info->log_dpfs_lvl2)))) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// allocate memory
|
// allocate memory
|
||||||
u8* lvl1 = (u8*) malloc(info->size_dpfs_lvl1);
|
u8* lvl1 = (u8*) malloc(info->size_dpfs_lvl1);
|
||||||
if (!lvl1) return 1; // this is never more than 8 byte in reality -___-
|
if (!lvl1) return 1;
|
||||||
|
|
||||||
// open file pointer
|
// open file pointer
|
||||||
if (DisaDiffOpen(path) != FR_OK) {
|
if (DisaDiffOpen(path) != FR_OK) {
|
||||||
@ -315,19 +357,27 @@ u32 BuildDisaDiffDpfsLvl2Cache(const char* path, const DisaDiffRWInfo* info, u8*
|
|||||||
u32 ret = 0;
|
u32 ret = 0;
|
||||||
if ((ret != 0) || DisaDiffRead(lvl1, info->size_dpfs_lvl1, offset_lvl1)) ret = 1;
|
if ((ret != 0) || DisaDiffRead(lvl1, info->size_dpfs_lvl1, offset_lvl1)) ret = 1;
|
||||||
|
|
||||||
// read full lvl2_0 to cache
|
// read full lvl2_0 to cache. this is the baseline, and we'll replace blocks that are actually in lv2_1 later
|
||||||
if ((ret != 0) || DisaDiffRead(cache, info->size_dpfs_lvl2, info->offset_dpfs_lvl2)) ret = 1;
|
if ((ret != 0) || DisaDiffRead(cache, info->size_dpfs_lvl2, info->offset_dpfs_lvl2)) ret = 1;
|
||||||
|
|
||||||
// cherry-pick lvl2_1
|
|
||||||
u32 log_lvl2 = info->log_dpfs_lvl2;
|
|
||||||
u32 offset_lvl2_1 = info->offset_dpfs_lvl2 + info->size_dpfs_lvl2;
|
u32 offset_lvl2_1 = info->offset_dpfs_lvl2 + info->size_dpfs_lvl2;
|
||||||
for (u32 i = 0; (ret == 0) && ((i << (3 + log_lvl2)) < min_cache_size); i += 4) {
|
|
||||||
u32 dword = *(u32*) (void*) (lvl1 + i);
|
for (u32 i = 0; i < lv2_min_num_blocks; i++) {
|
||||||
for (u32 b = 0; b < 32; b++) {
|
if (!GET_DPFS_BIT(i, lvl1)) {
|
||||||
if ((dword >> (31 - b)) & 1) {
|
// this lv2 block is not in lv2_1, so we don't need to replace it
|
||||||
u32 offset = ((i << 3) + b) << log_lvl2;
|
continue;
|
||||||
if (DisaDiffRead((u8*) cache + offset, 1 << log_lvl2, offset_lvl2_1 + offset) != FR_OK) ret = 1;
|
}
|
||||||
}
|
|
||||||
|
u32 offset = i * blocksize_lvl2;
|
||||||
|
if (offset > cache_size || offset + blocksize_lvl2 > cache_size || blocksize_lvl2 > cache_size) {
|
||||||
|
// this was known to corrupt the heap before, and shouldn't ever happen, but still
|
||||||
|
ret = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DisaDiffRead((u8*) cache + offset, blocksize_lvl2, offset_lvl2_1 + offset) != FR_OK) {
|
||||||
|
ret = 1;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -570,4 +620,201 @@ u32 WriteDisaDiffIvfcLvl4(const char* path, const DisaDiffRWInfo* info, u32 offs
|
|||||||
DisaDiffClose();
|
DisaDiffClose();
|
||||||
if (cache) free(cache);
|
if (cache) free(cache);
|
||||||
return size;
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline u64 CalcIvfcTreeSize(const IvfcDescriptor *ivfc, bool ext_lv4) {
|
||||||
|
int end_level = ext_lv4 ? 3 : 4;
|
||||||
|
return LVL(ivfc, end_level).offset + LVL(ivfc, end_level).size - LVL(ivfc, 1).offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline u64 CalcDpfsTreeSize(const DpfsDescriptor *dpfs) {
|
||||||
|
return LVL(dpfs, 3).offset + LVL(dpfs, 3).size * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline u64 CalcDifiDescriptorSize(const DifiHeader *difi) {
|
||||||
|
return difi->offset_hash + difi->size_hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline u64 CalcDiffFileSize(const DiffHeader *diff) {
|
||||||
|
return diff->offset_partition + diff->size_partition;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void BuildIvfcDescriptor(IvfcDescriptor *ivfc, u64 data_size, bool db) {
|
||||||
|
const IvfcDpfsConfig *config = GetIvfcDpfsConfig(db);
|
||||||
|
|
||||||
|
memset(ivfc, 0, sizeof(IvfcDescriptor));
|
||||||
|
memcpy(ivfc, ivfc_magic, sizeof(ivfc_magic));
|
||||||
|
ivfc->size_ivfc = sizeof(IvfcDescriptor);
|
||||||
|
ivfc->offset_tree = 0;
|
||||||
|
|
||||||
|
LVL(ivfc, 4).size = data_size;
|
||||||
|
LVL(ivfc, 3).size = 32 * ceil_div(data_size, config->ivfc_blocksizes[L(4)]);
|
||||||
|
LVL(ivfc, 2).size = 32 * ceil_div(LVL(ivfc, 3).size, config->ivfc_blocksizes[L(3)]);
|
||||||
|
LVL(ivfc, 1).size = 32 * ceil_div(LVL(ivfc, 2).size, config->ivfc_blocksizes[L(2)]);
|
||||||
|
|
||||||
|
ivfc->size_hash = 32 * ceil_div(LVL(ivfc, 1).size, config->ivfc_blocksizes[L(1)]);
|
||||||
|
|
||||||
|
u64 cur_offs = 0;
|
||||||
|
|
||||||
|
for (int i = 1; i < 4 + 1; i++) {
|
||||||
|
u64 cur_lvl_offs = 0;
|
||||||
|
if (LVL(ivfc, i).size < 4 * config->ivfc_blocksizes[L(i)]) {
|
||||||
|
cur_lvl_offs = align_pow2(cur_offs, 8);
|
||||||
|
} else {
|
||||||
|
cur_lvl_offs = align_pow2(cur_offs, config->ivfc_blocksizes[L(i)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
LVL(ivfc, i).offset = cur_lvl_offs;
|
||||||
|
LVL(ivfc, i).blocksize_log2 = log_2(config->ivfc_blocksizes[L(i)]);
|
||||||
|
|
||||||
|
cur_offs = LVL(ivfc, i).offset + LVL(ivfc, i).size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void BuildDpfsDescriptor(DpfsDescriptor *dpfs, u64 data_dupsize, bool db) {
|
||||||
|
const IvfcDpfsConfig *config = GetIvfcDpfsConfig(db);
|
||||||
|
|
||||||
|
memset(dpfs, 0, sizeof(DpfsDescriptor));
|
||||||
|
memcpy(dpfs, dpfs_magic, sizeof(dpfs_magic));
|
||||||
|
|
||||||
|
u32 lv2_num_block = ceil_div(data_dupsize, config->dpfs_lv3_blocksize);
|
||||||
|
u32 lv2_num_byte = ceil_div(lv2_num_block, 8);
|
||||||
|
u32 lv2_size = align_pow2(lv2_num_byte, config->dpfs_lv2_blocksize);
|
||||||
|
|
||||||
|
u32 lv1_num_block = ceil_div(lv2_size, config->dpfs_lv2_blocksize);
|
||||||
|
u32 lv1_num_byte = ceil_div(lv1_num_block, 8);
|
||||||
|
u32 lv1_size = align_pow2(lv1_num_byte, 4);
|
||||||
|
|
||||||
|
LVL(dpfs, 1).offset = 0;
|
||||||
|
LVL(dpfs, 1).size = lv1_size;
|
||||||
|
LVL(dpfs, 1).blocksize_log2 = 0;
|
||||||
|
|
||||||
|
LVL(dpfs, 2).offset = lv1_size * 2;
|
||||||
|
LVL(dpfs, 2).size = lv2_size;
|
||||||
|
LVL(dpfs, 2).blocksize_log2 = log_2(config->dpfs_lv2_blocksize);
|
||||||
|
|
||||||
|
LVL(dpfs, 3).offset = align_pow2(LVL(dpfs, 2).offset + lv2_size * 2, config->dpfs_lv3_blocksize);
|
||||||
|
LVL(dpfs, 3).size = (u32)data_dupsize;
|
||||||
|
LVL(dpfs, 3).blocksize_log2 = log_2(config->dpfs_lv3_blocksize);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void BuildDifiDescriptor(DifiHeader *difi, IvfcDescriptor *ivfc, DpfsDescriptor *dpfs, u64 data_size, bool ext_lv4, bool db) {
|
||||||
|
const IvfcDpfsConfig *config = GetIvfcDpfsConfig(db);
|
||||||
|
|
||||||
|
BuildIvfcDescriptor(ivfc, data_size, db);
|
||||||
|
|
||||||
|
u64 dpfs_duped_size = align_pow2(CalcIvfcTreeSize(ivfc, ext_lv4), config->dpfs_lv3_blocksize);
|
||||||
|
|
||||||
|
BuildDpfsDescriptor(dpfs, dpfs_duped_size, db);
|
||||||
|
|
||||||
|
memset(difi, 0, sizeof(DifiHeader));
|
||||||
|
memcpy(difi, difi_magic, sizeof(difi_magic));
|
||||||
|
difi->size_ivfc = sizeof(IvfcDescriptor);
|
||||||
|
difi->size_dpfs = sizeof(DpfsDescriptor);
|
||||||
|
difi->size_hash = ivfc->size_hash;
|
||||||
|
difi->dpfs_lvl1_selector = 1;
|
||||||
|
difi->ivfc_use_extlvl4 = ext_lv4 ? 1 : 0;
|
||||||
|
|
||||||
|
difi->offset_ivfc = sizeof(DifiHeader);
|
||||||
|
difi->offset_dpfs = align_pow2(difi->offset_ivfc + difi->size_ivfc, 4);
|
||||||
|
difi->offset_hash = align_pow2(difi->offset_dpfs + difi->size_dpfs, 4);
|
||||||
|
|
||||||
|
if (ext_lv4) {
|
||||||
|
difi->ivfc_offset_extlvl4 = align_pow2(CalcDpfsTreeSize(dpfs), config->ivfc_blocksizes[L(4)]);
|
||||||
|
} else {
|
||||||
|
difi->ivfc_offset_extlvl4 = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void BuildDiffHeader(DiffHeader *diff, DifiHeader *difi, IvfcDescriptor *ivfc, DpfsDescriptor *dpfs, u64 data_size, bool ext_lv4, bool db, u64 *out_uid) {
|
||||||
|
const IvfcDpfsConfig *config = GetIvfcDpfsConfig(db);
|
||||||
|
|
||||||
|
BuildDifiDescriptor(difi, ivfc, dpfs, data_size, ext_lv4, db);
|
||||||
|
|
||||||
|
memset(diff, 0, sizeof(DiffHeader));
|
||||||
|
|
||||||
|
memcpy(diff, diff_magic, sizeof(diff_magic));
|
||||||
|
|
||||||
|
if (db) { // db files have a 0 in the unique id field
|
||||||
|
diff->unique_id = 0;
|
||||||
|
} else { // non-db DIFFs (mainly extdata files) have a random 8-byte unique id
|
||||||
|
u32 *uid = (u32 *)&diff->unique_id;
|
||||||
|
*uid++ = (u32)rand();
|
||||||
|
*uid = (u32)rand();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out_uid)
|
||||||
|
*out_uid = diff->unique_id;
|
||||||
|
|
||||||
|
diff->size_table = CalcDifiDescriptorSize(difi);
|
||||||
|
|
||||||
|
if (ext_lv4) {
|
||||||
|
diff->size_partition = difi->ivfc_offset_extlvl4 + data_size;
|
||||||
|
} else {
|
||||||
|
diff->size_partition = CalcDpfsTreeSize(dpfs);
|
||||||
|
}
|
||||||
|
|
||||||
|
diff->offset_table1 = sizeof(DiffHeader) + 0x100 /* CMAC section size */;
|
||||||
|
diff->offset_table0 = align_pow2(diff->offset_table1 + diff->size_table, 8);
|
||||||
|
diff->offset_partition = align_pow2(diff->offset_table0 + diff->size_table, CalcIvfcDpfsConfigBlockSize(config));
|
||||||
|
diff->active_table = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 BuildDiffCalcRequiredSize(u64 data_size, bool ext_lv4, bool db) {
|
||||||
|
IvfcDescriptor ivfc;
|
||||||
|
DpfsDescriptor dpfs;
|
||||||
|
DifiHeader difi;
|
||||||
|
DiffHeader diff;
|
||||||
|
|
||||||
|
BuildDiffHeader(&diff, &difi, &ivfc, &dpfs, data_size, ext_lv4, db, NULL);
|
||||||
|
|
||||||
|
return diff.offset_partition + diff.size_partition;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 CreateDiff(const char *path, u64 data_size, bool ext_lv4, bool db, u64 *out_uid) {
|
||||||
|
IvfcDescriptor ivfc;
|
||||||
|
DpfsDescriptor dpfs;
|
||||||
|
DifiHeader difi;
|
||||||
|
DiffHeader diff;
|
||||||
|
u8 cmac[0x100];
|
||||||
|
|
||||||
|
BuildDiffHeader(&diff, &difi, &ivfc, &dpfs, data_size, ext_lv4, db, out_uid);
|
||||||
|
|
||||||
|
memset(cmac, 0, sizeof(cmac));
|
||||||
|
|
||||||
|
u8 *part_desc = (u8 *)malloc(diff.size_table);
|
||||||
|
if (!part_desc)
|
||||||
|
return 1;
|
||||||
|
u8 *desc = part_desc;
|
||||||
|
|
||||||
|
memcpy(desc, &difi, sizeof(DifiHeader)); desc += sizeof(DifiHeader);
|
||||||
|
memcpy(desc, &ivfc, sizeof(IvfcDescriptor)); desc += sizeof(IvfcDescriptor);
|
||||||
|
memcpy(desc, &dpfs, sizeof(DpfsDescriptor)); desc += sizeof(DpfsDescriptor);
|
||||||
|
memset(desc, 0, difi.size_hash);
|
||||||
|
|
||||||
|
sha_quick(diff.hash_table, part_desc, diff.size_table, SHA256_MODE);
|
||||||
|
|
||||||
|
FIL fp;
|
||||||
|
UINT written = 0;
|
||||||
|
|
||||||
|
if (fvx_open(&fp, path, FA_WRITE | FA_OPEN_EXISTING) != FR_OK) {
|
||||||
|
free(part_desc);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fvx_size(&fp) < CalcDiffFileSize(&diff) ||
|
||||||
|
(fvx_write(&fp, cmac, sizeof(cmac), &written) != FR_OK || written != sizeof(cmac)) ||
|
||||||
|
(fvx_write(&fp, &diff, sizeof(diff), &written) != FR_OK || written != sizeof(diff)) ||
|
||||||
|
fvx_lseek(&fp, diff.offset_table1) != FR_OK ||
|
||||||
|
(fvx_write(&fp, part_desc, diff.size_table, &written) != FR_OK || written != diff.size_table) ||
|
||||||
|
fvx_lseek(&fp, diff.offset_table0) != FR_OK ||
|
||||||
|
(fvx_write(&fp, part_desc, diff.size_table, &written) != FR_OK || written != diff.size_table)) {
|
||||||
|
free(part_desc);
|
||||||
|
fvx_close(&fp);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(part_desc);
|
||||||
|
fvx_close(&fp);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
// condensed info to enable reading/writing IVFC lvl4
|
// condensed info to enable reading/writing IVFC lvl4
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
u64 unique_id;
|
||||||
u32 offset_table;
|
u32 offset_table;
|
||||||
u32 size_table;
|
u32 size_table;
|
||||||
u32 offset_partition_hash;
|
u32 offset_partition_hash;
|
||||||
@ -46,9 +47,13 @@ typedef struct {
|
|||||||
u8* dpfs_lvl2_cache; // optional, NULL when unused
|
u8* dpfs_lvl2_cache; // optional, NULL when unused
|
||||||
} __attribute__((packed)) DisaDiffRWInfo;
|
} __attribute__((packed)) DisaDiffRWInfo;
|
||||||
|
|
||||||
|
u64 BuildDiffCalcRequiredSize(u64 data_size, bool ext_lv4, bool db);
|
||||||
|
u32 CreateDiff(const char *path, u64 data_size, bool ext_lv4, bool db, u64 *out_uid);
|
||||||
|
|
||||||
u32 GetDisaDiffRWInfo(const char* path, DisaDiffRWInfo* info, bool partitionB);
|
u32 GetDisaDiffRWInfo(const char* path, DisaDiffRWInfo* info, bool partitionB);
|
||||||
u32 BuildDisaDiffDpfsLvl2Cache(const char* path, const DisaDiffRWInfo* info, u8* cache, u32 cache_size);
|
u32 BuildDisaDiffDpfsLvl2Cache(const char* path, const DisaDiffRWInfo* info, u8* cache, u32 cache_size);
|
||||||
u32 ReadDisaDiffIvfcLvl4(const char* path, const DisaDiffRWInfo* info, u32 offset, u32 size, void* buffer);
|
u32 ReadDisaDiffIvfcLvl4(const char* path, const DisaDiffRWInfo* info, u32 offset, u32 size, void* buffer);
|
||||||
u32 WriteDisaDiffIvfcLvl4(const char* path, const DisaDiffRWInfo* info, u32 offset, u32 size, const void* buffer);
|
u32 WriteDisaDiffIvfcLvl4(const char* path, const DisaDiffRWInfo* info, u32 offset, u32 size, const void* buffer);
|
||||||
|
|
||||||
// Not intended for external use other than vdisadiff
|
// Not intended for external use other than vdisadiff
|
||||||
u32 FixDisaDiffIvfcLevel(const DisaDiffRWInfo* info, u32 level, u32 offset, u32 size, u32* next_offset, u32* next_size);
|
u32 FixDisaDiffIvfcLevel(const DisaDiffRWInfo* info, u32 level, u32 offset, u32 size, u32* next_offset, u32* next_size);
|
||||||
76
arm9/source/game/ifat_common.h
Normal file
76
arm9/source/game/ifat_common.h
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
// These structures are shared across 3 different inner-FAT (named ifat for brevity) types:
|
||||||
|
// - SAVE: save data
|
||||||
|
// - EXSV: extdata
|
||||||
|
// - IRDB (BDRI): title.db & ticket.db
|
||||||
|
|
||||||
|
// Each ifat type has its own type of file and directory entry.
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
union {
|
||||||
|
// this describes data inside the FAT (and thus, in the data region)
|
||||||
|
struct {
|
||||||
|
u32 starting_block_index;
|
||||||
|
u32 block_count;
|
||||||
|
u32 max_entry_count;
|
||||||
|
} PACKED_STRUCT;
|
||||||
|
|
||||||
|
// this describes data outside the FAT (and thus, outside the data region)
|
||||||
|
struct {
|
||||||
|
u64 outfat_offset;
|
||||||
|
u32 outfat_count;
|
||||||
|
} PACKED_STRUCT;
|
||||||
|
};
|
||||||
|
u32 _pad;
|
||||||
|
} PACKED_STRUCT IFatTableInfo;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
union {
|
||||||
|
u32 magic;
|
||||||
|
char magic_str[4];
|
||||||
|
};
|
||||||
|
u32 version;
|
||||||
|
u64 fs_info_offset;
|
||||||
|
u64 fs_image_size_blocks;
|
||||||
|
u32 fs_image_blocksize;
|
||||||
|
u32 __pad;
|
||||||
|
} PACKED_STRUCT IFatPreHeader;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u32 _unk;
|
||||||
|
u32 data_region_blocksize;
|
||||||
|
IFatTableInfo dir_hashtbl;
|
||||||
|
IFatTableInfo file_hashtbl;
|
||||||
|
IFatTableInfo fat;
|
||||||
|
IFatTableInfo data_region;
|
||||||
|
IFatTableInfo dirtable_info;
|
||||||
|
IFatTableInfo filetable_info;
|
||||||
|
} PACKED_STRUCT IFatFsInfo;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* for node head,
|
||||||
|
* - U.index --> index of previous node head.
|
||||||
|
* - U.flag --> set if this is the first node head.
|
||||||
|
* - V.index --> index of next node head.
|
||||||
|
* - V.flag --> whether or not this node has extended entries (multiple entries)
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* for extended node,
|
||||||
|
* - U.index --> index of previous entry in this node.
|
||||||
|
* - U.flag --> always set.
|
||||||
|
* - V.index --> index of next entry in this node.
|
||||||
|
* - V.flag --> never set.
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct __attribute__((packed)) IFatEntryHalf {
|
||||||
|
u32 index: 31;
|
||||||
|
u32 flag: 1;
|
||||||
|
} IFatEntryHalf;
|
||||||
|
|
||||||
|
typedef struct __attribute__((packed)) IFatEntry {
|
||||||
|
IFatEntryHalf U, V;
|
||||||
|
} IFatEntry;
|
||||||
261
arm9/source/game/saveexsv.c
Normal file
261
arm9/source/game/saveexsv.c
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
#include "saveexsv.h"
|
||||||
|
#include "common.h"
|
||||||
|
#include "fsutil.h"
|
||||||
|
#include "types.h"
|
||||||
|
#include "vff.h"
|
||||||
|
#include "ff.h"
|
||||||
|
|
||||||
|
#define PART_A_PATH "D:/partitionA.bin"
|
||||||
|
#define PART_B_PATH "D:/partitionB.bin"
|
||||||
|
|
||||||
|
static int read_alloc_chunk(void **output, FIL *file, u64 offset, u32 size) {
|
||||||
|
UINT nread = 0;
|
||||||
|
*output = NULL;
|
||||||
|
if (!(*output = malloc(size)) ||
|
||||||
|
fvx_lseek(file, offset) != FR_OK ||
|
||||||
|
fvx_read(file, *output, size, &nread) != FR_OK || nread != size) {
|
||||||
|
if (*output) { free(*output); *output = NULL; };
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static FIL part_a = { 0 }, part_b = { 0 };
|
||||||
|
static FIL *data_part = NULL;
|
||||||
|
|
||||||
|
int SaveExsvFileInit(SaveExsvFile *sav) {
|
||||||
|
UINT nread = 0;
|
||||||
|
int retval = 0;
|
||||||
|
|
||||||
|
// this shouldn't happen, but if it does, it should be a no-op
|
||||||
|
if (sav->init_ok) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// partitionA is mandatory
|
||||||
|
if (fvx_open(&part_a, PART_A_PATH, FA_READ) != FR_OK)
|
||||||
|
goto err_exit;
|
||||||
|
|
||||||
|
if (PathExist(PART_B_PATH)) {
|
||||||
|
// if partitionB exists, we need it
|
||||||
|
if (fvx_open(&part_b, PART_B_PATH, FA_READ) != FR_OK)
|
||||||
|
goto err_exit;
|
||||||
|
|
||||||
|
data_part = &part_b;
|
||||||
|
} else {
|
||||||
|
data_part = &part_a;
|
||||||
|
}
|
||||||
|
|
||||||
|
sav->duplicate_meta = data_part == &part_b;
|
||||||
|
|
||||||
|
u32 dir_table_offset = 0, dir_table_size = 0;
|
||||||
|
u32 file_table_offset = 0, file_table_size = 0;
|
||||||
|
|
||||||
|
if (fvx_read(&part_a, &sav->pre_header, sizeof(IFatPreHeader), &nread) != FR_OK || nread != sizeof(IFatPreHeader))
|
||||||
|
goto err_exit;
|
||||||
|
|
||||||
|
u32 full_header_size = sizeof(IFatPreHeader) + sizeof(IFatFsInfo);
|
||||||
|
|
||||||
|
if (sav->pre_header.magic == 0x45564153 ) { /* SAVE */
|
||||||
|
if (sav->pre_header.version != 0x40000 || sav->pre_header.fs_info_offset != 0x20)
|
||||||
|
goto err_exit;
|
||||||
|
} else if (sav->pre_header.magic == 0x45585356) { /* EXSV (VSXE) */
|
||||||
|
if (sav->pre_header.version != 0x30000 || sav->pre_header.fs_info_offset != 0x138 ||
|
||||||
|
fvx_read(&part_a, &sav->exsv_extra_hdr, sizeof(ExsvExtraHeader), &nread) != FR_OK || nread != sizeof(ExsvExtraHeader))
|
||||||
|
goto err_exit;
|
||||||
|
|
||||||
|
full_header_size += sizeof(ExsvExtraHeader);
|
||||||
|
sav->is_exsv = true;
|
||||||
|
} else {
|
||||||
|
goto err_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fvx_read(&part_a, &sav->fs_info, sizeof(IFatFsInfo), &nread) != FR_OK || nread != sizeof(IFatFsInfo))
|
||||||
|
goto err_exit;
|
||||||
|
|
||||||
|
u64 part_a_size = fvx_size(&part_a);
|
||||||
|
|
||||||
|
if (!sav->duplicate_meta) {
|
||||||
|
|
||||||
|
dir_table_offset = sav->fs_info.data_region.outfat_offset + sav->fs_info.dirtable_info.starting_block_index * sav->fs_info.data_region_blocksize;
|
||||||
|
dir_table_size = sav->fs_info.data_region_blocksize * sav->fs_info.dirtable_info.block_count;
|
||||||
|
|
||||||
|
file_table_offset = sav->fs_info.data_region.outfat_offset + sav->fs_info.filetable_info.starting_block_index * sav->fs_info.data_region_blocksize;
|
||||||
|
file_table_size = sav->fs_info.data_region_blocksize * sav->fs_info.filetable_info.block_count;
|
||||||
|
|
||||||
|
// duplicate data has the dir and file tables in the data region, so we don't need to count them separately
|
||||||
|
u64 min_part_a_size = align(full_header_size +
|
||||||
|
sav->fs_info.dir_hashtbl.outfat_count * sizeof(u32) +
|
||||||
|
sav->fs_info.file_hashtbl.outfat_count * sizeof(u32) +
|
||||||
|
(sav->fs_info.fat.outfat_count + 1) * sizeof(IFatEntry),
|
||||||
|
sav->pre_header.fs_image_blocksize) +
|
||||||
|
sav->fs_info.data_region.outfat_count * sav->fs_info.data_region_blocksize;
|
||||||
|
|
||||||
|
if (part_a_size < min_part_a_size) {
|
||||||
|
goto err_exit;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// it should not be possible for the extdata main DIFF to be duplicate meta
|
||||||
|
if (sav->is_exsv)
|
||||||
|
goto err_exit;
|
||||||
|
|
||||||
|
dir_table_offset = sav->fs_info.dirtable_info.outfat_offset;
|
||||||
|
dir_table_size = (sav->fs_info.dirtable_info.outfat_count + 2) * sizeof(SaveDirectoryEntry);
|
||||||
|
|
||||||
|
file_table_offset = sav->fs_info.filetable_info.outfat_offset;
|
||||||
|
file_table_size = (sav->fs_info.filetable_info.outfat_count + 1) * sizeof(SaveFileEntry);
|
||||||
|
|
||||||
|
// duplicate meta has the dir and file tables outside the data region
|
||||||
|
u64 part_b_size = fvx_size(&part_b);
|
||||||
|
|
||||||
|
u64 min_part_a_size = align(full_header_size +
|
||||||
|
sav->fs_info.dir_hashtbl.outfat_count * sizeof(u32) +
|
||||||
|
sav->fs_info.file_hashtbl.outfat_count * sizeof(u32) +
|
||||||
|
(sav->fs_info.fat.outfat_count + 1) * sizeof(IFatEntry) +
|
||||||
|
dir_table_size +
|
||||||
|
file_table_size,
|
||||||
|
sav->pre_header.fs_image_blocksize);
|
||||||
|
u64 min_part_b_size = sav->fs_info.data_region.outfat_count * sav->fs_info.data_region_blocksize;
|
||||||
|
|
||||||
|
if (part_a_size < min_part_a_size || part_b_size < min_part_b_size)
|
||||||
|
goto err_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dir_table_offset || !dir_table_size || !file_table_offset || !file_table_size)
|
||||||
|
goto err_exit;
|
||||||
|
|
||||||
|
sav->max_num_file_entries = file_table_size / sizeof(SaveFileEntry);
|
||||||
|
sav->max_num_dir_entries = dir_table_size / sizeof(SaveDirectoryEntry);
|
||||||
|
|
||||||
|
if (read_alloc_chunk((void **)&sav->dir_hashtbl, &part_a, sav->fs_info.dir_hashtbl.outfat_offset, sav->fs_info.dir_hashtbl.outfat_count * 4) ||
|
||||||
|
read_alloc_chunk((void **)&sav->file_hashtbl, &part_a, sav->fs_info.file_hashtbl.outfat_offset, sav->fs_info.file_hashtbl.outfat_count * 4) ||
|
||||||
|
read_alloc_chunk((void **)&sav->fat_entries, &part_a, sav->fs_info.fat.outfat_offset, (sav->fs_info.fat.outfat_count + 1) * sizeof(IFatEntry)) ||
|
||||||
|
read_alloc_chunk((void **)&sav->dir_entries, &part_a, dir_table_offset, dir_table_size) ||
|
||||||
|
read_alloc_chunk((void **)&sav->file_entries, &part_a, file_table_offset, file_table_size)) {
|
||||||
|
goto err_exit;
|
||||||
|
}
|
||||||
|
ret:
|
||||||
|
fvx_close(&part_a);
|
||||||
|
fvx_close(&part_b);
|
||||||
|
sav->init_ok = !retval;
|
||||||
|
return retval;
|
||||||
|
err_exit:
|
||||||
|
retval = 1;
|
||||||
|
goto ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SaveExsvFileFree(SaveExsvFile *sav) {
|
||||||
|
data_part = NULL;
|
||||||
|
if (sav->dir_hashtbl) { free(sav->dir_hashtbl); sav->dir_hashtbl = NULL; }
|
||||||
|
if (sav->file_hashtbl) { free(sav->file_hashtbl); sav->file_hashtbl = NULL; }
|
||||||
|
if (sav->fat_entries) { free(sav->fat_entries); sav->fat_entries = NULL; }
|
||||||
|
if (sav->dir_entries) { free(sav->dir_entries); sav->dir_entries = NULL; }
|
||||||
|
if (sav->file_entries) { free(sav->file_entries); sav->file_entries = NULL; }
|
||||||
|
sav->max_num_file_entries = sav->max_num_dir_entries = 0;
|
||||||
|
sav->init_ok = false;
|
||||||
|
sav->is_exsv = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct ReadFileData {
|
||||||
|
u32 offset;
|
||||||
|
u32 size;
|
||||||
|
char *outbuf;
|
||||||
|
} ReadFileData;
|
||||||
|
|
||||||
|
static int ReadFile(u32 part_offset, u32 file_offset, u32 size, void *arg) {
|
||||||
|
ReadFileData *data = (ReadFileData *)arg;
|
||||||
|
|
||||||
|
UINT nread = 0;
|
||||||
|
|
||||||
|
if (file_offset <= data->offset && data->offset < file_offset + size) {
|
||||||
|
u32 in_block_offs = data->offset - file_offset;
|
||||||
|
u32 in_block_size = min(size - in_block_offs, data->size);
|
||||||
|
|
||||||
|
if (fvx_lseek(data_part, part_offset + in_block_offs) != FR_OK ||
|
||||||
|
fvx_read(data_part, data->outbuf, in_block_size, &nread) != FR_OK || nread != in_block_size) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
data->offset += in_block_size;
|
||||||
|
data->outbuf += in_block_size;
|
||||||
|
data->size -= in_block_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data->size) return 2;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0: OK/continue
|
||||||
|
// 1: error
|
||||||
|
// 2: OK, early exit processing chain
|
||||||
|
typedef int (* follow_cb)(u32 in_part_offset, u32 file_offset, u32 size, void *arg);
|
||||||
|
|
||||||
|
static int ProcessFatDataBlock(SaveExsvFile *save, u32 index, u32 *file_offset, follow_cb cb, void *cb_arg) {
|
||||||
|
if (!index)
|
||||||
|
return 0; // not yet
|
||||||
|
|
||||||
|
int cb_ret = cb(save->fs_info.data_region.outfat_offset + (index - 1) * save->fs_info.data_region_blocksize, *file_offset, save->fs_info.data_region_blocksize, cb_arg);
|
||||||
|
if (cb_ret)
|
||||||
|
return cb_ret;
|
||||||
|
|
||||||
|
*file_offset += save->fs_info.data_region_blocksize;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ProcessFatChain(SaveExsvFile *save, u32 initial_index, follow_cb cb, void *arg) {
|
||||||
|
u32 idx = initial_index;
|
||||||
|
u32 file_offset = 0;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (idx >= save->fs_info.fat.outfat_count) // shouldn't happen
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
IFatEntry *ent = &save->fat_entries[idx];
|
||||||
|
|
||||||
|
ret = ProcessFatDataBlock(save, idx, &file_offset, cb, arg);
|
||||||
|
if (ret == 2) return 0;
|
||||||
|
else if (ret == 1) return 1;
|
||||||
|
|
||||||
|
/* process extended nodes first */
|
||||||
|
if (ent->V.flag) {
|
||||||
|
if (idx + 1 >= save->fs_info.fat.outfat_count) // same as above, shouldn't happen, but still
|
||||||
|
return 1;
|
||||||
|
u32 end_index = save->fat_entries[idx + 1].V.index;
|
||||||
|
for (u32 i = idx + 1; i < end_index + 1; i++) {
|
||||||
|
ret = ProcessFatDataBlock(save, i, &file_offset, cb, arg);
|
||||||
|
if (ret == 2) return 0;
|
||||||
|
else if (ret == 1) return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ent->V.index)
|
||||||
|
idx = ent->V.index; /* go to next node, if we have one. */
|
||||||
|
else
|
||||||
|
idx = 0; /* no next node, we're done */
|
||||||
|
}
|
||||||
|
while (idx);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SaveExsvReadFatFile(SaveExsvFile *sav, u32 index, void *buffer, u32 offset, u32 count) {
|
||||||
|
if (!sav->init_ok || index >= sav->max_num_file_entries)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (sav->is_exsv) {
|
||||||
|
// Extdata uses physical files. Therefore, reading extdata files this way should be impossible.
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
SaveFileEntry *fent = &sav->file_entries[index];
|
||||||
|
|
||||||
|
// empty file
|
||||||
|
if (fent->ent.first_block_index == 0x80000000)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
ReadFileData data = { .offset = offset, .size = count, .outbuf = (char *)buffer };
|
||||||
|
|
||||||
|
return ProcessFatChain(sav, fent->ent.first_block_index + 1, ReadFile, &data);
|
||||||
|
}
|
||||||
|
}
|
||||||
97
arm9/source/game/saveexsv.h
Normal file
97
arm9/source/game/saveexsv.h
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
#include "ifat_common.h"
|
||||||
|
|
||||||
|
// SAVE images may be created in two modes:
|
||||||
|
// - "duplicate data" mode, in which both filesystem metadata and the actual data FAT are duplicated
|
||||||
|
// (partitionA: file metadata and FAT, duplicated by DPFS tree)
|
||||||
|
// - "duplicate meta" mode, in which only the filesystem metadata is duplicated, and the FAT is not
|
||||||
|
// (partitionA: file metadata, duplicated by DPFS tree. partitionB: data FAT)
|
||||||
|
|
||||||
|
typedef enum ExsvAction
|
||||||
|
{
|
||||||
|
EXSV_ACTION_NONE = 0x0, // no last action
|
||||||
|
EXSV_ACTION_DELETE_FILE = 0x1, // last action was a file deletion
|
||||||
|
EXSV_ACTION_CREATE_FILE = 0x2, // last action was a file creation
|
||||||
|
} ExsvAction;
|
||||||
|
|
||||||
|
typedef struct __attribute__((packed)) ExsvExtraHeader {
|
||||||
|
u64 sub_file_base_id;
|
||||||
|
u32 pending_action;
|
||||||
|
u32 unk2;
|
||||||
|
u64 last_file_id;
|
||||||
|
char LastFilePath[256];
|
||||||
|
} ExsvExtraHeader;
|
||||||
|
|
||||||
|
// same for files and directories
|
||||||
|
typedef struct __attribute__((packed)) SaveEntryKey {
|
||||||
|
u32 parent_dir_idx;
|
||||||
|
char name[16];
|
||||||
|
} SaveEntryKey;
|
||||||
|
|
||||||
|
typedef struct SaveDirectoryEntryData {
|
||||||
|
SaveEntryKey key;
|
||||||
|
u32 next_sibling_dir_index;
|
||||||
|
u32 first_subdir_index;
|
||||||
|
u32 first_file_index; /* in file entry table */
|
||||||
|
u32 __pad;
|
||||||
|
u32 next_dir_in_bucket_index;
|
||||||
|
} SaveDirectoryEntryData;
|
||||||
|
|
||||||
|
typedef struct SaveDummyDirectoryEntryData {
|
||||||
|
u32 current_total_entry_count;
|
||||||
|
u32 max_entry_count; /* max dir count + 2 */
|
||||||
|
u8 __pad[0x1C];
|
||||||
|
u32 next_dummy_index;
|
||||||
|
} SaveDummyDirectoryEntryData;
|
||||||
|
|
||||||
|
typedef union __attribute__((packed)) SaveDirectoryEntry {
|
||||||
|
SaveDummyDirectoryEntryData dmy;
|
||||||
|
SaveDirectoryEntryData ent;
|
||||||
|
} SaveDirectoryEntry;
|
||||||
|
|
||||||
|
typedef struct __attribute__((packed)) SaveFileEntryData {
|
||||||
|
SaveEntryKey key;
|
||||||
|
u32 next_sibling_index;
|
||||||
|
u32 __pad;
|
||||||
|
u32 first_block_index; /* in data region; 0x80000000 if no data */
|
||||||
|
union {
|
||||||
|
u64 file_size; // for SAVE
|
||||||
|
u64 file_unique_id; // for EXSV
|
||||||
|
};
|
||||||
|
u32 _unk_pad;
|
||||||
|
u32 next_file_in_bucket_index;
|
||||||
|
} SaveFileEntryData;
|
||||||
|
|
||||||
|
typedef struct SaveDummyFileEntryData {
|
||||||
|
u32 current_total_entry_count;
|
||||||
|
u32 max_entry_count; /* max file count + 1 */
|
||||||
|
u8 __pad[0x24];
|
||||||
|
u32 next_dummy_index;
|
||||||
|
} SaveDummyFileEntryData;
|
||||||
|
|
||||||
|
typedef union SaveFileEntry {
|
||||||
|
SaveDummyFileEntryData dmy;
|
||||||
|
SaveFileEntryData ent;
|
||||||
|
} SaveFileEntry;
|
||||||
|
|
||||||
|
typedef struct SaveExsvFile {
|
||||||
|
ExsvExtraHeader exsv_extra_hdr;
|
||||||
|
IFatFsInfo fs_info;
|
||||||
|
IFatPreHeader pre_header;
|
||||||
|
u32 *dir_hashtbl;
|
||||||
|
u32 *file_hashtbl;
|
||||||
|
IFatEntry *fat_entries;
|
||||||
|
SaveDirectoryEntry *dir_entries;
|
||||||
|
u32 max_num_dir_entries;
|
||||||
|
SaveFileEntry *file_entries;
|
||||||
|
u32 max_num_file_entries;
|
||||||
|
bool duplicate_meta;
|
||||||
|
bool is_exsv;
|
||||||
|
bool init_ok;
|
||||||
|
} SaveExsvFile;
|
||||||
|
|
||||||
|
int SaveExsvFileInit(SaveExsvFile *sav);
|
||||||
|
int SaveExsvReadFatFile(SaveExsvFile *sav, u32 index, void *buffer, u32 offset, u32 count);
|
||||||
|
void SaveExsvFileFree(SaveExsvFile *sav);
|
||||||
@ -1071,7 +1071,7 @@ u32 CartRawDump(void) {
|
|||||||
|
|
||||||
if (ret) ShowPrompt(false, STR_FAILED_DUMPING_CART, cname);
|
if (ret) ShowPrompt(false, STR_FAILED_DUMPING_CART, cname);
|
||||||
else ShowPrompt(false, STR_PATH_DUMPED_TO_OUT, cname, OUTPUT_PATH);
|
else ShowPrompt(false, STR_PATH_DUMPED_TO_OUT, cname, OUTPUT_PATH);
|
||||||
|
|
||||||
free(buf);
|
free(buf);
|
||||||
free(cdata);
|
free(cdata);
|
||||||
return ret;
|
return ret;
|
||||||
@ -1566,7 +1566,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
|||||||
ShowSelectPrompt(n_opt, optionstr, STR_PATH_N_FILES_SELECTED, pathstr, n_marked) :
|
ShowSelectPrompt(n_opt, optionstr, STR_PATH_N_FILES_SELECTED, pathstr, n_marked) :
|
||||||
ShowSelectPrompt(n_opt, optionstr, "%s%s", pathstr, tidstr));
|
ShowSelectPrompt(n_opt, optionstr, "%s%s", pathstr, tidstr));
|
||||||
if (user_select == mount) { // -> mount file as image
|
if (user_select == mount) { // -> mount file as image
|
||||||
const char* mnt_drv_paths[] = { "7:", "G:", "K:", "T:", "I:", "D:" }; // maybe move that to fsdrive.h
|
const char* mnt_drv_paths[] = { "7:", "G:", "K:", "T:", "I:", "F:", "D:" }; // maybe move that to fsdrive.h
|
||||||
if (clipboard->n_entries && (DriveType(clipboard->entry[0].path) & DRV_IMAGE))
|
if (clipboard->n_entries && (DriveType(clipboard->entry[0].path) & DRV_IMAGE))
|
||||||
clipboard->n_entries = 0; // remove last mounted image clipboard entries
|
clipboard->n_entries = 0; // remove last mounted image clipboard entries
|
||||||
SetTitleManagerMode(false); // disable title manager mode
|
SetTitleManagerMode(false); // disable title manager mode
|
||||||
@ -2654,7 +2654,7 @@ u32 GodMode(int entrypoint) {
|
|||||||
// basic navigation commands
|
// basic navigation commands
|
||||||
if ((pad_state & BUTTON_A) && (curr_entry->type != T_FILE) && (curr_entry->type != T_DOTDOT)) { // for dirs
|
if ((pad_state & BUTTON_A) && (curr_entry->type != T_FILE) && (curr_entry->type != T_DOTDOT)) { // for dirs
|
||||||
if (switched && !(DriveType(curr_entry->path) & (DRV_SEARCH|DRV_TITLEMAN))) { // exclude Y/Z
|
if (switched && !(DriveType(curr_entry->path) & (DRV_SEARCH|DRV_TITLEMAN))) { // exclude Y/Z
|
||||||
const char* optionstr[8] = { NULL };
|
const char* optionstr[9] = { NULL };
|
||||||
char tpath[16], copyToOut[UTF_BUFFER_BYTESIZE(64)], dumpToOut[UTF_BUFFER_BYTESIZE(64)];
|
char tpath[16], copyToOut[UTF_BUFFER_BYTESIZE(64)], dumpToOut[UTF_BUFFER_BYTESIZE(64)];
|
||||||
snprintf(tpath, sizeof(tpath), "%2.2s/dbs/title.db", curr_entry->path);
|
snprintf(tpath, sizeof(tpath), "%2.2s/dbs/title.db", curr_entry->path);
|
||||||
snprintf(copyToOut, sizeof(copyToOut), STR_COPY_TO_OUT, OUTPUT_PATH);
|
snprintf(copyToOut, sizeof(copyToOut), STR_COPY_TO_OUT, OUTPUT_PATH);
|
||||||
@ -2664,6 +2664,7 @@ u32 GodMode(int entrypoint) {
|
|||||||
((strncmp(curr_entry->path, tpath, 16) == 0) ||
|
((strncmp(curr_entry->path, tpath, 16) == 0) ||
|
||||||
(!*current_path && PathExist(tpath)))) ? ++n_opt : -1;
|
(!*current_path && PathExist(tpath)))) ? ++n_opt : -1;
|
||||||
int srch_f = ++n_opt;
|
int srch_f = ++n_opt;
|
||||||
|
int createdbs = (!*current_path && (strspn(curr_entry->path, "14AB") == 1)) ? ++n_opt : -1;
|
||||||
int fixcmac = (!*current_path && ((strspn(curr_entry->path, "14AB") == 1) ||
|
int fixcmac = (!*current_path && ((strspn(curr_entry->path, "14AB") == 1) ||
|
||||||
((GetMountState() == IMG_NAND) && (*(curr_entry->path) == '7')))) ? ++n_opt : -1;
|
((GetMountState() == IMG_NAND) && (*(curr_entry->path) == '7')))) ? ++n_opt : -1;
|
||||||
int dirnfo = ++n_opt;
|
int dirnfo = ++n_opt;
|
||||||
@ -2671,6 +2672,7 @@ u32 GodMode(int entrypoint) {
|
|||||||
int rawdump = (!*current_path && (DriveType(curr_entry->path) & DRV_CART)) ? ++n_opt : -1;
|
int rawdump = (!*current_path && (DriveType(curr_entry->path) & DRV_CART)) ? ++n_opt : -1;
|
||||||
if (tman > 0) optionstr[tman-1] = STR_OPEN_TITLE_MANAGER;
|
if (tman > 0) optionstr[tman-1] = STR_OPEN_TITLE_MANAGER;
|
||||||
if (srch_f > 0) optionstr[srch_f-1] = STR_SEARCH_FOR_FILES;
|
if (srch_f > 0) optionstr[srch_f-1] = STR_SEARCH_FOR_FILES;
|
||||||
|
if (createdbs > 0) optionstr[createdbs-1] = STR_CREATE_MISSING_DB_FILES;
|
||||||
if (fixcmac > 0) optionstr[fixcmac-1] = STR_FIX_CMACS_FOR_DRIVE;
|
if (fixcmac > 0) optionstr[fixcmac-1] = STR_FIX_CMACS_FOR_DRIVE;
|
||||||
if (dirnfo > 0) optionstr[dirnfo-1] = (*current_path) ? STR_SHOW_DIRECTORY_INFO : STR_SHOW_DRIVE_INFO;
|
if (dirnfo > 0) optionstr[dirnfo-1] = (*current_path) ? STR_SHOW_DIRECTORY_INFO : STR_SHOW_DRIVE_INFO;
|
||||||
if (stdcpy > 0) optionstr[stdcpy-1] = copyToOut;
|
if (stdcpy > 0) optionstr[stdcpy-1] = copyToOut;
|
||||||
@ -2698,6 +2700,9 @@ u32 GodMode(int entrypoint) {
|
|||||||
cursor = 1;
|
cursor = 1;
|
||||||
scroll = 0;
|
scroll = 0;
|
||||||
}
|
}
|
||||||
|
} else if (user_select == createdbs) {
|
||||||
|
char drv[3] = { *curr_entry->path, ':', '\0' };
|
||||||
|
CreateDbFilesForDrive(drv, false, false);
|
||||||
} else if (user_select == fixcmac) {
|
} else if (user_select == fixcmac) {
|
||||||
RecursiveFixFileCmac(curr_entry->path);
|
RecursiveFixFileCmac(curr_entry->path);
|
||||||
ShowPrompt(false, "%s", STR_FIX_CMACS_FOR_DRIVE_FINISHED);
|
ShowPrompt(false, "%s", STR_FIX_CMACS_FOR_DRIVE_FINISHED);
|
||||||
|
|||||||
@ -80,7 +80,7 @@ static int internalfs_remove(lua_State* L) {
|
|||||||
if (!(PathDelete(path))) {
|
if (!(PathDelete(path))) {
|
||||||
return luaL_error(L, "PathDelete failed on %s", path);
|
return luaL_error(L, "PathDelete failed on %s", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,6 +184,22 @@ static int internalfs_fix_cmacs(lua_State* L) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int internalfs_create_dbs(lua_State* L) {
|
||||||
|
bool extra = CheckLuaArgCountPlusExtra(L, 1, "_fs.create_dbs");
|
||||||
|
const char* destdrv = luaL_checkstring(L, 1);
|
||||||
|
|
||||||
|
u32 flags = 0;
|
||||||
|
if (extra) {
|
||||||
|
flags = GetFlagsFromTable(L, 2, flags, SILENT | OVERWRITE_ALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CreateDbFilesForDrive(destdrv, (flags & SILENT), (flags & OVERWRITE_ALL)) != 0) {
|
||||||
|
return luaL_error(L, "createdbs failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int internalfs_stat_fs(lua_State* L) {
|
static int internalfs_stat_fs(lua_State* L) {
|
||||||
CheckLuaArgCount(L, 1, "_fs.stat_fs");
|
CheckLuaArgCount(L, 1, "_fs.stat_fs");
|
||||||
const char* path = luaL_checkstring(L, 1);
|
const char* path = luaL_checkstring(L, 1);
|
||||||
@ -421,7 +437,7 @@ static int internalfs_write_file(lua_State* L) {
|
|||||||
const char* data = luaL_checklstring(L, 3, &data_length);
|
const char* data = luaL_checklstring(L, 3, &data_length);
|
||||||
|
|
||||||
CheckWritePermissionsLuaError(L, path);
|
CheckWritePermissionsLuaError(L, path);
|
||||||
|
|
||||||
UINT bytes_written = 0;
|
UINT bytes_written = 0;
|
||||||
FRESULT res = fvx_qwrite(path, data, offset, data_length, &bytes_written);
|
FRESULT res = fvx_qwrite(path, data, offset, data_length, &bytes_written);
|
||||||
if (res != FR_OK) {
|
if (res != FR_OK) {
|
||||||
@ -517,9 +533,9 @@ static int internalfs_img_mount(lua_State* L) {
|
|||||||
|
|
||||||
static int internalfs_img_umount(lua_State* L) {
|
static int internalfs_img_umount(lua_State* L) {
|
||||||
CheckLuaArgCount(L, 0, "_fs.img_umount");
|
CheckLuaArgCount(L, 0, "_fs.img_umount");
|
||||||
|
|
||||||
InitImgFS(NULL);
|
InitImgFS(NULL);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -793,6 +809,7 @@ static const luaL_Reg internalfs_lib[] = {
|
|||||||
{"sd_is_mounted", internalfs_sd_is_mounted},
|
{"sd_is_mounted", internalfs_sd_is_mounted},
|
||||||
{"sd_switch", internalfs_sd_switch},
|
{"sd_switch", internalfs_sd_switch},
|
||||||
{"fix_cmacs", internalfs_fix_cmacs},
|
{"fix_cmacs", internalfs_fix_cmacs},
|
||||||
|
{"create_dbs", internalfs_create_dbs},
|
||||||
{"key_dump", internalfs_key_dump},
|
{"key_dump", internalfs_key_dump},
|
||||||
{"cart_dump", internalfs_cart_dump},
|
{"cart_dump", internalfs_cart_dump},
|
||||||
{NULL, NULL}
|
{NULL, NULL}
|
||||||
|
|||||||
@ -4359,4 +4359,4 @@ u32 GetGoodName(char* name, const char* path, bool quick) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
#include "vsaveexsv.h" // for mounting savedata/extdata
|
||||||
#include "vdisadiff.h"
|
#include "vdisadiff.h"
|
||||||
#include "disadiff.h"
|
#include "disadiff.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
@ -194,7 +195,9 @@ u64 InitVDisaDiffDrive(void) {
|
|||||||
partitionB_info->rw_info = info;
|
partitionB_info->rw_info = info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// could be either of these
|
||||||
InitVBDRIDrive();
|
InitVBDRIDrive();
|
||||||
|
InitVSaveDrive();
|
||||||
|
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
#include "vbdri.h"
|
#include "vbdri.h"
|
||||||
#include "vkeydb.h"
|
#include "vkeydb.h"
|
||||||
#include "vcart.h"
|
#include "vcart.h"
|
||||||
|
#include "vsaveexsv.h"
|
||||||
#include "vvram.h"
|
#include "vvram.h"
|
||||||
#include "vdisadiff.h"
|
#include "vdisadiff.h"
|
||||||
#include "ff.h"
|
#include "ff.h"
|
||||||
@ -29,13 +30,14 @@ u32 GetVirtualSource(const char* path) {
|
|||||||
void DeinitVirtualImageDrive(void) {
|
void DeinitVirtualImageDrive(void) {
|
||||||
DeinitVGameDrive();
|
DeinitVGameDrive();
|
||||||
DeinitVBDRIDrive();
|
DeinitVBDRIDrive();
|
||||||
|
DeinitVSaveDrive();
|
||||||
DeinitVKeyDbDrive();
|
DeinitVKeyDbDrive();
|
||||||
DeinitVDisaDiffDrive();
|
DeinitVDisaDiffDrive();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InitVirtualImageDrive(void) {
|
bool InitVirtualImageDrive(void) {
|
||||||
DeinitVirtualImageDrive();
|
DeinitVirtualImageDrive();
|
||||||
return InitVGameDrive() || InitVDisaDiffDrive() || InitVKeyDbDrive();
|
return InitVGameDrive() || InitVDisaDiffDrive() /* calls InitVBDRIDrive and InitVSaveDrive */ || InitVKeyDbDrive();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CheckVirtualDrive(const char* path) {
|
bool CheckVirtualDrive(const char* path) {
|
||||||
@ -52,6 +54,8 @@ bool CheckVirtualDrive(const char* path) {
|
|||||||
return CheckVKeyDbDrive();
|
return CheckVKeyDbDrive();
|
||||||
else if (virtual_src & VRT_DISADIFF)
|
else if (virtual_src & VRT_DISADIFF)
|
||||||
return CheckVDisaDiffDrive();
|
return CheckVDisaDiffDrive();
|
||||||
|
else if (virtual_src & VRT_SAVE)
|
||||||
|
return CheckVSaveDrive();
|
||||||
return virtual_src; // this is safe for SysNAND & memory
|
return virtual_src; // this is safe for SysNAND & memory
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,6 +78,8 @@ bool ReadVirtualDir(VirtualFile* vfile, VirtualDir* vdir) {
|
|||||||
ret = ReadVVramDir(vfile, vdir);
|
ret = ReadVVramDir(vfile, vdir);
|
||||||
} else if (virtual_src & VRT_DISADIFF) {
|
} else if (virtual_src & VRT_DISADIFF) {
|
||||||
ret = ReadVDisaDiffDir(vfile, vdir);
|
ret = ReadVDisaDiffDir(vfile, vdir);
|
||||||
|
} else if (virtual_src & VRT_SAVE) {
|
||||||
|
ret = ReadVSaveDir(vfile, vdir);
|
||||||
}
|
}
|
||||||
vfile->flags |= virtual_src; // add source flag
|
vfile->flags |= virtual_src; // add source flag
|
||||||
return ret;
|
return ret;
|
||||||
@ -185,6 +191,8 @@ int ReadVirtualFile(const VirtualFile* vfile, void* buffer, u64 offset, u64 coun
|
|||||||
return ReadVVramFile(vfile, buffer, offset, count);
|
return ReadVVramFile(vfile, buffer, offset, count);
|
||||||
} else if (vfile->flags & VRT_DISADIFF) {
|
} else if (vfile->flags & VRT_DISADIFF) {
|
||||||
return ReadVDisaDiffFile(vfile, buffer, offset, count);
|
return ReadVDisaDiffFile(vfile, buffer, offset, count);
|
||||||
|
} else if (vfile->flags & VRT_SAVE) {
|
||||||
|
return ReadVSaveFile(vfile, buffer, offset, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
@ -253,5 +261,7 @@ u64 GetVirtualDriveSize(const char* path) {
|
|||||||
return GetVCartDriveSize();
|
return GetVCartDriveSize();
|
||||||
else if (virtual_src & VRT_VRAM)
|
else if (virtual_src & VRT_VRAM)
|
||||||
return GetVVramDriveSize();
|
return GetVVramDriveSize();
|
||||||
|
else if (virtual_src & VRT_SAVE)
|
||||||
|
return GetVSaveDriveSize();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,25 +14,26 @@
|
|||||||
#define VRT_CART (1UL<<8)
|
#define VRT_CART (1UL<<8)
|
||||||
#define VRT_VRAM (1UL<<9)
|
#define VRT_VRAM (1UL<<9)
|
||||||
#define VRT_DISADIFF (1UL<<10)
|
#define VRT_DISADIFF (1UL<<10)
|
||||||
|
#define VRT_SAVE (1UL<<11)
|
||||||
|
|
||||||
#define VRT_SOURCE (VRT_SYSNAND|VRT_EMUNAND|VRT_IMGNAND|VRT_XORPAD|VRT_MEMORY|VRT_GAME|VRT_BDRI|VRT_KEYDB|VRT_CART|VRT_VRAM|VRT_DISADIFF)
|
#define VRT_SOURCE (VRT_SYSNAND|VRT_EMUNAND|VRT_IMGNAND|VRT_XORPAD|VRT_MEMORY|VRT_GAME|VRT_BDRI|VRT_KEYDB|VRT_CART|VRT_VRAM|VRT_DISADIFF|VRT_SAVE)
|
||||||
|
|
||||||
#define VFLAG_DIR (1UL<<11)
|
#define VFLAG_DIR (1UL<<12)
|
||||||
#define VFLAG_ROOT (1UL<<12)
|
#define VFLAG_ROOT (1UL<<13)
|
||||||
#define VFLAG_READONLY (1UL<<13)
|
#define VFLAG_READONLY (1UL<<14)
|
||||||
#define VFLAG_DELETABLE (1UL<<14)
|
#define VFLAG_DELETABLE (1UL<<15)
|
||||||
#define VFLAG_LV3 (1UL<<15)
|
#define VFLAG_LV3 (1UL<<16)
|
||||||
|
|
||||||
|
|
||||||
#define VRT_DRIVES {'S', VRT_SYSNAND}, {'E', VRT_EMUNAND}, {'I', VRT_IMGNAND}, {'X', VRT_XORPAD }, \
|
#define VRT_DRIVES {'S', VRT_SYSNAND}, {'E', VRT_EMUNAND}, {'I', VRT_IMGNAND}, {'X', VRT_XORPAD }, \
|
||||||
{'M', VRT_MEMORY}, {'G', VRT_GAME}, {'K', VRT_KEYDB}, {'T', VRT_BDRI}, \
|
{'M', VRT_MEMORY}, {'G', VRT_GAME}, {'K', VRT_KEYDB}, {'T', VRT_BDRI}, \
|
||||||
{'C', VRT_CART}, {'V', VRT_VRAM}, {'D', VRT_DISADIFF}
|
{'C', VRT_CART}, {'V', VRT_VRAM}, {'D', VRT_DISADIFF}, {'F', VRT_SAVE}
|
||||||
|
|
||||||
// virtual file flag (subject to change):
|
// virtual file flag (subject to change):
|
||||||
// bits 0...3 : reserved for NAND virtual sources and info
|
// bits 0...3 : reserved for NAND virtual sources and info
|
||||||
// bits 4...10 : reserved for other virtual sources
|
// bits 4...11 : reserved for other virtual sources
|
||||||
// bits 11...15: reserved for external flags
|
// bits 12...16: reserved for external flags
|
||||||
// bits 16...31: reserved for internal flags (different per source, see vgame.c)
|
// bits 17...31: reserved for internal flags (different per source, see vgame.c)
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char name[32];
|
char name[32];
|
||||||
u64 offset; // must be a multiple of 0x200 (for NAND access)
|
u64 offset; // must be a multiple of 0x200 (for NAND access)
|
||||||
|
|||||||
172
arm9/source/virtual/vsaveexsv.c
Normal file
172
arm9/source/virtual/vsaveexsv.c
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
#include "disadiff.h"
|
||||||
|
#include "image.h"
|
||||||
|
#include "vdisadiff.h"
|
||||||
|
#include "vsaveexsv.h"
|
||||||
|
#include "saveexsv.h"
|
||||||
|
#include "filetype.h"
|
||||||
|
|
||||||
|
static SaveExsvFile sav = { 0 };
|
||||||
|
|
||||||
|
static char exsv_root_path[256] = { 0 };
|
||||||
|
static char exsv_file_tmp_path[256] = { 0 };
|
||||||
|
static DisaDiffRWInfo exsv_cur_file_cache = { 0 };
|
||||||
|
static u64 last_exsv_file_id = 0;
|
||||||
|
|
||||||
|
static int LoadExsvDiffFile(u64 id, u64 uniq) {
|
||||||
|
if (last_exsv_file_id == id && exsv_cur_file_cache.unique_id == uniq) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 dir = id / 126;
|
||||||
|
if (dir > 0xFFFFFFFF) // there can be no more than 0xFFFFFFFF dirs (including dir 0)
|
||||||
|
return 1;
|
||||||
|
u32 file = id % 126;
|
||||||
|
|
||||||
|
snprintf(exsv_file_tmp_path, sizeof(exsv_file_tmp_path), "%s%08lX/%08lX", exsv_root_path, (u32)dir, file);
|
||||||
|
if (GetDisaDiffRWInfo(exsv_file_tmp_path, &exsv_cur_file_cache, false) || exsv_cur_file_cache.unique_id != uniq) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
last_exsv_file_id = id;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 InitVSaveDrive(void) { // prerequisite: save mounted as virtual disa image
|
||||||
|
u64 mount_state = CheckVDisaDiffDrive();
|
||||||
|
if (!(mount_state & (SYS_DISA | SYS_DIFF))) return 0;
|
||||||
|
|
||||||
|
DeinitVSaveDrive();
|
||||||
|
|
||||||
|
if (SaveExsvFileInit(&sav) != 0) {
|
||||||
|
DeinitVSaveDrive();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sav.is_exsv) {
|
||||||
|
// ensure we're initializing extdata from a correct structure
|
||||||
|
const char *exsv_root = GetMountPath();
|
||||||
|
int pathlen = strlen(exsv_root);
|
||||||
|
|
||||||
|
if (pathlen < 20 || strcmp(&exsv_root[pathlen - 17], "00000000/00000001") != 0) {
|
||||||
|
DeinitVSaveDrive();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
strncpy(exsv_root_path, exsv_root, pathlen - 17);
|
||||||
|
exsv_root_path[pathlen - 17] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mount_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 CheckVSaveDrive(void) {
|
||||||
|
u64 mount_state = CheckVDisaDiffDrive();
|
||||||
|
return ((mount_state & (SYS_DISA | SYS_DIFF)) && sav.init_ok) ? mount_state : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeinitVSaveDrive() {
|
||||||
|
SaveExsvFileFree(&sav);
|
||||||
|
memset(exsv_root_path, 0, sizeof(exsv_root_path));
|
||||||
|
memset(exsv_file_tmp_path, 0, sizeof(exsv_file_tmp_path));
|
||||||
|
memset(&exsv_cur_file_cache, 0, sizeof(exsv_cur_file_cache));
|
||||||
|
last_exsv_file_id = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ReadVSaveDir(VirtualFile* vfile, VirtualDir* vdir) {
|
||||||
|
if (!CheckVSaveDrive())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (vdir->index == -1)
|
||||||
|
vdir->index = 0;
|
||||||
|
|
||||||
|
|
||||||
|
if (vdir->index == 0) {
|
||||||
|
u32 dir_offset = vdir->offset == 0 ? 1 : vdir->offset;
|
||||||
|
SaveDirectoryEntry *dir = &sav.dir_entries[dir_offset];
|
||||||
|
if (!dir->ent.first_file_index) {
|
||||||
|
vdir->index = 2;
|
||||||
|
} else {
|
||||||
|
vdir->index = 1;
|
||||||
|
vdir->offset = dir->ent.first_file_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vdir->index == 1) {
|
||||||
|
SaveFileEntry *fent = &sav.file_entries[vdir->offset];
|
||||||
|
vfile->offset = vdir->offset;
|
||||||
|
vfile->flags = VFLAG_READONLY;
|
||||||
|
if (sav.is_exsv) {
|
||||||
|
if (LoadExsvDiffFile(sav.exsv_extra_hdr.sub_file_base_id + vdir->offset, fent->ent.file_unique_id) != 0) {
|
||||||
|
vfile->size = 0;
|
||||||
|
} else {
|
||||||
|
vfile->size = exsv_cur_file_cache.size_ivfc_lvl4;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vfile->size = fent->ent.file_size;
|
||||||
|
}
|
||||||
|
memset(vfile->name, 0, sizeof(vfile->name));
|
||||||
|
memcpy(vfile->name, fent->ent.key.name, sizeof(fent->ent.key.name));
|
||||||
|
|
||||||
|
if (fent->ent.next_sibling_index) {
|
||||||
|
vdir->offset = fent->ent.next_sibling_index;
|
||||||
|
} else {
|
||||||
|
vdir->offset = fent->ent.key.parent_dir_idx;
|
||||||
|
vdir->index = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vdir->index == 2) {
|
||||||
|
SaveDirectoryEntry *dir = &sav.dir_entries[vdir->offset];
|
||||||
|
if (!dir->ent.first_subdir_index)
|
||||||
|
return false;
|
||||||
|
else {
|
||||||
|
vdir->index = 3;
|
||||||
|
vdir->offset = dir->ent.first_subdir_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vdir->index == 3) {
|
||||||
|
SaveDirectoryEntry *dir = &sav.dir_entries[vdir->offset];
|
||||||
|
vfile->offset = vdir->offset;
|
||||||
|
vfile->flags = VFLAG_READONLY | VFLAG_DIR;
|
||||||
|
memset(vfile->name, 0, sizeof(vfile->name));
|
||||||
|
memcpy(vfile->name, dir->ent.key.name, sizeof(dir->ent.key.name));
|
||||||
|
|
||||||
|
if (dir->ent.next_sibling_dir_index) {
|
||||||
|
vdir->offset = dir->ent.next_sibling_dir_index;
|
||||||
|
} else {
|
||||||
|
vdir->index = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ReadVSaveFile(const VirtualFile* vfile, void* buffer, u64 offset, u64 count) {
|
||||||
|
if (vfile->flags & VFLAG_DIR)
|
||||||
|
return 1;
|
||||||
|
if (sav.is_exsv) {
|
||||||
|
SaveFileEntry *fent = &sav.file_entries[vfile->offset];
|
||||||
|
if (LoadExsvDiffFile(sav.exsv_extra_hdr.sub_file_base_id + vfile->offset, fent->ent.file_unique_id) != 0)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return ReadDisaDiffIvfcLvl4(exsv_file_tmp_path, &exsv_cur_file_cache, offset, count, buffer) == count ? 0 : 1;
|
||||||
|
} else {
|
||||||
|
return SaveExsvReadFatFile(&sav, vfile->offset, buffer, (u32)offset, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 GetVSaveDriveSize(void) {
|
||||||
|
if (!CheckVSaveDrive())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (sav.is_exsv) {
|
||||||
|
return 0; // maybe implement Quota.dat reading for this at some point
|
||||||
|
} else {
|
||||||
|
return sav.pre_header.fs_image_size_blocks * sav.pre_header.fs_image_blocksize;
|
||||||
|
}
|
||||||
|
}
|
||||||
12
arm9/source/virtual/vsaveexsv.h
Normal file
12
arm9/source/virtual/vsaveexsv.h
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "virtual.h"
|
||||||
|
|
||||||
|
void DeinitVSaveDrive(void);
|
||||||
|
u64 InitVSaveDrive(void);
|
||||||
|
u64 CheckVSaveDrive(void);
|
||||||
|
|
||||||
|
bool ReadVSaveDir(VirtualFile* vfile, VirtualDir* vdir);
|
||||||
|
int ReadVSaveFile(const VirtualFile* vfile, void* buffer, u64 offset, u64 count);
|
||||||
|
|
||||||
|
u64 GetVSaveDriveSize(void);
|
||||||
@ -49,6 +49,15 @@
|
|||||||
#define align(v,a) \
|
#define align(v,a) \
|
||||||
(((v) % (a)) ? ((v) + (a) - ((v) % (a))) : (v))
|
(((v) % (a)) ? ((v) + (a) - ((v) % (a))) : (v))
|
||||||
|
|
||||||
|
#define align_pow2(v,a) \
|
||||||
|
(((v) + (a) - 1) & ~((a) - 1))
|
||||||
|
|
||||||
|
#define ceil_div(v,a) \
|
||||||
|
(((v) + (a) - 1) / (a))
|
||||||
|
|
||||||
|
#define log_2(x) \
|
||||||
|
(31 - __builtin_clz(x))
|
||||||
|
|
||||||
#define countof(x) \
|
#define countof(x) \
|
||||||
(sizeof(x) / sizeof(*(x)))
|
(sizeof(x) / sizeof(*(x)))
|
||||||
|
|
||||||
|
|||||||
@ -30,6 +30,7 @@ fs.allow = _fs.allow
|
|||||||
fs.sd_is_mounted = _fs.sd_is_mounted
|
fs.sd_is_mounted = _fs.sd_is_mounted
|
||||||
fs.sd_switch = _fs.sd_switch
|
fs.sd_switch = _fs.sd_switch
|
||||||
fs.fix_cmacs = _fs.fix_cmacs
|
fs.fix_cmacs = _fs.fix_cmacs
|
||||||
|
fs.create_dbs = _fs.create_dbs
|
||||||
fs.key_dump = _fs.key_dump
|
fs.key_dump = _fs.key_dump
|
||||||
fs.cart_dump = _fs.cart_dump
|
fs.cart_dump = _fs.cart_dump
|
||||||
|
|
||||||
@ -38,27 +39,27 @@ function os.remove(path)
|
|||||||
local success, allowed, stat, error
|
local success, allowed, stat, error
|
||||||
success, stat = pcall(fs.stat, path)
|
success, stat = pcall(fs.stat, path)
|
||||||
if not success then
|
if not success then
|
||||||
return nil, path..": No such file or directory", 2
|
return nil, path .. ": No such file or directory", 2
|
||||||
end
|
end
|
||||||
if stat.type == "dir" then
|
if stat.type == "dir" then
|
||||||
-- os.remove can remove an empty directory, so we gotta check
|
-- os.remove can remove an empty directory, so we gotta check
|
||||||
success, dir_list = pcall(fs.list_dir, path)
|
success, dir_list = pcall(fs.list_dir, path)
|
||||||
if not success then
|
if not success then
|
||||||
return nil, "Error occurred listing directory: "..dir_list, 2001
|
return nil, "Error occurred listing directory: " .. dir_list, 2001
|
||||||
end
|
end
|
||||||
if #dir_list ~= 0 then
|
if #dir_list ~= 0 then
|
||||||
return nil, path..": Directory not empty", 39
|
return nil, path .. ": Directory not empty", 39
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
allowed = fs.allow(path)
|
allowed = fs.allow(path)
|
||||||
if not allowed then
|
if not allowed then
|
||||||
return nil, path..": Operation not permitted", 1
|
return nil, path .. ": Operation not permitted", 1
|
||||||
end
|
end
|
||||||
success, error = pcall(fs.remove, path, {recursive=true})
|
success, error = pcall(fs.remove, path, { recursive = true })
|
||||||
if success then
|
if success then
|
||||||
return true
|
return true
|
||||||
else
|
else
|
||||||
return nil, "Error occurred removing item: "..error, 2001
|
return nil, "Error occurred removing item: " .. error, 2001
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -100,7 +101,7 @@ end
|
|||||||
|
|
||||||
function fs.verify_with_sha_file(path)
|
function fs.verify_with_sha_file(path)
|
||||||
local success, file_hash, sha_data, path_sha
|
local success, file_hash, sha_data, path_sha
|
||||||
path_sha = path..".sha"
|
path_sha = path .. ".sha"
|
||||||
success, sha_data = pcall(fs.read_file, path_sha, 0, 0x20)
|
success, sha_data = pcall(fs.read_file, path_sha, 0, 0x20)
|
||||||
if not success then
|
if not success then
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -825,5 +825,14 @@
|
|||||||
"VERIFY_SIGNATURES": "Verify signatures",
|
"VERIFY_SIGNATURES": "Verify signatures",
|
||||||
"STANDARD_CRYPTO": "Standard encryption",
|
"STANDARD_CRYPTO": "Standard encryption",
|
||||||
"ORIGINAL_CRYPTO": "Original encryption",
|
"ORIGINAL_CRYPTO": "Original encryption",
|
||||||
"SELECT_TYPE_OF_ENCRYPTION": "Select type of encryption"
|
"SELECT_TYPE_OF_ENCRYPTION": "Select type of encryption",
|
||||||
|
"LAB_SAVE_FILE_IMAGE": "SAVE FILE IMAGE",
|
||||||
|
"LAB_EXTDATA_IMAGE": "EXTDATA IMAGE",
|
||||||
|
"CREATE_MISSING_DB_FILES": "Create missing .db files",
|
||||||
|
"PATH_FILE_ALREADY_EXISTS_RECREATE_IT_ANYWAY": "%s\nFile already exists. Recreate it anyway?",
|
||||||
|
"CREATE_DB_FILES_N_N_N_OK_FAILED_ALREADY_EXIST": "Create .db files ok/failed/already exist %d/%d/%d",
|
||||||
|
"CREATE_DB_FILES_N_N_OK_FAILED": "Create .db files ok/failed %d/%d",
|
||||||
|
"PATH_CREATE_DB_FILE_FAILED": "%s\nCreate .db file failed",
|
||||||
|
"NO_MISSING_DB_FILES_DETECTED": "No missing .db files detected.",
|
||||||
|
"SUCCESSFULLY_CREATED_DB_FILES_FOR_DRIVE": "Successfully created .db files for drive."
|
||||||
}
|
}
|
||||||
|
|||||||
@ -284,7 +284,7 @@ The image must not be larger than 400 pixels horizontal or 240 pixels vertical.
|
|||||||
|
|
||||||
* **Arguments**
|
* **Arguments**
|
||||||
* `path` - Path to PNG file
|
* `path` - Path to PNG file
|
||||||
* **Throws**
|
* **Throws**
|
||||||
* `"Could not read (file)"` - file does not exist or there was another read error
|
* `"Could not read (file)"` - file does not exist or there was another read error
|
||||||
* `"Invalid PNG file"` - file is not a valid PNG
|
* `"Invalid PNG file"` - file is not a valid PNG
|
||||||
* `"PNG too large"` - too large horizontal or vertical, or an out-of-memory error
|
* `"PNG too large"` - too large horizontal or vertical, or an out-of-memory error
|
||||||
@ -756,6 +756,24 @@ Fix CMACs for a directory.
|
|||||||
* **Throws**
|
* **Throws**
|
||||||
* `fixcmac failed` - user denied permission, or fixing failed
|
* `fixcmac failed` - user denied permission, or fixing failed
|
||||||
|
|
||||||
|
#### fs.create_dbs
|
||||||
|
|
||||||
|
* `void fs.create_dbs(string destdrv[, table opts {bool silent, bool overwrite}])`
|
||||||
|
|
||||||
|
(Re)create potentially missing .db files (except certs.db) for the given drive.
|
||||||
|
|
||||||
|
Supported drives are:
|
||||||
|
- SysNAND/EmuNAND CTRNAND (`1:`/`4:`): `import.db`, `ticket.db`, `title.db`, `tmp_i.db`, `tmp_t.db`
|
||||||
|
- SysNAND/EmuNAND SD (`A:`/`B:`): `import.db`, `title.db`
|
||||||
|
|
||||||
|
* **Arguments**
|
||||||
|
* `destdrv` - Drive to (re)create .db files for.
|
||||||
|
* `opts` (optional) - Option flags
|
||||||
|
* `overwrite` - Overwrite existing .db files (use with caution!)
|
||||||
|
* `silent` - Do not show any messages during the process (e.g. which specific file couldn't be created, number of created/already existing files, etc.)
|
||||||
|
* **Throws**
|
||||||
|
* `createdbs failed` - user denied permission, or .db file(s) creation failed
|
||||||
|
|
||||||
#### fs.read_file
|
#### fs.read_file
|
||||||
|
|
||||||
* `string fs.read_file(string path, int offset, int/string size)`
|
* `string fs.read_file(string path, int offset, int/string size)`
|
||||||
@ -1168,9 +1186,9 @@ VERSION_LOW | 0x1 | Minor version of the MCU firmware.
|
|||||||
RESET_EVENTS | 0x2 | @ref mcu.reset_event_flags
|
RESET_EVENTS | 0x2 | @ref mcu.reset_event_flags
|
||||||
VCOM_TOP | 0x3 | Flicker/VCOM value for the top screen.
|
VCOM_TOP | 0x3 | Flicker/VCOM value for the top screen.
|
||||||
VCOM_BOTTOM | 0x4 | Flicker/VCOM value for the bottom screen.
|
VCOM_BOTTOM | 0x4 | Flicker/VCOM value for the bottom screen.
|
||||||
FIRMWARE_UPLOAD_0 | 0x5 | Firmware upload register.
|
FIRMWARE_UPLOAD_0 | 0x5 | Firmware upload register.
|
||||||
FIRMWARE_UPLOAD_1 | 0x6 | Firmware upload register.
|
FIRMWARE_UPLOAD_1 | 0x6 | Firmware upload register.
|
||||||
FIRMWARE_UPLOAD_2 | 0x7 | Firmware upload register.
|
FIRMWARE_UPLOAD_2 | 0x7 | Firmware upload register.
|
||||||
RAW_3D_SLIDER_POSITION | 0x8 | Position of the 3D slider.
|
RAW_3D_SLIDER_POSITION | 0x8 | Position of the 3D slider.
|
||||||
VOLUME_SLIDER_POSITION | 0x9 | Position of the volume slider.
|
VOLUME_SLIDER_POSITION | 0x9 | Position of the volume slider.
|
||||||
BATTERY_PCB_TEMPERATURE | 0xA | Temperature of the battery, measured on a sensor on the PCB.
|
BATTERY_PCB_TEMPERATURE | 0xA | Temperature of the battery, measured on a sensor on the PCB.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user