1
0
Fork 0
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:
Pablo Curiel 2020-04-26 04:35:01 -04:00
parent dccb33ab0c
commit c6eaf3c8b6
13 changed files with 985 additions and 332 deletions

View file

@ -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.]

View file

@ -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

View file

@ -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))
{

View file

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

View file

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

View file

@ -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
View 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
View 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__ */

View file

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

View file

@ -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
View 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
View 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__ */

View file

@ -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,