mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-26 04:02:11 +00:00
PFS0 mod data + RomFS start.
This commit is contained in:
parent
dccb33ab0c
commit
c6eaf3c8b6
13 changed files with 985 additions and 332 deletions
3
.github/ISSUE_TEMPLATE/bug_report.md
vendored
3
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -20,6 +20,9 @@ Steps to reproduce the behavior:
|
|||
**Screenshots**
|
||||
Add screenshots to help explain your problem.
|
||||
|
||||
**Logfile**
|
||||
If available, please upload your logfile located at `sdmc:/nxdumptool/nxdumptool.log`.
|
||||
|
||||
**Please fill the following information:**
|
||||
- Horizon OS (Switch FW) version: [e.g. 10.0.0]
|
||||
- CFW: [e.g. Atmosphère, SX OS, etc.]
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
todo:
|
||||
|
||||
hfs0 methods
|
||||
hfs0: filelist generation methods
|
||||
|
||||
nca: more data replacement methods ???
|
||||
|
||||
pfs0: filelist generation methods
|
||||
pfs0: full header aligned to 0x20 (nsp)
|
||||
|
||||
romfs: filelist generation methods
|
||||
romfs: data replacement methods
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -104,27 +104,27 @@ static bool gamecardCreateDetectionThread(void);
|
|||
static void gamecardDestroyDetectionThread(void);
|
||||
static int gamecardDetectionThreadFunc(void *arg);
|
||||
|
||||
static inline bool gamecardIsInserted(void);
|
||||
NX_INLINE bool gamecardIsInserted(void);
|
||||
|
||||
static void gamecardLoadInfo(void);
|
||||
static void gamecardFreeInfo(void);
|
||||
|
||||
static bool gamecardGetHandle(void);
|
||||
static inline void gamecardCloseHandle(void);
|
||||
NX_INLINE void gamecardCloseHandle(void);
|
||||
|
||||
static bool gamecardOpenStorageArea(u8 area);
|
||||
static bool gamecardReadStorageArea(void *out, u64 read_size, u64 offset, bool lock);
|
||||
static void gamecardCloseStorageArea(void);
|
||||
|
||||
static bool gamecardGetStorageAreasSizes(void);
|
||||
static inline u64 gamecardGetCapacityFromRomSizeValue(u8 rom_size);
|
||||
NX_INLINE u64 gamecardGetCapacityFromRomSizeValue(u8 rom_size);
|
||||
|
||||
static GameCardHashFileSystemHeader *gamecardGetHashFileSystemPartitionHeader(u8 hfs_partition_type, u32 *out_hfs_partition_idx);
|
||||
|
||||
static inline GameCardHashFileSystemEntry *gamecardGetHashFileSystemEntryByIndex(void *header, u32 idx);
|
||||
static inline char *gamecardGetHashFileSystemNameTable(void *header);
|
||||
static inline char *gamecardGetHashFileSystemEntryNameByIndex(void *header, u32 idx);
|
||||
static inline bool gamecardGetHashFileSystemEntryIndexByName(void *header, const char *name, u32 *out_idx);
|
||||
NX_INLINE GameCardHashFileSystemEntry *gamecardGetHashFileSystemEntryByIndex(void *header, u32 idx);
|
||||
NX_INLINE char *gamecardGetHashFileSystemNameTable(void *header);
|
||||
NX_INLINE char *gamecardGetHashFileSystemEntryNameByIndex(void *header, u32 idx);
|
||||
NX_INLINE bool gamecardGetHashFileSystemEntryIndexByName(void *header, const char *name, u32 *out_idx);
|
||||
|
||||
/* Service guard used to generate thread-safe initialize + exit functions. */
|
||||
/* I'm using this here even though this actually isn't a real service but who cares, it gets the job done. */
|
||||
|
@ -570,7 +570,7 @@ static int gamecardDetectionThreadFunc(void *arg)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static inline bool gamecardIsInserted(void)
|
||||
NX_INLINE bool gamecardIsInserted(void)
|
||||
{
|
||||
bool inserted = false;
|
||||
Result rc = fsDeviceOperatorIsGameCardInserted(&g_deviceOperator, &inserted);
|
||||
|
@ -695,7 +695,7 @@ static void gamecardLoadInfo(void)
|
|||
|
||||
/* Calculate the full header size for the current hash FS partition and round it to a GAMECARD_MEDIA_UNIT_SIZE bytes boundary */
|
||||
g_gameCardHfsPartitions[i].header_size = (sizeof(GameCardHashFileSystemHeader) + (partition_header.entry_count * sizeof(GameCardHashFileSystemEntry)) + partition_header.name_table_size);
|
||||
g_gameCardHfsPartitions[i].header_size = ROUND_UP(g_gameCardHfsPartitions[i].header_size, GAMECARD_MEDIA_UNIT_SIZE);
|
||||
g_gameCardHfsPartitions[i].header_size = ALIGN_UP(g_gameCardHfsPartitions[i].header_size, GAMECARD_MEDIA_UNIT_SIZE);
|
||||
|
||||
/* Allocate memory for the hash FS partition header */
|
||||
g_gameCardHfsPartitions[i].header = calloc(g_gameCardHfsPartitions[i].header_size, sizeof(u8));
|
||||
|
@ -796,7 +796,7 @@ static bool gamecardGetHandle(void)
|
|||
return true;
|
||||
}
|
||||
|
||||
static inline void gamecardCloseHandle(void)
|
||||
NX_INLINE void gamecardCloseHandle(void)
|
||||
{
|
||||
svcCloseHandle(g_gameCardHandle.value);
|
||||
g_gameCardHandle.value = 0;
|
||||
|
@ -894,8 +894,8 @@ static bool gamecardReadStorageArea(void *out, u64 read_size, u64 offset, bool l
|
|||
success = true;
|
||||
} else {
|
||||
/* Fix offset and/or size to avoid unaligned reads */
|
||||
u64 block_start_offset = ROUND_DOWN(base_offset, GAMECARD_MEDIA_UNIT_SIZE);
|
||||
u64 block_end_offset = ROUND_UP(base_offset + read_size, GAMECARD_MEDIA_UNIT_SIZE);
|
||||
u64 block_start_offset = ALIGN_DOWN(base_offset, GAMECARD_MEDIA_UNIT_SIZE);
|
||||
u64 block_end_offset = ALIGN_UP(base_offset + read_size, GAMECARD_MEDIA_UNIT_SIZE);
|
||||
u64 block_size = (block_end_offset - block_start_offset);
|
||||
|
||||
u64 data_start_offset = (base_offset - block_start_offset);
|
||||
|
@ -975,7 +975,7 @@ static bool gamecardGetStorageAreasSizes(void)
|
|||
return true;
|
||||
}
|
||||
|
||||
static inline u64 gamecardGetCapacityFromRomSizeValue(u8 rom_size)
|
||||
NX_INLINE u64 gamecardGetCapacityFromRomSizeValue(u8 rom_size)
|
||||
{
|
||||
u64 capacity = 0;
|
||||
|
||||
|
@ -1027,20 +1027,20 @@ static GameCardHashFileSystemHeader *gamecardGetHashFileSystemPartitionHeader(u8
|
|||
return fs_header;
|
||||
}
|
||||
|
||||
static inline GameCardHashFileSystemEntry *gamecardGetHashFileSystemEntryByIndex(void *header, u32 idx)
|
||||
NX_INLINE GameCardHashFileSystemEntry *gamecardGetHashFileSystemEntryByIndex(void *header, u32 idx)
|
||||
{
|
||||
if (!header || idx >= ((GameCardHashFileSystemHeader*)header)->entry_count) return NULL;
|
||||
return (GameCardHashFileSystemEntry*)((u8*)header + sizeof(GameCardHashFileSystemHeader) + (idx * sizeof(GameCardHashFileSystemEntry)));
|
||||
}
|
||||
|
||||
static inline char *gamecardGetHashFileSystemNameTable(void *header)
|
||||
NX_INLINE char *gamecardGetHashFileSystemNameTable(void *header)
|
||||
{
|
||||
GameCardHashFileSystemHeader *fs_header = (GameCardHashFileSystemHeader*)header;
|
||||
if (!fs_header || !fs_header->entry_count) return NULL;
|
||||
return ((char*)header + sizeof(GameCardHashFileSystemHeader) + (fs_header->entry_count * sizeof(GameCardHashFileSystemEntry)));
|
||||
}
|
||||
|
||||
static inline char *gamecardGetHashFileSystemEntryNameByIndex(void *header, u32 idx)
|
||||
NX_INLINE char *gamecardGetHashFileSystemEntryNameByIndex(void *header, u32 idx)
|
||||
{
|
||||
GameCardHashFileSystemEntry *fs_entry = gamecardGetHashFileSystemEntryByIndex(header, idx);
|
||||
char *name_table = gamecardGetHashFileSystemNameTable(header);
|
||||
|
@ -1048,17 +1048,17 @@ static inline char *gamecardGetHashFileSystemEntryNameByIndex(void *header, u32
|
|||
return (name_table + fs_entry->name_offset);
|
||||
}
|
||||
|
||||
static inline bool gamecardGetHashFileSystemEntryIndexByName(void *header, const char *name, u32 *out_idx)
|
||||
NX_INLINE bool gamecardGetHashFileSystemEntryIndexByName(void *header, const char *name, u32 *out_idx)
|
||||
{
|
||||
size_t name_len = 0;
|
||||
GameCardHashFileSystemEntry *fs_entry = NULL;
|
||||
GameCardHashFileSystemHeader *fs_header = (GameCardHashFileSystemHeader*)header;
|
||||
char *name_table = gamecardGetHashFileSystemNameTable(header);
|
||||
if (!fs_header || !fs_header->entry_count || !name_table || !name || !(name_len = strlen(name)) || !out_idx) return false;
|
||||
|
||||
for(u32 i = 0; i < fs_header->entry_count; i++)
|
||||
{
|
||||
GameCardHashFileSystemEntry *fs_entry = gamecardGetHashFileSystemEntryByIndex(header, i);
|
||||
if (!fs_entry) continue;
|
||||
if (!(fs_entry = gamecardGetHashFileSystemEntryByIndex(header, i))) return false;
|
||||
|
||||
if (!strncmp(name_table + fs_entry->name_offset, name, name_len))
|
||||
{
|
||||
|
|
162
source/main.c
162
source/main.c
|
@ -26,7 +26,8 @@
|
|||
#include <dirent.h>
|
||||
|
||||
#include "nca.h"
|
||||
#include "pfs0.h"
|
||||
#include "pfs.h"
|
||||
#include "rsa.h"
|
||||
|
||||
|
||||
|
||||
|
@ -91,8 +92,9 @@ int main(int argc, char *argv[])
|
|||
}
|
||||
};
|
||||
|
||||
PartitionFileSystemContext pfs0_ctx = {0};
|
||||
PartitionFileSystemEntry *pfs0_entry = NULL;
|
||||
u64 pfs_size = 0;
|
||||
PartitionFileSystemContext pfs_ctx = {0};
|
||||
PartitionFileSystemEntry *pfs_entry = NULL;
|
||||
|
||||
buf = malloc(0x400000);
|
||||
if (!buf)
|
||||
|
@ -175,67 +177,66 @@ int main(int argc, char *argv[])
|
|||
|
||||
consoleUpdate(NULL);
|
||||
|
||||
if (!pfs0InitializeContext(&pfs0_ctx, &(nca_ctx->fs_contexts[0])))
|
||||
if (!pfsInitializeContext(&pfs_ctx, &(nca_ctx->fs_contexts[0])))
|
||||
{
|
||||
printf("pfs0 initialize ctx failed\n");
|
||||
printf("pfs initialize ctx failed\n");
|
||||
goto out2;
|
||||
}
|
||||
|
||||
printf("pfs0 initialize ctx succeeded\n");
|
||||
printf("pfs initialize ctx succeeded\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
tmp_file = fopen("sdmc:/nxdt_test/pfs0_ctx.bin", "wb");
|
||||
if (pfsGetTotalDataSize(&pfs_ctx, &pfs_size))
|
||||
{
|
||||
printf("pfs size succeeded: 0x%lX\n", pfs_size);
|
||||
} else {
|
||||
printf("pfs size failed\n");
|
||||
}
|
||||
|
||||
consoleUpdate(NULL);
|
||||
|
||||
tmp_file = fopen("sdmc:/nxdt_test/pfs_ctx.bin", "wb");
|
||||
if (tmp_file)
|
||||
{
|
||||
fwrite(&pfs0_ctx, 1, sizeof(PartitionFileSystemContext), tmp_file);
|
||||
fwrite(&pfs_ctx, 1, sizeof(PartitionFileSystemContext), tmp_file);
|
||||
fclose(tmp_file);
|
||||
tmp_file = NULL;
|
||||
printf("pfs0 ctx saved\n");
|
||||
printf("pfs ctx saved\n");
|
||||
} else {
|
||||
printf("pfs0 ctx not saved\n");
|
||||
printf("pfs ctx not saved\n");
|
||||
}
|
||||
|
||||
consoleUpdate(NULL);
|
||||
|
||||
tmp_file = fopen("sdmc:/nxdt_test/pfs0_header.bin", "wb");
|
||||
tmp_file = fopen("sdmc:/nxdt_test/pfs_header.bin", "wb");
|
||||
if (tmp_file)
|
||||
{
|
||||
fwrite(pfs0_ctx.header, 1, pfs0_ctx.header_size, tmp_file);
|
||||
fwrite(pfs_ctx.header, 1, pfs_ctx.header_size, tmp_file);
|
||||
fclose(tmp_file);
|
||||
tmp_file = NULL;
|
||||
printf("pfs0 header saved\n");
|
||||
printf("pfs header saved\n");
|
||||
} else {
|
||||
printf("pfs0 header not saved\n");
|
||||
printf("pfs header not saved\n");
|
||||
}
|
||||
|
||||
consoleUpdate(NULL);
|
||||
|
||||
pfs0_entry = pfs0GetEntryByName(&pfs0_ctx, "main.npdm");
|
||||
if (!pfs0_entry)
|
||||
{
|
||||
printf("pfs0 get entry by name failed\n");
|
||||
goto out2;
|
||||
}
|
||||
|
||||
printf("pfs0 get entry by name succeeded\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
tmp_file = fopen("sdmc:/nxdt_test/main.npdm", "wb");
|
||||
tmp_file = fopen("sdmc:/nxdt_test/pfs.bin", "wb");
|
||||
if (tmp_file)
|
||||
{
|
||||
u64 blksize = 0x400000;
|
||||
u64 total = pfs0_entry->size;
|
||||
u64 total = pfs_ctx.size;
|
||||
|
||||
printf("main.npdm created. Target size -> 0x%lX\n", total);
|
||||
printf("pfs created: 0x%lX\n", total);
|
||||
consoleUpdate(NULL);
|
||||
|
||||
for(u64 curpos = 0; curpos < total; curpos += blksize)
|
||||
{
|
||||
if (blksize > (total - curpos)) blksize = (total - curpos);
|
||||
|
||||
if (!pfs0ReadEntryData(&pfs0_ctx, pfs0_entry, buf, blksize, 0))
|
||||
if (!pfsReadPartitionData(&pfs_ctx, buf, blksize, curpos))
|
||||
{
|
||||
printf("pfs0 read entry data failed\n");
|
||||
printf("pfs read partition failed\n");
|
||||
goto out2;
|
||||
}
|
||||
|
||||
|
@ -245,11 +246,110 @@ int main(int argc, char *argv[])
|
|||
fclose(tmp_file);
|
||||
tmp_file = NULL;
|
||||
|
||||
printf("pfs0 read main.npdm success\n");
|
||||
printf("pfs read partition success\n");
|
||||
} else {
|
||||
printf("pfs not created\n");
|
||||
}
|
||||
|
||||
consoleUpdate(NULL);
|
||||
|
||||
pfs_entry = pfsGetEntryByName(&pfs_ctx, "main.npdm");
|
||||
if (!pfs_entry)
|
||||
{
|
||||
printf("pfs get entry by name failed\n");
|
||||
goto out2;
|
||||
}
|
||||
|
||||
printf("pfs get entry by name succeeded\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
tmp_file = fopen("sdmc:/nxdt_test/main.npdm", "wb");
|
||||
if (tmp_file)
|
||||
{
|
||||
printf("main.npdm created. Target size -> 0x%lX\n", pfs_entry->size);
|
||||
consoleUpdate(NULL);
|
||||
|
||||
if (!pfsReadEntryData(&pfs_ctx, pfs_entry, buf, pfs_entry->size, 0))
|
||||
{
|
||||
printf("pfs read entry data failed\n");
|
||||
goto out2;
|
||||
}
|
||||
|
||||
fwrite(buf, 1, pfs_entry->size, tmp_file);
|
||||
fclose(tmp_file);
|
||||
tmp_file = NULL;
|
||||
|
||||
printf("pfs read main.npdm success\n");
|
||||
} else {
|
||||
printf("main.npdm not created\n");
|
||||
}
|
||||
|
||||
consoleUpdate(NULL);
|
||||
|
||||
u32 acid_offset = 0;
|
||||
memcpy(&acid_offset, buf + 0x78, sizeof(u32));
|
||||
|
||||
PartitionFileSystemModifiedBlockInfo pfs_block_info = {0};
|
||||
|
||||
if (pfsGenerateModifiedEntryData(&pfs_ctx, pfs_entry, rsa2048GetCustomAcidPublicKey(), RSA2048_SIGNATURE_SIZE, acid_offset + RSA2048_SIGNATURE_SIZE, &pfs_block_info))
|
||||
{
|
||||
printf("pfs mod data success | hbo: 0x%lX | hbs: 0x%lX | dbo: 0x%lX | dbs: 0x%lX\n", pfs_block_info.hash_block_offset, pfs_block_info.hash_block_size, pfs_block_info.data_block_offset, pfs_block_info.data_block_size);
|
||||
|
||||
consoleUpdate(NULL);
|
||||
|
||||
tmp_file = fopen("sdmc:/nxdt_test/pfs_mod.bin", "wb");
|
||||
if (tmp_file)
|
||||
{
|
||||
fwrite(&pfs_block_info, 1, sizeof(PartitionFileSystemModifiedBlockInfo), tmp_file);
|
||||
fclose(tmp_file);
|
||||
tmp_file = NULL;
|
||||
printf("pfs mod data saved\n");
|
||||
} else {
|
||||
printf("pfs mod data not saved\n");
|
||||
}
|
||||
|
||||
consoleUpdate(NULL);
|
||||
|
||||
tmp_file = fopen("sdmc:/nxdt_test/pfs_hash_mod.bin", "wb");
|
||||
if (tmp_file)
|
||||
{
|
||||
fwrite(pfs_block_info.hash_block, 1, pfs_block_info.hash_block_size, tmp_file);
|
||||
fclose(tmp_file);
|
||||
tmp_file = NULL;
|
||||
printf("pfs hash mod data saved\n");
|
||||
} else {
|
||||
printf("pfs hash mod data not saved\n");
|
||||
}
|
||||
|
||||
consoleUpdate(NULL);
|
||||
|
||||
tmp_file = fopen("sdmc:/nxdt_test/pfs_data_mod.bin", "wb");
|
||||
if (tmp_file)
|
||||
{
|
||||
fwrite(pfs_block_info.data_block, 1, pfs_block_info.data_block_size, tmp_file);
|
||||
fclose(tmp_file);
|
||||
tmp_file = NULL;
|
||||
printf("pfs data mod data saved\n");
|
||||
} else {
|
||||
printf("pfs data mod data not saved\n");
|
||||
}
|
||||
|
||||
consoleUpdate(NULL);
|
||||
|
||||
tmp_file = fopen("sdmc:/nxdt_test/new_nca_ctx.bin", "wb");
|
||||
if (tmp_file)
|
||||
{
|
||||
fwrite(nca_ctx, 1, sizeof(NcaContext), tmp_file);
|
||||
fclose(tmp_file);
|
||||
tmp_file = NULL;
|
||||
printf("nca ctx saved\n");
|
||||
} else {
|
||||
printf("nca ctx not saved\n");
|
||||
}
|
||||
} else {
|
||||
printf("pfs mod data failed\n");
|
||||
}
|
||||
|
||||
out2:
|
||||
while(appletMainLoop())
|
||||
{
|
||||
|
@ -260,7 +360,7 @@ out2:
|
|||
|
||||
if (tmp_file) fclose(tmp_file);
|
||||
|
||||
pfs0FreeContext(&pfs0_ctx);
|
||||
pfsFreeContext(&pfs_ctx);
|
||||
|
||||
if (serviceIsActive(&(ncm_storage.s))) ncmContentStorageClose(&ncm_storage);
|
||||
|
||||
|
|
39
source/nca.c
39
source/nca.c
|
@ -43,13 +43,13 @@ static size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const voi
|
|||
static bool ncaDecryptHeader(NcaContext *ctx);
|
||||
static bool ncaDecryptKeyArea(NcaContext *ctx);
|
||||
|
||||
static inline bool ncaCheckIfVersion0KeyAreaIsEncrypted(NcaContext *ctx);
|
||||
static inline u8 ncaGetKeyGenerationValue(NcaContext *ctx);
|
||||
static inline bool ncaCheckRightsIdAvailability(NcaContext *ctx);
|
||||
NX_INLINE bool ncaCheckIfVersion0KeyAreaIsEncrypted(NcaContext *ctx);
|
||||
NX_INLINE u8 ncaGetKeyGenerationValue(NcaContext *ctx);
|
||||
NX_INLINE bool ncaCheckRightsIdAvailability(NcaContext *ctx);
|
||||
|
||||
static inline void ncaInitializeAesCtrIv(u8 *out, const u8 *ctr, u64 offset);
|
||||
static inline void ncaUpdateAesCtrIv(u8 *ctr, u64 offset);
|
||||
static inline void ncaUpdateAesCtrExIv(u8 *ctr, u32 ctr_val, u64 offset);
|
||||
NX_INLINE void ncaInitializeAesCtrIv(u8 *out, const u8 *ctr, u64 offset);
|
||||
NX_INLINE void ncaUpdateAesCtrIv(u8 *ctr, u64 offset);
|
||||
NX_INLINE void ncaUpdateAesCtrExIv(u8 *ctr, u32 ctr_val, u64 offset);
|
||||
|
||||
static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, bool lock);
|
||||
|
||||
|
@ -298,11 +298,11 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, NcmContentStorage *ncm
|
|||
{
|
||||
switch(out->fs_contexts[i].section_num)
|
||||
{
|
||||
case 0: /* ExeFS PFS0 */
|
||||
case 0: /* ExeFS Partition FS */
|
||||
case 1: /* RomFS */
|
||||
out->fs_contexts[i].encryption_type = NcaEncryptionType_AesCtr;
|
||||
break;
|
||||
case 2: /* Logo PFS0 */
|
||||
case 2: /* Logo Partition FS */
|
||||
out->fs_contexts[i].encryption_type = NcaEncryptionType_None;
|
||||
break;
|
||||
default:
|
||||
|
@ -392,7 +392,7 @@ bool ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 of
|
|||
return _ncaReadFsSection(ctx, out, read_size, offset, true);
|
||||
}
|
||||
|
||||
void *ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset)
|
||||
void *ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset)
|
||||
{
|
||||
mutexLock(&g_ncaCryptoBufferMutex);
|
||||
|
||||
|
@ -466,8 +466,8 @@ void *ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, void *data, u
|
|||
}
|
||||
|
||||
/* Calculate block offsets and size */
|
||||
block_start_offset = ROUND_DOWN(data_offset, (ctx->encryption_type == NcaEncryptionType_AesXts || ctx->encryption_type == NcaEncryptionType_Nca0) ? NCA_AES_XTS_SECTOR_SIZE : AES_BLOCK_SIZE);
|
||||
block_end_offset = ROUND_UP(data_offset + data_size, (ctx->encryption_type == NcaEncryptionType_AesXts || ctx->encryption_type == NcaEncryptionType_Nca0) ? NCA_AES_XTS_SECTOR_SIZE : AES_BLOCK_SIZE);
|
||||
block_start_offset = ALIGN_DOWN(data_offset, (ctx->encryption_type == NcaEncryptionType_AesXts || ctx->encryption_type == NcaEncryptionType_Nca0) ? NCA_AES_XTS_SECTOR_SIZE : AES_BLOCK_SIZE);
|
||||
block_end_offset = ALIGN_UP(data_offset + data_size, (ctx->encryption_type == NcaEncryptionType_AesXts || ctx->encryption_type == NcaEncryptionType_Nca0) ? NCA_AES_XTS_SECTOR_SIZE : AES_BLOCK_SIZE);
|
||||
block_size = (block_end_offset - block_start_offset);
|
||||
|
||||
plain_chunk_offset = (data_offset - block_start_offset);
|
||||
|
@ -698,25 +698,24 @@ static bool ncaDecryptKeyArea(NcaContext *ctx)
|
|||
return true;
|
||||
}
|
||||
|
||||
static inline bool ncaCheckIfVersion0KeyAreaIsEncrypted(NcaContext *ctx)
|
||||
NX_INLINE bool ncaCheckIfVersion0KeyAreaIsEncrypted(NcaContext *ctx)
|
||||
{
|
||||
if (!ctx || ctx->format_version != NcaVersion_Nca0) return false;
|
||||
|
||||
u8 nca0_key_area_hash[SHA256_HASH_SIZE] = {0};
|
||||
sha256CalculateHash(nca0_key_area_hash, ctx->header.encrypted_keys, 0x40);
|
||||
|
||||
if (!memcmp(nca0_key_area_hash, g_nca0KeyAreaHash, SHA256_HASH_SIZE)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline u8 ncaGetKeyGenerationValue(NcaContext *ctx)
|
||||
NX_INLINE u8 ncaGetKeyGenerationValue(NcaContext *ctx)
|
||||
{
|
||||
if (!ctx) return 0;
|
||||
return (ctx->header.key_generation > ctx->header.key_generation_old ? ctx->header.key_generation : ctx->header.key_generation_old);
|
||||
}
|
||||
|
||||
static inline bool ncaCheckRightsIdAvailability(NcaContext *ctx)
|
||||
NX_INLINE bool ncaCheckRightsIdAvailability(NcaContext *ctx)
|
||||
{
|
||||
if (!ctx) return false;
|
||||
|
||||
|
@ -734,7 +733,7 @@ static inline bool ncaCheckRightsIdAvailability(NcaContext *ctx)
|
|||
return rights_id_available;
|
||||
}
|
||||
|
||||
static inline void ncaInitializeAesCtrIv(u8 *out, const u8 *ctr, u64 offset)
|
||||
NX_INLINE void ncaInitializeAesCtrIv(u8 *out, const u8 *ctr, u64 offset)
|
||||
{
|
||||
if (!out || !ctr) return;
|
||||
|
||||
|
@ -748,7 +747,7 @@ static inline void ncaInitializeAesCtrIv(u8 *out, const u8 *ctr, u64 offset)
|
|||
}
|
||||
}
|
||||
|
||||
static inline void ncaUpdateAesCtrIv(u8 *ctr, u64 offset)
|
||||
NX_INLINE void ncaUpdateAesCtrIv(u8 *ctr, u64 offset)
|
||||
{
|
||||
if (!ctr) return;
|
||||
|
||||
|
@ -761,7 +760,7 @@ static inline void ncaUpdateAesCtrIv(u8 *ctr, u64 offset)
|
|||
}
|
||||
}
|
||||
|
||||
static inline void ncaUpdateAesCtrExIv(u8 *ctr, u32 ctr_val, u64 offset)
|
||||
NX_INLINE void ncaUpdateAesCtrExIv(u8 *ctr, u32 ctr_val, u64 offset)
|
||||
{
|
||||
if (!ctr) return;
|
||||
|
||||
|
@ -853,8 +852,8 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size
|
|||
}
|
||||
|
||||
/* Calculate offsets and block sizes */
|
||||
block_start_offset = ROUND_DOWN(content_offset, (ctx->encryption_type == NcaEncryptionType_AesXts || ctx->encryption_type == NcaEncryptionType_Nca0) ? NCA_AES_XTS_SECTOR_SIZE : AES_BLOCK_SIZE);
|
||||
block_end_offset = ROUND_UP(content_offset + read_size, (ctx->encryption_type == NcaEncryptionType_AesXts || ctx->encryption_type == NcaEncryptionType_Nca0) ? NCA_AES_XTS_SECTOR_SIZE : AES_BLOCK_SIZE);
|
||||
block_start_offset = ALIGN_DOWN(content_offset, (ctx->encryption_type == NcaEncryptionType_AesXts || ctx->encryption_type == NcaEncryptionType_Nca0) ? NCA_AES_XTS_SECTOR_SIZE : AES_BLOCK_SIZE);
|
||||
block_end_offset = ALIGN_UP(content_offset + read_size, (ctx->encryption_type == NcaEncryptionType_AesXts || ctx->encryption_type == NcaEncryptionType_Nca0) ? NCA_AES_XTS_SECTOR_SIZE : AES_BLOCK_SIZE);
|
||||
block_size = (block_end_offset - block_start_offset);
|
||||
|
||||
data_start_offset = (content_offset - block_start_offset);
|
||||
|
|
47
source/nca.h
47
source/nca.h
|
@ -31,7 +31,11 @@
|
|||
#define NCA_NCA2_MAGIC 0x4E434132 /* "NCA2" */
|
||||
#define NCA_NCA3_MAGIC 0x4E434133 /* "NCA3" */
|
||||
|
||||
#define NCA_HIERARCHICAL_SHA256_LAYER_COUNT 2
|
||||
|
||||
#define NCA_IVFC_MAGIC 0x49564643 /* "IVFC" */
|
||||
#define NCA_IVFC_HASH_DATA_LAYER_COUNT 5
|
||||
#define NCA_IVFC_BLOCK_SIZE(x) (1 << (x))
|
||||
|
||||
#define NCA_BKTR_MAGIC 0x424B5452 /* "BKTR" */
|
||||
|
||||
|
@ -41,8 +45,6 @@
|
|||
#define NCA_AES_XTS_SECTOR_SIZE 0x200
|
||||
#define NCA_NCA0_FS_HEADER_AES_XTS_SECTOR(x) (((x) - NCA_HEADER_LENGTH) >> 9)
|
||||
|
||||
#define NCA_IVFC_BLOCK_SIZE(x) (1 << (x))
|
||||
|
||||
typedef enum {
|
||||
NcaDistributionType_Download = 0,
|
||||
NcaDistributionType_GameCard = 1
|
||||
|
@ -145,7 +147,7 @@ typedef struct {
|
|||
u32 version;
|
||||
u32 master_hash_size;
|
||||
u32 layer_count;
|
||||
NcaHierarchicalIntegrityLayerInfo hash_data_layer_info[5];
|
||||
NcaHierarchicalIntegrityLayerInfo hash_data_layer_info[NCA_IVFC_HASH_DATA_LAYER_COUNT];
|
||||
NcaHierarchicalIntegrityLayerInfo hash_target_layer_info;
|
||||
u8 signature_salt[0x20];
|
||||
u8 master_hash[0x20];
|
||||
|
@ -310,7 +312,7 @@ bool ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 of
|
|||
/// Input offset must be relative to the start of the NCA FS section.
|
||||
/// Output size and offset are guaranteed to be aligned to the AES sector size used by the encryption type from the FS section.
|
||||
/// Output offset is relative to the start of the NCA content file, making it easier to use the output encrypted block to replace data in-place while writing a NCA.
|
||||
void *ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset);
|
||||
void *ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset);
|
||||
|
||||
|
||||
|
||||
|
@ -325,30 +327,59 @@ bool ncaEncryptHeader(NcaContext *ctx);
|
|||
|
||||
/// Miscellanous functions.
|
||||
|
||||
static inline void ncaConvertNcmContentSizeToU64(const u8 *size, u64 *out)
|
||||
NX_INLINE void ncaConvertNcmContentSizeToU64(const u8 *size, u64 *out)
|
||||
{
|
||||
if (!size || !out) return;
|
||||
*out = 0;
|
||||
memcpy(out, size, 6);
|
||||
}
|
||||
|
||||
static inline void ncaConvertU64ToNcmContentSize(const u64 *size, u8 *out)
|
||||
NX_INLINE void ncaConvertU64ToNcmContentSize(const u64 *size, u8 *out)
|
||||
{
|
||||
if (size && out) memcpy(out, size, 6);
|
||||
}
|
||||
|
||||
static inline void ncaSetDownloadDistributionType(NcaContext *ctx)
|
||||
NX_INLINE void ncaSetDownloadDistributionType(NcaContext *ctx)
|
||||
{
|
||||
if (!ctx || ctx->header.distribution_type == NcaDistributionType_Download) return;
|
||||
ctx->header.distribution_type = NcaDistributionType_Download;
|
||||
ctx->dirty_header = true;
|
||||
}
|
||||
|
||||
static inline void ncaWipeRightsId(NcaContext *ctx)
|
||||
NX_INLINE void ncaWipeRightsId(NcaContext *ctx)
|
||||
{
|
||||
if (!ctx || !ctx->rights_id_available) return;
|
||||
memset(&(ctx->header.rights_id), 0, sizeof(FsRightsId));
|
||||
ctx->dirty_header = true;
|
||||
}
|
||||
|
||||
NX_INLINE bool ncaValidateHierarchicalSha256Offsets(NcaHierarchicalSha256 *hierarchical_sha256, u64 section_size)
|
||||
{
|
||||
if (!hierarchical_sha256 || !section_size || !hierarchical_sha256->hash_block_size || hierarchical_sha256->layer_count != NCA_HIERARCHICAL_SHA256_LAYER_COUNT) return false;
|
||||
|
||||
/* Validate layer offsets and sizes */
|
||||
for(u8 i = 0; i < NCA_HIERARCHICAL_SHA256_LAYER_COUNT; i++)
|
||||
{
|
||||
NcaHierarchicalSha256LayerInfo *layer_info = (i == 0 ? &(hierarchical_sha256->hash_data_layer_info) : &(hierarchical_sha256->hash_target_layer_info));
|
||||
if (layer_info->offset >= section_size || !layer_info->size || (layer_info->offset + layer_info->size) > section_size) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
NX_INLINE bool ncaValidateHierarchicalIntegrityOffsets(NcaHierarchicalIntegrity *hierarchical_integrity, u64 section_size)
|
||||
{
|
||||
if (!hierarchical_integrity || !section_size || __builtin_bswap32(hierarchical_integrity->magic) != NCA_IVFC_MAGIC || !hierarchical_integrity->master_hash_size || \
|
||||
hierarchical_integrity->layer_count != NCA_IVFC_HASH_DATA_LAYER_COUNT) return false;
|
||||
|
||||
/* Validate layer offsets and sizes */
|
||||
for(u8 i = 0; i < (NCA_IVFC_HASH_DATA_LAYER_COUNT + 1); i++)
|
||||
{
|
||||
NcaHierarchicalIntegrityLayerInfo *layer_info = (i < NCA_IVFC_HASH_DATA_LAYER_COUNT ? &(hierarchical_integrity->hash_data_layer_info[i]) : &(hierarchical_integrity->hash_target_layer_info));
|
||||
if (layer_info->offset >= section_size || !layer_info->size || !layer_info->block_size || (layer_info->offset + layer_info->size) > section_size) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif /* __NCA_H__ */
|
||||
|
|
240
source/pfs.c
Normal file
240
source/pfs.c
Normal file
|
@ -0,0 +1,240 @@
|
|||
/*
|
||||
* Copyright (c) 2020 DarkMatterCore
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "pfs.h"
|
||||
#include "utils.h"
|
||||
|
||||
bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *nca_fs_ctx)
|
||||
{
|
||||
if (!out || !nca_fs_ctx || nca_fs_ctx->section_type != NcaFsSectionType_PartitionFs || !nca_fs_ctx->header || nca_fs_ctx->header->fs_type != NcaFsType_PartitionFs || \
|
||||
nca_fs_ctx->header->hash_type != NcaHashType_HierarchicalSha256)
|
||||
{
|
||||
LOGFILE("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Fill context */
|
||||
out->nca_fs_ctx = nca_fs_ctx;
|
||||
out->hash_info = &(nca_fs_ctx->header->hash_info.hierarchical_sha256);
|
||||
out->offset = 0;
|
||||
out->size = 0;
|
||||
out->is_exefs = false;
|
||||
out->header_size = 0;
|
||||
out->header = NULL;
|
||||
|
||||
if (!ncaValidateHierarchicalSha256Offsets(out->hash_info, nca_fs_ctx->section_size))
|
||||
{
|
||||
LOGFILE("Invalid HierarchicalSha256 block!");
|
||||
return false;
|
||||
}
|
||||
|
||||
out->offset = out->hash_info->hash_target_layer_info.offset;
|
||||
out->size = out->hash_info->hash_target_layer_info.size;
|
||||
|
||||
/* Read partial PFS header */
|
||||
u32 magic = 0;
|
||||
PartitionFileSystemHeader pfs_header = {0};
|
||||
PartitionFileSystemEntry *main_npdm_entry = NULL;
|
||||
|
||||
if (!ncaReadFsSection(nca_fs_ctx, &pfs_header, sizeof(PartitionFileSystemHeader), out->offset))
|
||||
{
|
||||
LOGFILE("Failed to read partial partition FS header!");
|
||||
return false;
|
||||
}
|
||||
|
||||
magic = __builtin_bswap32(pfs_header.magic);
|
||||
if (magic != PFS0_MAGIC)
|
||||
{
|
||||
LOGFILE("Invalid partition FS magic word! (0x%08X)", magic);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!pfs_header.entry_count || !pfs_header.name_table_size)
|
||||
{
|
||||
LOGFILE("Invalid partition FS entry count / name table size!");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Calculate full partition FS header size */
|
||||
out->header_size = (sizeof(PartitionFileSystemHeader) + (pfs_header.entry_count * sizeof(PartitionFileSystemEntry)) + pfs_header.name_table_size);
|
||||
|
||||
/* Allocate memory for the full partition FS header */
|
||||
out->header = calloc(out->header_size, sizeof(u8));
|
||||
if (!out->header)
|
||||
{
|
||||
LOGFILE("Unable to allocate 0x%lX bytes buffer for the full partition FS header!", out->header_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Read full partition FS header */
|
||||
if (!ncaReadFsSection(nca_fs_ctx, out->header, out->header_size, out->offset))
|
||||
{
|
||||
LOGFILE("Failed to read full partition FS header!");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check if we're dealing with an ExeFS section */
|
||||
if ((main_npdm_entry = pfsGetEntryByName(out, "main.npdm")) != NULL && pfsReadEntryData(out, main_npdm_entry, &magic, sizeof(u32), 0) && \
|
||||
__builtin_bswap32(magic) == NPDM_META_MAGIC) out->is_exefs = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pfsReadPartitionData(PartitionFileSystemContext *ctx, void *out, u64 read_size, u64 offset)
|
||||
{
|
||||
if (!ctx || !ctx->nca_fs_ctx || !ctx->size || !out || !read_size || offset >= ctx->size || (offset + read_size) > ctx->size)
|
||||
{
|
||||
LOGFILE("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Read partition data */
|
||||
if (!ncaReadFsSection(ctx->nca_fs_ctx, out, read_size, ctx->offset + offset))
|
||||
{
|
||||
LOGFILE("Failed to read partition FS data!");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pfsReadEntryData(PartitionFileSystemContext *ctx, PartitionFileSystemEntry *fs_entry, void *out, u64 read_size, u64 offset)
|
||||
{
|
||||
if (!ctx || !fs_entry || fs_entry->offset >= ctx->size || !fs_entry->size || (fs_entry->offset + fs_entry->size) > ctx->size || !out || !read_size || offset >= fs_entry->size || \
|
||||
(offset + read_size) > fs_entry->size)
|
||||
{
|
||||
LOGFILE("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Read entry data */
|
||||
if (!pfsReadPartitionData(ctx, out, read_size, ctx->header_size + fs_entry->offset + offset))
|
||||
{
|
||||
LOGFILE("Failed to read partition FS entry data!");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pfsGenerateModifiedEntryData(PartitionFileSystemContext *ctx, PartitionFileSystemEntry *fs_entry, const void *data, u64 data_size, u64 data_offset, PartitionFileSystemModifiedBlockInfo *out)
|
||||
{
|
||||
NcaContext *nca_ctx = NULL;
|
||||
|
||||
if (!ctx || !ctx->nca_fs_ctx || !(nca_ctx = (NcaContext*)ctx->nca_fs_ctx->nca_ctx) || !ctx->hash_info || !ctx->header_size || !ctx->header || !fs_entry || fs_entry->offset >= ctx->size || \
|
||||
!fs_entry->size || (fs_entry->offset + fs_entry->size) > ctx->size || !data || !data_size || data_offset >= fs_entry->size || (data_offset + data_size) > fs_entry->size || !out)
|
||||
{
|
||||
LOGFILE("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Calculate required offsets and sizes */
|
||||
u64 partition_offset = (ctx->header_size + fs_entry->offset + data_offset);
|
||||
u64 block_size = ctx->hash_info->hash_block_size;
|
||||
|
||||
u64 hash_table_offset = ctx->hash_info->hash_data_layer_info.offset;
|
||||
u64 hash_table_size = ctx->hash_info->hash_data_layer_info.size;
|
||||
|
||||
u64 hash_block_start_offset = ((partition_offset / block_size) * SHA256_HASH_SIZE);
|
||||
u64 hash_block_end_offset = (((partition_offset + data_size) / block_size) * SHA256_HASH_SIZE);
|
||||
u64 hash_block_size = (hash_block_end_offset != hash_block_start_offset ? (hash_block_end_offset - hash_block_start_offset) : SHA256_HASH_SIZE);
|
||||
|
||||
u64 data_block_start_offset = (ctx->offset + ALIGN_DOWN(partition_offset, block_size));
|
||||
u64 data_block_end_offset = (ctx->offset + ALIGN_UP(partition_offset + data_size, block_size));
|
||||
u64 data_block_size = (data_block_end_offset - data_block_start_offset);
|
||||
|
||||
u64 block_count = (hash_block_size / SHA256_HASH_SIZE);
|
||||
u64 new_data_offset = (partition_offset - ALIGN_DOWN(partition_offset, block_size));
|
||||
|
||||
u8 *hash_table = NULL, *data_block = NULL;
|
||||
|
||||
bool success = false;
|
||||
|
||||
/* Allocate memory for the full hash table */
|
||||
hash_table = malloc(hash_table_size);
|
||||
if (!hash_table)
|
||||
{
|
||||
LOGFILE("Unable to allocate 0x%lX bytes buffer for the full partition FS hash table!", hash_table_size);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Read full hash table */
|
||||
if (!ncaReadFsSection(ctx->nca_fs_ctx, hash_table, hash_table_size, hash_table_offset))
|
||||
{
|
||||
LOGFILE("Failed to read full partition FS hash table!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Allocate memory for the modified data block */
|
||||
data_block = malloc(data_block_size);
|
||||
if (!data_block)
|
||||
{
|
||||
LOGFILE("Unable to allocate 0x%lX bytes buffer for the modified partition FS data block!", data_block_size);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Read data block */
|
||||
if (!ncaReadFsSection(ctx->nca_fs_ctx, data_block, data_block_size, data_block_start_offset))
|
||||
{
|
||||
LOGFILE("Failed to read partition FS data block!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Replace data */
|
||||
memcpy(data_block + new_data_offset, data, data_size);
|
||||
|
||||
/* Recalculate hashes */
|
||||
for(u64 i = 0; i < block_count; i++) sha256CalculateHash(hash_table + hash_block_start_offset + (i * SHA256_HASH_SIZE), data_block + (i * block_size), block_size);
|
||||
|
||||
/* Reencrypt hash block */
|
||||
out->hash_block = ncaGenerateEncryptedFsSectionBlock(ctx->nca_fs_ctx, hash_table + hash_block_start_offset, hash_block_size, hash_table_offset + hash_block_start_offset, \
|
||||
&(out->hash_block_size), &(out->hash_block_offset));
|
||||
if (!out->hash_block)
|
||||
{
|
||||
LOGFILE("Failed to generate encrypted partition FS hash block!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Reencrypt data block */
|
||||
out->data_block = ncaGenerateEncryptedFsSectionBlock(ctx->nca_fs_ctx, data_block, data_block_size, data_block_start_offset, &(out->data_block_size), &(out->data_block_offset));
|
||||
if (!out->data_block)
|
||||
{
|
||||
LOGFILE("Failed to generate encrypted partition FS data block!");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Recalculate master hash from hash info block */
|
||||
sha256CalculateHash(ctx->hash_info->master_hash, hash_table, hash_table_size);
|
||||
|
||||
/* Recalculate FS header hash */
|
||||
sha256CalculateHash(nca_ctx->header.fs_hashes[ctx->nca_fs_ctx->section_num].hash, ctx->header, sizeof(NcaFsHeader));
|
||||
|
||||
/* Enable the 'dirty_header' flag */
|
||||
nca_ctx->dirty_header = true;
|
||||
|
||||
success = true;
|
||||
|
||||
exit:
|
||||
if (data_block) free(data_block);
|
||||
|
||||
if (hash_table) free(hash_table);
|
||||
|
||||
return success;
|
||||
}
|
159
source/pfs.h
Normal file
159
source/pfs.h
Normal file
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* Copyright (c) 2020 DarkMatterCore
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef __PFS_H__
|
||||
#define __PFS_H__
|
||||
|
||||
#include <switch.h>
|
||||
#include "nca.h"
|
||||
|
||||
#define PFS0_MAGIC 0x50465330 /* "PFS0" */
|
||||
|
||||
typedef struct {
|
||||
u32 magic; ///< "PFS0".
|
||||
u32 entry_count;
|
||||
u32 name_table_size;
|
||||
u8 reserved[0x4];
|
||||
} PartitionFileSystemHeader;
|
||||
|
||||
typedef struct {
|
||||
u64 offset;
|
||||
u64 size;
|
||||
u32 name_offset;
|
||||
u8 reserved[0x4];
|
||||
} PartitionFileSystemEntry;
|
||||
|
||||
typedef struct {
|
||||
NcaFsSectionContext *nca_fs_ctx; ///< Used to read NCA FS section data.
|
||||
NcaHierarchicalSha256 *hash_info; ///< Hash table information.
|
||||
u64 offset; ///< Partition offset (relative to the start of the NCA FS section).
|
||||
u64 size; ///< Partition size.
|
||||
bool is_exefs; ///< ExeFS flag.
|
||||
u64 header_size; ///< Full header size.
|
||||
u8 *header; ///< PartitionFileSystemHeader + (PartitionFileSystemEntry * entry_count) + Name Table.
|
||||
} PartitionFileSystemContext;
|
||||
|
||||
typedef struct {
|
||||
u64 hash_block_offset; ///< New hash block offset (relative to the start of the NCA content file).
|
||||
u64 hash_block_size; ///< New hash block size.
|
||||
u8 *hash_block; ///< New hash block contents.
|
||||
u64 data_block_offset; ///< New data block offset.
|
||||
u64 data_block_size; ///< New data block size.
|
||||
u8 *data_block; ///< New data block contents.
|
||||
} PartitionFileSystemModifiedBlockInfo;
|
||||
|
||||
/// Initializes a partition FS context.
|
||||
bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *nca_fs_ctx);
|
||||
|
||||
/// Cleanups a previously initialized partition FS context.
|
||||
NX_INLINE void pfsFreeContext(PartitionFileSystemContext *ctx)
|
||||
{
|
||||
if (!ctx) return;
|
||||
if (ctx->header) free(ctx->header);
|
||||
memset(ctx, 0, sizeof(PartitionFileSystemContext));
|
||||
}
|
||||
|
||||
/// Reads raw partition data using a partition FS context.
|
||||
bool pfsReadPartitionData(PartitionFileSystemContext *ctx, void *out, u64 read_size, u64 offset);
|
||||
|
||||
/// Reads data from a previously retrieved PartitionFileSystemEntry using a partition FS context.
|
||||
bool pfsReadEntryData(PartitionFileSystemContext *ctx, PartitionFileSystemEntry *fs_entry, void *out, u64 read_size, u64 offset);
|
||||
|
||||
/// Generates modified + encrypted hash and data blocks using a partition FS context + entry information. Both blocks are ready to be used to replace NCA content data during writing operations.
|
||||
/// Input offset must be relative to the start of the partition FS entry data.
|
||||
/// Bear in mind that this function recalculates both the NcaHashInfo block master hash and the NCA FS header hash from the NCA header, and enables the 'dirty_header' flag from the NCA context.
|
||||
/// As such, this function is only capable of modifying a single file from a partition FS in a NCA content file.
|
||||
bool pfsGenerateModifiedEntryData(PartitionFileSystemContext *ctx, PartitionFileSystemEntry *fs_entry, const void *data, u64 data_size, u64 data_offset, PartitionFileSystemModifiedBlockInfo *out);
|
||||
|
||||
/// Miscellaneous functions.
|
||||
|
||||
NX_INLINE u32 pfsGetEntryCount(PartitionFileSystemContext *ctx)
|
||||
{
|
||||
if (!ctx || !ctx->header_size || !ctx->header) return 0;
|
||||
return ((PartitionFileSystemHeader*)ctx->header)->entry_count;
|
||||
}
|
||||
|
||||
NX_INLINE PartitionFileSystemEntry *pfsGetEntryByIndex(PartitionFileSystemContext *ctx, u32 idx)
|
||||
{
|
||||
if (idx >= pfsGetEntryCount(ctx)) return NULL;
|
||||
return (PartitionFileSystemEntry*)(ctx->header + sizeof(PartitionFileSystemHeader) + (idx * sizeof(PartitionFileSystemEntry)));
|
||||
}
|
||||
|
||||
NX_INLINE char *pfsGetNameTable(PartitionFileSystemContext *ctx)
|
||||
{
|
||||
u32 entry_count = pfsGetEntryCount(ctx);
|
||||
if (!entry_count) return NULL;
|
||||
return (char*)(ctx->header + sizeof(PartitionFileSystemHeader) + (entry_count * sizeof(PartitionFileSystemEntry)));
|
||||
}
|
||||
|
||||
NX_INLINE char *pfsGetEntryNameByIndex(PartitionFileSystemContext *ctx, u32 idx)
|
||||
{
|
||||
PartitionFileSystemEntry *fs_entry = pfsGetEntryByIndex(ctx, idx);
|
||||
char *name_table = pfsGetNameTable(ctx);
|
||||
if (!fs_entry || !name_table) return NULL;
|
||||
return (name_table + fs_entry->name_offset);
|
||||
}
|
||||
|
||||
NX_INLINE bool pfsGetEntryIndexByName(PartitionFileSystemContext *ctx, const char *name, u32 *out_idx)
|
||||
{
|
||||
size_t name_len = 0;
|
||||
PartitionFileSystemEntry *fs_entry = NULL;
|
||||
u32 entry_count = pfsGetEntryCount(ctx);
|
||||
char *name_table = pfsGetNameTable(ctx);
|
||||
if (!entry_count || !name_table || !name || !(name_len = strlen(name)) || !out_idx) return false;
|
||||
|
||||
for(u32 i = 0; i < entry_count; i++)
|
||||
{
|
||||
if (!(fs_entry = pfsGetEntryByIndex(ctx, i))) return false;
|
||||
|
||||
if (!strncmp(name_table + fs_entry->name_offset, name, name_len))
|
||||
{
|
||||
*out_idx = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
NX_INLINE PartitionFileSystemEntry *pfsGetEntryByName(PartitionFileSystemContext *ctx, const char *name)
|
||||
{
|
||||
u32 idx = 0;
|
||||
if (!pfsGetEntryIndexByName(ctx, name, &idx)) return NULL;
|
||||
return pfsGetEntryByIndex(ctx, idx);
|
||||
}
|
||||
|
||||
NX_INLINE bool pfsGetTotalDataSize(PartitionFileSystemContext *ctx, u64 *out_size)
|
||||
{
|
||||
u64 total_size = 0;
|
||||
u32 entry_count = pfsGetEntryCount(ctx);
|
||||
PartitionFileSystemEntry *fs_entry = NULL;
|
||||
if (!entry_count || !out_size) return false;
|
||||
|
||||
for(u32 i = 0; i < entry_count; i++)
|
||||
{
|
||||
if (!(fs_entry = pfsGetEntryByIndex(ctx, i))) return false;
|
||||
total_size += fs_entry->size;
|
||||
}
|
||||
|
||||
*out_size = total_size;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif /* __PFS_H__ */
|
127
source/pfs0.c
127
source/pfs0.c
|
@ -1,127 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020 DarkMatterCore
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "pfs0.h"
|
||||
#include "utils.h"
|
||||
|
||||
#define PFS0_NCA_FS_HEADER_LAYER_COUNT 2
|
||||
|
||||
#define NPDM_META_MAGIC 0x4D455441 /* "META" */
|
||||
|
||||
bool pfs0InitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *nca_fs_ctx)
|
||||
{
|
||||
if (!out || !nca_fs_ctx || nca_fs_ctx->section_type != NcaFsSectionType_PartitionFs || !nca_fs_ctx->header || nca_fs_ctx->header->fs_type != NcaFsType_PartitionFs || \
|
||||
nca_fs_ctx->header->hash_type != NcaHashType_HierarchicalSha256)
|
||||
{
|
||||
LOGFILE("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Fill context */
|
||||
out->nca_fs_ctx = nca_fs_ctx;
|
||||
out->hash_info = &(nca_fs_ctx->header->hash_info.hierarchical_sha256);
|
||||
out->offset = 0;
|
||||
out->size = 0;
|
||||
out->is_exefs = false;
|
||||
out->header_size = 0;
|
||||
out->header = NULL;
|
||||
|
||||
if (!out->hash_info->hash_block_size || out->hash_info->layer_count != PFS0_NCA_FS_HEADER_LAYER_COUNT || out->hash_info->hash_data_layer_info.offset >= out->nca_fs_ctx->section_size || \
|
||||
!out->hash_info->hash_data_layer_info.size || (out->hash_info->hash_data_layer_info.offset + out->hash_info->hash_data_layer_info.size) > out->nca_fs_ctx->section_size || \
|
||||
out->hash_info->hash_target_layer_info.offset >= out->nca_fs_ctx->section_size || !out->hash_info->hash_target_layer_info.size || \
|
||||
(out->hash_info->hash_target_layer_info.offset + out->hash_info->hash_target_layer_info.size) > out->nca_fs_ctx->section_size)
|
||||
{
|
||||
LOGFILE("Invalid HierarchicalSha256 block!");
|
||||
return false;
|
||||
}
|
||||
|
||||
out->offset = out->hash_info->hash_target_layer_info.offset;
|
||||
out->size = out->hash_info->hash_target_layer_info.size;
|
||||
|
||||
/* Read partial PFS0 header */
|
||||
u32 magic = 0;
|
||||
PartitionFileSystemHeader pfs0_header = {0};
|
||||
PartitionFileSystemEntry *main_npdm_entry = NULL;
|
||||
|
||||
if (!ncaReadFsSection(nca_fs_ctx, &pfs0_header, sizeof(PartitionFileSystemHeader), out->offset))
|
||||
{
|
||||
LOGFILE("Failed to read partial PFS0 header!");
|
||||
return false;
|
||||
}
|
||||
|
||||
magic = __builtin_bswap32(pfs0_header.magic);
|
||||
if (magic != PFS0_MAGIC)
|
||||
{
|
||||
LOGFILE("Invalid PFS0 magic word! (0x%08X)", magic);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!pfs0_header.entry_count || !pfs0_header.name_table_size)
|
||||
{
|
||||
LOGFILE("Invalid PFS0 entry count / name table size!");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Calculate full PFS0 header size */
|
||||
out->header_size = (sizeof(PartitionFileSystemHeader) + (pfs0_header.entry_count * sizeof(PartitionFileSystemEntry)) + pfs0_header.name_table_size);
|
||||
|
||||
/* Allocate memory for the full PFS0 header */
|
||||
out->header = calloc(out->header_size, sizeof(u8));
|
||||
if (!out->header)
|
||||
{
|
||||
LOGFILE("Unable to allocate 0x%lX bytes buffer for the full PFS0 header!", out->header_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Read full PFS0 header */
|
||||
if (!ncaReadFsSection(nca_fs_ctx, out->header, out->header_size, out->offset))
|
||||
{
|
||||
LOGFILE("Failed to read full PFS0 header!");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check if we're dealing with an ExeFS section */
|
||||
if ((main_npdm_entry = pfs0GetEntryByName(out, "main.npdm")) != NULL && pfs0ReadEntryData(out, main_npdm_entry, &magic, sizeof(u32), 0) && \
|
||||
__builtin_bswap32(magic) == NPDM_META_MAGIC) out->is_exefs = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pfs0ReadEntryData(PartitionFileSystemContext *ctx, PartitionFileSystemEntry *fs_entry, void *out, u64 read_size, u64 offset)
|
||||
{
|
||||
if (!ctx || !ctx->nca_fs_ctx || !ctx->hash_info || !ctx->size || !ctx->header_size || !ctx->header || !fs_entry || fs_entry->offset >= ctx->size || \
|
||||
(fs_entry->offset + fs_entry->size) > ctx->size || !out || !read_size || offset >= fs_entry->size || (offset + read_size) > fs_entry->size)
|
||||
{
|
||||
LOGFILE("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Calculate offset relative to the start of the NCA FS section */
|
||||
u64 section_offset = (ctx->offset + ctx->header_size + fs_entry->offset + offset);
|
||||
|
||||
/* Read entry data */
|
||||
if (!ncaReadFsSection(ctx->nca_fs_ctx, out, read_size, section_offset))
|
||||
{
|
||||
LOGFILE("Failed to read PFS0 entry data!");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
123
source/pfs0.h
123
source/pfs0.h
|
@ -1,123 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020 DarkMatterCore
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef __PFS0_H__
|
||||
#define __PFS0_H__
|
||||
|
||||
#include <switch.h>
|
||||
#include "nca.h"
|
||||
|
||||
#define PFS0_MAGIC 0x50465330 /* "PFS0" */
|
||||
|
||||
typedef struct {
|
||||
u32 magic; ///< "PFS0".
|
||||
u32 entry_count;
|
||||
u32 name_table_size;
|
||||
u8 reserved[0x4];
|
||||
} PartitionFileSystemHeader;
|
||||
|
||||
typedef struct {
|
||||
u64 offset;
|
||||
u64 size;
|
||||
u32 name_offset;
|
||||
u8 reserved[0x4];
|
||||
} PartitionFileSystemEntry;
|
||||
|
||||
typedef struct {
|
||||
NcaFsSectionContext *nca_fs_ctx; ///< Used to read NCA FS section data.
|
||||
NcaHierarchicalSha256 *hash_info; ///< Hash table information.
|
||||
u64 offset; ///< Partition offset (relative to the start of the NCA FS section).
|
||||
u64 size; ///< Partition size.
|
||||
bool is_exefs; ///< ExeFS flag.
|
||||
u64 header_size; ///< Full header size.
|
||||
u8 *header; ///< PartitionFileSystemHeader + (PartitionFileSystemEntry * entry_count) + Name Table.
|
||||
} PartitionFileSystemContext;
|
||||
|
||||
/// Initializes a PFS0 context.
|
||||
bool pfs0InitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *nca_fs_ctx);
|
||||
|
||||
/// Cleanups a previously initialized PFS0 context.
|
||||
static inline void pfs0FreeContext(PartitionFileSystemContext *ctx)
|
||||
{
|
||||
if (!ctx) return;
|
||||
if (ctx->header) free(ctx->header);
|
||||
memset(ctx, 0, sizeof(PartitionFileSystemContext));
|
||||
}
|
||||
|
||||
/// Reads data from a previously retrieved PartitionFileSystemEntry using a PFS0 context.
|
||||
bool pfs0ReadEntryData(PartitionFileSystemContext *ctx, PartitionFileSystemEntry *fs_entry, void *out, u64 read_size, u64 offset);
|
||||
|
||||
/// Miscellaneous functions.
|
||||
|
||||
static inline u32 pfs0GetEntryCount(PartitionFileSystemContext *ctx)
|
||||
{
|
||||
if (!ctx || !ctx->header_size || !ctx->header) return 0;
|
||||
return ((PartitionFileSystemHeader*)ctx->header)->entry_count;
|
||||
}
|
||||
|
||||
static inline PartitionFileSystemEntry *pfs0GetEntryByIndex(PartitionFileSystemContext *ctx, u32 idx)
|
||||
{
|
||||
if (idx >= pfs0GetEntryCount(ctx)) return NULL;
|
||||
return (PartitionFileSystemEntry*)(ctx->header + sizeof(PartitionFileSystemHeader) + (idx * sizeof(PartitionFileSystemEntry)));
|
||||
}
|
||||
|
||||
static inline char *pfs0GetNameTable(PartitionFileSystemContext *ctx)
|
||||
{
|
||||
u32 entry_count = pfs0GetEntryCount(ctx);
|
||||
if (!entry_count) return NULL;
|
||||
return (char*)(ctx->header + sizeof(PartitionFileSystemHeader) + (entry_count * sizeof(PartitionFileSystemEntry)));
|
||||
}
|
||||
|
||||
static inline char *pfs0GetEntryNameByIndex(PartitionFileSystemContext *ctx, u32 idx)
|
||||
{
|
||||
PartitionFileSystemEntry *fs_entry = pfs0GetEntryByIndex(ctx, idx);
|
||||
char *name_table = pfs0GetNameTable(ctx);
|
||||
if (!fs_entry || !name_table) return NULL;
|
||||
return (name_table + fs_entry->name_offset);
|
||||
}
|
||||
|
||||
static inline bool pfs0GetEntryIndexByName(PartitionFileSystemContext *ctx, const char *name, u32 *out_idx)
|
||||
{
|
||||
size_t name_len = 0;
|
||||
u32 entry_count = pfs0GetEntryCount(ctx);
|
||||
char *name_table = pfs0GetNameTable(ctx);
|
||||
if (!entry_count || !name_table || !name || !(name_len = strlen(name)) || !out_idx) return false;
|
||||
|
||||
for(u32 i = 0; i < entry_count; i++)
|
||||
{
|
||||
PartitionFileSystemEntry *fs_entry = pfs0GetEntryByIndex(ctx, i);
|
||||
if (!fs_entry) continue;
|
||||
|
||||
if (!strncmp(name_table + fs_entry->name_offset, name, name_len))
|
||||
{
|
||||
*out_idx = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline PartitionFileSystemEntry *pfs0GetEntryByName(PartitionFileSystemContext *ctx, const char *name)
|
||||
{
|
||||
u32 idx = 0;
|
||||
if (!pfs0GetEntryIndexByName(ctx, name, &idx)) return NULL;
|
||||
return pfs0GetEntryByIndex(ctx, idx);
|
||||
}
|
||||
|
||||
#endif /* __PFS0_H__ */
|
222
source/romfs.c
Normal file
222
source/romfs.c
Normal file
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
* Copyright (c) 2020 DarkMatterCore
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "romfs.h"
|
||||
#include "utils.h"
|
||||
|
||||
bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *nca_fs_ctx)
|
||||
{
|
||||
NcaContext *nca_ctx = NULL;
|
||||
u64 dir_table_offset = 0, file_table_offset = 0;
|
||||
|
||||
if (!out || !nca_fs_ctx || nca_fs_ctx->section_type != NcaFsSectionType_RomFs || !nca_fs_ctx->header || !(nca_ctx = (NcaContext*)nca_fs_ctx->nca_ctx) || \
|
||||
(nca_ctx->format_version == NcaVersion_Nca0 && (nca_fs_ctx->section_type != NcaFsSectionType_Nca0RomFs || nca_fs_ctx->header->hash_type != NcaHashType_HierarchicalSha256)) || \
|
||||
(nca_ctx->format_version != NcaVersion_Nca0 && (nca_fs_ctx->section_type != NcaFsSectionType_RomFs || nca_fs_ctx->header->hash_type != NcaHashType_HierarchicalIntegrity)))
|
||||
{
|
||||
LOGFILE("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Fill context */
|
||||
out->nca_fs_ctx = nca_fs_ctx;
|
||||
out->offset = 0;
|
||||
out->size = 0;
|
||||
out->dir_table_size = 0;
|
||||
out->dir_table = NULL;
|
||||
out->file_table_size = 0;
|
||||
out->file_table = NULL;
|
||||
out->body_offset = 0;
|
||||
|
||||
if (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs)
|
||||
{
|
||||
out->sha256_hash_info = &(nca_fs_ctx->header->hash_info.hierarchical_sha256);
|
||||
out->integrity_hash_info = NULL;
|
||||
|
||||
if (!ncaValidateHierarchicalSha256Offsets(out->sha256_hash_info, nca_fs_ctx->section_size))
|
||||
{
|
||||
LOGFILE("Invalid HierarchicalSha256 block!");
|
||||
return false;
|
||||
}
|
||||
|
||||
out->offset = out->sha256_hash_info->hash_target_layer_info.offset;
|
||||
out->size = out->sha256_hash_info->hash_target_layer_info.size;
|
||||
} else {
|
||||
out->sha256_hash_info = NULL;
|
||||
out->integrity_hash_info = &(nca_fs_ctx->header->hash_info.hierarchical_integrity);
|
||||
|
||||
if (!ncaValidateHierarchicalIntegrityOffsets(out->integrity_hash_info, nca_fs_ctx->section_size))
|
||||
{
|
||||
LOGFILE("Invalid HierarchicalIntegrity block!");
|
||||
return false;
|
||||
}
|
||||
|
||||
out->offset = out->integrity_hash_info->hash_target_layer_info.offset;
|
||||
out->size = out->integrity_hash_info->hash_target_layer_info.size;
|
||||
}
|
||||
|
||||
/* Read RomFS header */
|
||||
if (!ncaReadFsSection(nca_fs_ctx, &(out->header), sizeof(RomFileSystemHeader), out->offset))
|
||||
{
|
||||
LOGFILE("Failed to read RomFS header!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs && out->header.old_format.header_size != ROMFS_OLD_HEADER_SIZE) || \
|
||||
(nca_fs_ctx->section_type == NcaFsSectionType_RomFs && out->header.cur_format.header_size != ROMFS_HEADER_SIZE))
|
||||
{
|
||||
LOGFILE("Invalid RomFS header size!");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Read directory entries table */
|
||||
dir_table_offset = (out->offset + (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs ? (u64)out->header.old_format.directory_entry_offset : out->header.cur_format.directory_entry_offset));
|
||||
out->dir_table_size = (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs ? (u64)out->header.old_format.directory_entry_size : out->header.cur_format.directory_entry_size);
|
||||
|
||||
if (dir_table_offset >= out->size || !out->dir_table_size || (dir_table_offset + out->dir_table_size) > out->size)
|
||||
{
|
||||
LOGFILE("Invalid RomFS directory entries table!");
|
||||
return false;
|
||||
}
|
||||
|
||||
out->dir_table = malloc(out->dir_table_size);
|
||||
if (!out->dir_table)
|
||||
{
|
||||
LOGFILE("Unable to allocate memory for RomFS directory entries table!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ncaReadFsSection(nca_fs_ctx, out->dir_table, out->dir_table_size, dir_table_offset))
|
||||
{
|
||||
LOGFILE("Failed to read RomFS directory entries table!");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Read file entries table */
|
||||
file_table_offset = (out->offset + (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs ? (u64)out->header.old_format.file_entry_offset : out->header.cur_format.file_entry_offset));
|
||||
out->file_table_size = (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs ? (u64)out->header.old_format.file_entry_size : out->header.cur_format.file_entry_size);
|
||||
|
||||
if (file_table_offset >= out->size || !out->file_table_size || (file_table_offset + out->file_table_size) > out->size)
|
||||
{
|
||||
LOGFILE("Invalid RomFS file entries table!");
|
||||
return false;
|
||||
}
|
||||
|
||||
out->file_table = malloc(out->file_table_size);
|
||||
if (!out->file_table)
|
||||
{
|
||||
LOGFILE("Unable to allocate memory for RomFS file entries table!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ncaReadFsSection(nca_fs_ctx, out->file_table, out->file_table_size, file_table_offset))
|
||||
{
|
||||
LOGFILE("Failed to read RomFS file entries table!");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Calculate file data body offset */
|
||||
out->body_offset = (out->offset + (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs ? (u64)out->header.old_format.body_offset : out->header.cur_format.body_offset));
|
||||
if (out->body_offset >= out->size)
|
||||
{
|
||||
LOGFILE("Invalid RomFS file data body!");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool romfsReadFileEntryData(RomFileSystemContext *ctx, RomFileSystemFileEntry *file_entry, void *out, u64 read_size, u64 offset)
|
||||
{
|
||||
if (!ctx || !ctx->nca_fs_ctx || !ctx->size || !ctx->body_offset || !file_entry || !file_entry->size || file_entry->offset >= ctx->size || (file_entry->offset + file_entry->size) > ctx->size || \
|
||||
!out || !read_size || offset >= file_entry->size || (offset + read_size) > file_entry->size)
|
||||
{
|
||||
LOGFILE("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Calculate offset relative to the start of the NCA FS section */
|
||||
u64 section_offset = (ctx->body_offset + file_entry->offset + offset);
|
||||
|
||||
/* Read entry data */
|
||||
if (!ncaReadFsSection(ctx->nca_fs_ctx, out, read_size, section_offset))
|
||||
{
|
||||
LOGFILE("Failed to read RomFS file entry data!");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool romfsGetTotalDataSize(RomFileSystemContext *ctx, u64 *out_size)
|
||||
{
|
||||
if (!ctx || !ctx->file_table_size || !ctx->file_table || !out_size) return false;
|
||||
|
||||
u64 offset = 0, total_size = 0;
|
||||
RomFileSystemFileEntry *file_entry = NULL;
|
||||
|
||||
while(offset < ctx->file_table_size)
|
||||
{
|
||||
if (!(file_entry = romfsGetFileEntry(ctx, offset))) return false;
|
||||
total_size += file_entry->size;
|
||||
offset += ALIGN_UP(sizeof(RomFileSystemFileEntry) + file_entry->name_length, 4);
|
||||
}
|
||||
|
||||
*out_size = total_size;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool romfsGetDirectoryDataSize(RomFileSystemContext *ctx, u32 dir_entry_offset, u64 *out_size)
|
||||
{
|
||||
u64 total_size = 0, child_dir_size = 0;
|
||||
RomFileSystemDirectoryEntry *dir_entry = NULL;
|
||||
RomFileSystemFileEntry *file_entry = NULL;
|
||||
|
||||
if (!ctx || !ctx->dir_table_size || !ctx->dir_table || !ctx->file_table_size || !ctx->file_table || !out_size || !(dir_entry = romfsGetDirectoryEntry(ctx, dir_entry_offset)) || \
|
||||
(!dir_entry->name_length && dir_entry_offset > 0)) return false;
|
||||
|
||||
if (dir_entry->file_offset != ROMFS_VOID_ENTRY)
|
||||
{
|
||||
if (!(file_entry = romfsGetFileEntry(ctx, dir_entry->file_offset))) return false;
|
||||
total_size += file_entry->size;
|
||||
|
||||
while(file_entry->next_offset != ROMFS_VOID_ENTRY)
|
||||
{
|
||||
if (!(file_entry = romfsGetFileEntry(ctx, file_entry->next_offset))) return false;
|
||||
total_size += file_entry->size;
|
||||
}
|
||||
}
|
||||
|
||||
if (dir_entry->directory_offset != ROMFS_VOID_ENTRY)
|
||||
{
|
||||
if (!romfsGetDirectoryDataSize(ctx, dir_entry->directory_offset, &child_dir_size)) return false;
|
||||
total_size += child_dir_size;
|
||||
|
||||
while(dir_entry->next_offset != ROMFS_VOID_ENTRY)
|
||||
{
|
||||
if (!romfsGetDirectoryDataSize(ctx, dir_entry->next_offset, &child_dir_size)) return false;
|
||||
total_size += child_dir_size;
|
||||
if (!(dir_entry = romfsGetDirectoryEntry(ctx, dir_entry->next_offset))) return false;
|
||||
}
|
||||
}
|
||||
|
||||
*out_size = total_size;
|
||||
|
||||
return true;
|
||||
}
|
140
source/romfs.h
Normal file
140
source/romfs.h
Normal file
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright (c) 2020 DarkMatterCore
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef __ROMFS_H__
|
||||
#define __ROMFS_H__
|
||||
|
||||
#include <switch.h>
|
||||
#include "nca.h"
|
||||
|
||||
#define ROMFS_OLD_HEADER_SIZE 0x28
|
||||
#define ROMFS_HEADER_SIZE 0x50
|
||||
|
||||
#define ROMFS_VOID_ENTRY 0xFFFFFFFF
|
||||
|
||||
/// Header used by NCA0 RomFS sections.
|
||||
typedef struct {
|
||||
u32 header_size; ///< Header size. Must be equal to ROMFS_OLD_HEADER_SIZE.
|
||||
u32 directory_bucket_offset; ///< Directory buckets table offset.
|
||||
u32 directory_bucket_size; ///< Directory buckets table size.
|
||||
u32 directory_entry_offset; ///< Directory entries table offset.
|
||||
u32 directory_entry_size; ///< Directory entries table size.
|
||||
u32 file_bucket_offset; ///< File buckets table offset.
|
||||
u32 file_bucket_size; ///< File buckets table size.
|
||||
u32 file_entry_offset; ///< File entries table offset.
|
||||
u32 file_entry_size; ///< File entries table size.
|
||||
u32 body_offset; ///< File data body offset.
|
||||
} RomFileSystemInformationOld;
|
||||
|
||||
/// Header used by NCA2/NCA3 RomFS sections.
|
||||
typedef struct {
|
||||
u64 header_size; ///< Header size. Must be equal to ROMFS_HEADER_SIZE.
|
||||
u64 directory_bucket_offset; ///< Directory buckets table offset.
|
||||
u64 directory_bucket_size; ///< Directory buckets table size.
|
||||
u64 directory_entry_offset; ///< Directory entries table offset.
|
||||
u64 directory_entry_size; ///< Directory entries table size.
|
||||
u64 file_bucket_offset; ///< File buckets table offset.
|
||||
u64 file_bucket_size; ///< File buckets table size.
|
||||
u64 file_entry_offset; ///< File entries table offset.
|
||||
u64 file_entry_size; ///< File entries table size.
|
||||
u64 body_offset; ///< File data body offset.
|
||||
} RomFileSystemInformation;
|
||||
|
||||
/// Header union.
|
||||
typedef struct {
|
||||
union {
|
||||
struct {
|
||||
RomFileSystemInformationOld old_format;
|
||||
u8 padding[ROMFS_OLD_HEADER_SIZE];
|
||||
};
|
||||
RomFileSystemInformation cur_format;
|
||||
};
|
||||
} RomFileSystemHeader;
|
||||
|
||||
/// Directory entry. Always aligned to a 4-byte boundary past the directory name.
|
||||
typedef struct {
|
||||
u32 parent_offset; ///< Parent directory offset.
|
||||
u32 next_offset; ///< Next sibling directory offset.
|
||||
u32 directory_offset; ///< First child directory offset.
|
||||
u32 file_offset; ///< First child file offset.
|
||||
u32 bucket_offset; ///< Directory bucket offset.
|
||||
u32 name_length; ///< Name length.
|
||||
char name[]; ///< Name (UTF-8).
|
||||
} RomFileSystemDirectoryEntry;
|
||||
|
||||
/// Directory entry. Always aligned to a 4-byte boundary past the file name.
|
||||
typedef struct {
|
||||
u32 parent_offset; ///< Parent directory offset.
|
||||
u32 next_offset; ///< Next sibling file offset.
|
||||
u64 offset; ///< File data offset.
|
||||
u64 size; ///< File data size.
|
||||
u32 bucket_offset; ///< File bucket offset.
|
||||
u32 name_length; ///< Name length.
|
||||
char name[]; ///< Name (UTF-8).
|
||||
} RomFileSystemFileEntry;
|
||||
|
||||
typedef struct {
|
||||
NcaFsSectionContext *nca_fs_ctx; ///< Used to read NCA FS section data.
|
||||
NcaHierarchicalSha256 *sha256_hash_info; ///< HierarchicalSha256 hash table information. Used with NCA0 RomFS sections.
|
||||
NcaHierarchicalIntegrity *integrity_hash_info; ///< HierarchicalIntegrity hash table information. Used with NCA2/NCA3 RomFS sections.
|
||||
u64 offset; ///< RomFS offset (relative to the start of the NCA FS section).
|
||||
u64 size; ///< RomFS size.
|
||||
RomFileSystemHeader header; ///< RomFS header.
|
||||
u64 dir_table_size; ///< RomFS directory entries table size.
|
||||
RomFileSystemDirectoryEntry *dir_table; ///< RomFS directory entries table.
|
||||
u64 file_table_size; ///< RomFS file entries table size.
|
||||
RomFileSystemFileEntry *file_table; ///< RomFS file entries table.
|
||||
u64 body_offset; ///< RomFS file data body offset (relative to the start of the NCA FS section).
|
||||
} RomFileSystemContext;
|
||||
|
||||
/// Initializes a RomFS context.
|
||||
bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *nca_fs_ctx);
|
||||
|
||||
/// Cleanups a previously initialized RomFS context.
|
||||
NX_INLINE void romfsFreeContext(RomFileSystemContext *ctx)
|
||||
{
|
||||
if (!ctx) return;
|
||||
if (ctx->dir_table) free(ctx->dir_table);
|
||||
if (ctx->file_table) free(ctx->file_table);
|
||||
memset(ctx, 0, sizeof(RomFileSystemContext));
|
||||
}
|
||||
|
||||
/// Reads data from a previously retrieved RomFileSystemFileEntry using a RomFS context.
|
||||
bool romfsReadFileEntryData(RomFileSystemContext *ctx, RomFileSystemFileEntry *file_entry, void *out, u64 read_size, u64 offset);
|
||||
|
||||
/// Calculates the extracted RomFS size.
|
||||
bool romfsGetTotalDataSize(RomFileSystemContext *ctx, u64 *out_size);
|
||||
|
||||
/// Calculates the extracted size from a RomFS directory.
|
||||
bool romfsGetDirectoryDataSize(RomFileSystemContext *ctx, u32 dir_entry_offset, u64 *out_size);
|
||||
|
||||
/// Miscellaneous functions.
|
||||
|
||||
NX_INLINE RomFileSystemDirectoryEntry *romfsGetDirectoryEntry(RomFileSystemContext *ctx, u32 file_entry_offset)
|
||||
{
|
||||
if (!ctx || !ctx->dir_table || file_entry_offset >= ctx->dir_table_size) return NULL;
|
||||
return (RomFileSystemDirectoryEntry*)((u8*)ctx->dir_table + file_entry_offset);
|
||||
}
|
||||
|
||||
NX_INLINE RomFileSystemFileEntry *romfsGetFileEntry(RomFileSystemContext *ctx, u32 dir_entry_offset)
|
||||
{
|
||||
if (!ctx || !ctx->file_table || dir_entry_offset >= ctx->file_table_size) return NULL;
|
||||
return (RomFileSystemFileEntry*)((u8*)ctx->file_table + dir_entry_offset);
|
||||
}
|
||||
|
||||
#endif /* __ROMFS_H__ */
|
|
@ -29,11 +29,13 @@
|
|||
|
||||
#define MAX_ELEMENTS(x) ((sizeof((x))) / (sizeof((x)[0])))
|
||||
|
||||
#define ROUND_DOWN(x, y) ((x) & ~((y) - 1))
|
||||
#define ROUND_UP(x, y) ((x) + (((y) - ((x) % (y))) % (y)))
|
||||
#define ALIGN_DOWN(x, y) ((x) & ~((y) - 1))
|
||||
#define ALIGN_UP(x, y) ((((y) - 1) + (x)) & ~((y) - 1))
|
||||
|
||||
#define BIS_SYSTEM_PARTITION_MOUNT_NAME "sys:"
|
||||
|
||||
#define NPDM_META_MAGIC 0x4D455441 /* "META" */
|
||||
|
||||
typedef enum {
|
||||
UtilsCustomFirmwareType_Unknown = 0,
|
||||
UtilsCustomFirmwareType_Atmosphere = 1,
|
||||
|
|
Loading…
Reference in a new issue