mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-22 18:26:39 +00:00
PFS0 context.
This commit is contained in:
parent
76550adab8
commit
7c8bf5c831
9 changed files with 663 additions and 447 deletions
|
@ -1,7 +1,7 @@
|
|||
todo:
|
||||
|
||||
hfs0 methods
|
||||
pfs0: full header aligned to 0x20
|
||||
pfs0: full header aligned to 0x20 (nsp)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
#include "service_guard.h"
|
||||
#include "utils.h"
|
||||
|
||||
#define GAMECARD_HFS0_MAGIC 0x48465330 /* "HFS0" */
|
||||
|
||||
#define GAMECARD_READ_BUFFER_SIZE 0x800000 /* 8 MiB */
|
||||
|
||||
#define GAMECARD_ACCESS_WAIT_TIME 3 /* Seconds */
|
||||
|
@ -43,6 +45,22 @@
|
|||
|
||||
/* Type definitions. */
|
||||
|
||||
typedef struct {
|
||||
u32 magic; ///< "HFS0".
|
||||
u32 entry_count;
|
||||
u32 name_table_size;
|
||||
u8 reserved[0x4];
|
||||
} GameCardHashFileSystemHeader;
|
||||
|
||||
typedef struct {
|
||||
u64 offset;
|
||||
u64 size;
|
||||
u32 name_offset;
|
||||
u32 hash_target_size;
|
||||
u64 hash_target_offset;
|
||||
u8 hash[SHA256_HASH_SIZE];
|
||||
} GameCardHashFileSystemEntry;
|
||||
|
||||
typedef enum {
|
||||
GameCardStorageArea_None = 0,
|
||||
GameCardStorageArea_Normal = 1,
|
||||
|
@ -53,7 +71,7 @@ typedef struct {
|
|||
u64 offset; ///< Relative to the start of the gamecard header.
|
||||
u64 size; ///< Whole partition size.
|
||||
u64 header_size; ///< Full header size.
|
||||
u8 *header; ///< GameCardHashFileSystemHeader + GameCardHashFileSystemEntry + Name Table.
|
||||
u8 *header; ///< GameCardHashFileSystemHeader + (GameCardHashFileSystemEntry * entry_count) + Name Table.
|
||||
} GameCardHashFileSystemPartitionInfo;
|
||||
|
||||
/* Global variables. */
|
||||
|
@ -77,7 +95,7 @@ static GameCardHeader g_gameCardHeader = {0};
|
|||
static u64 g_gameCardStorageNormalAreaSize = 0, g_gameCardStorageSecureAreaSize = 0;
|
||||
static u64 g_gameCardCapacity = 0;
|
||||
|
||||
static u8 *g_gameCardHfsRootHeader = NULL; /// GameCardHashFileSystemHeader + GameCardHashFileSystemEntry + Name Table.
|
||||
static u8 *g_gameCardHfsRootHeader = NULL; /// GameCardHashFileSystemHeader + (entry_count * GameCardHashFileSystemEntry) + Name Table.
|
||||
static GameCardHashFileSystemPartitionInfo *g_gameCardHfsPartitions = NULL;
|
||||
|
||||
/* Function prototypes. */
|
||||
|
@ -101,9 +119,12 @@ static void gamecardCloseStorageArea(void);
|
|||
static bool gamecardGetStorageAreasSizes(void);
|
||||
static inline u64 gamecardGetCapacityFromRomSizeValue(u8 rom_size);
|
||||
|
||||
static bool gamecardGetHashFileSystemPartitionIndexByType(u8 type, u32 *out);
|
||||
static inline GameCardHashFileSystemEntry *gamecardGetHashFileSystemEntryByIndex(void *hfs_header, u32 idx);
|
||||
static inline char *gamecardGetHashFileSystemEntryName(void *hfs_header, u32 name_offset);
|
||||
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);
|
||||
|
||||
/* 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. */
|
||||
|
@ -120,7 +141,7 @@ bool gamecardIsReady(void)
|
|||
return ret;
|
||||
}
|
||||
|
||||
bool gamecardRead(void *out, u64 read_size, u64 offset)
|
||||
bool gamecardReadStorage(void *out, u64 read_size, u64 offset)
|
||||
{
|
||||
return gamecardReadStorageArea(out, read_size, offset, true);
|
||||
}
|
||||
|
@ -214,6 +235,7 @@ bool gamecardGetBundledFirmwareUpdateVersion(u32 *out)
|
|||
{
|
||||
rc = fsDeviceOperatorUpdatePartitionInfo(&g_deviceOperator, &g_gameCardHandle, &update_version, &update_id);
|
||||
if (R_FAILED(rc)) LOGFILE("fsDeviceOperatorUpdatePartitionInfo failed! (0x%08X)", rc);
|
||||
|
||||
ret = (R_SUCCEEDED(rc) && update_id == GAMECARD_UPDATE_TID);
|
||||
if (ret) *out = update_version;
|
||||
}
|
||||
|
@ -222,41 +244,84 @@ bool gamecardGetBundledFirmwareUpdateVersion(u32 *out)
|
|||
return ret;
|
||||
}
|
||||
|
||||
bool gamecardGetOffsetAndSizeFromHashFileSystemPartitionEntryByName(u8 hfs_partition_type, const char *name, u64 *out_offset, u64 *out_size)
|
||||
bool gamecardGetEntryCountFromHashFileSystemPartition(u8 hfs_partition_type, u32 *out_count)
|
||||
{
|
||||
bool ret = false;
|
||||
GameCardHashFileSystemHeader *fs_header = NULL;
|
||||
|
||||
mtx_lock(&g_gameCardSharedDataMutex);
|
||||
if (g_gameCardInserted && g_gameCardInfoLoaded && out_count)
|
||||
{
|
||||
fs_header = gamecardGetHashFileSystemPartitionHeader(hfs_partition_type, NULL);
|
||||
if (fs_header)
|
||||
{
|
||||
*out_count = fs_header->entry_count;
|
||||
ret = true;
|
||||
} else {
|
||||
LOGFILE("Failed to retrieve hash FS partition header!");
|
||||
}
|
||||
}
|
||||
mtx_unlock(&g_gameCardSharedDataMutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool gamecardGetEntryInfoFromHashFileSystemPartitionByIndex(u8 hfs_partition_type, u32 idx, u64 *out_offset, u64 *out_size, char **out_name)
|
||||
{
|
||||
bool ret = false;
|
||||
char *entry_name = NULL;
|
||||
size_t name_len = 0;
|
||||
u32 hfs_partition_idx = 0;
|
||||
GameCardHashFileSystemHeader *fs_header = NULL;
|
||||
GameCardHashFileSystemEntry *fs_entry = NULL;
|
||||
|
||||
mtx_lock(&g_gameCardSharedDataMutex);
|
||||
|
||||
if (!g_gameCardInserted || !g_gameCardInfoLoaded || !name || !*name || (!out_offset && !out_size) || !gamecardGetHashFileSystemPartitionIndexByType(hfs_partition_type, &hfs_partition_idx))
|
||||
if (g_gameCardInserted && g_gameCardInfoLoaded && (out_offset || out_size || out_name))
|
||||
{
|
||||
LOGFILE("Invalid parameters!");
|
||||
fs_header = gamecardGetHashFileSystemPartitionHeader(hfs_partition_type, &hfs_partition_idx);
|
||||
if (!fs_header)
|
||||
{
|
||||
LOGFILE("Failed to retrieve hash FS partition header!");
|
||||
goto out;
|
||||
}
|
||||
|
||||
name_len = strlen(name);
|
||||
fs_header = (GameCardHashFileSystemHeader*)g_gameCardHfsPartitions[hfs_partition_idx].header;
|
||||
|
||||
for(u32 i = 0; i < fs_header->entry_count; i++)
|
||||
fs_entry = gamecardGetHashFileSystemEntryByIndex(fs_header, idx);
|
||||
if (!fs_entry)
|
||||
{
|
||||
fs_entry = gamecardGetHashFileSystemEntryByIndex(fs_header, i);
|
||||
if (!fs_entry) continue;
|
||||
|
||||
entry_name = gamecardGetHashFileSystemEntryName(fs_header, fs_entry->name_offset);
|
||||
if (!entry_name) continue;
|
||||
|
||||
if (!strncasecmp(entry_name, name, name_len))
|
||||
{
|
||||
if (out_offset) *out_offset = (g_gameCardHfsPartitions[hfs_partition_idx].offset + g_gameCardHfsPartitions[hfs_partition_idx].header_size + fs_entry->offset);
|
||||
if (out_size) *out_size = fs_entry->size;
|
||||
ret = true;
|
||||
break;
|
||||
LOGFILE("Failed to retrieve hash FS partition entry by index!");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (out_offset)
|
||||
{
|
||||
if (hfs_partition_type == GameCardHashFileSystemPartitionType_Root)
|
||||
{
|
||||
*out_offset = g_gameCardHfsPartitions[idx].offset; /* No need to recalculate what we already have */
|
||||
} else {
|
||||
*out_offset = (g_gameCardHfsPartitions[hfs_partition_idx].offset + g_gameCardHfsPartitions[hfs_partition_idx].header_size + fs_entry->offset);
|
||||
}
|
||||
}
|
||||
|
||||
if (out_size) *out_size = fs_entry->size;
|
||||
|
||||
if (out_name)
|
||||
{
|
||||
entry_name = gamecardGetHashFileSystemEntryNameByIndex(fs_header, idx);
|
||||
if (!entry_name || !strlen(entry_name))
|
||||
{
|
||||
LOGFILE("Invalid hash FS partition entry name!");
|
||||
goto out;
|
||||
}
|
||||
|
||||
*out_name = strdup(entry_name);
|
||||
if (!*out_name)
|
||||
{
|
||||
LOGFILE("Failed to duplicate hash FS partition entry name!");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
ret = true;
|
||||
}
|
||||
|
||||
out:
|
||||
|
@ -265,18 +330,57 @@ out:
|
|||
return ret;
|
||||
}
|
||||
|
||||
bool gamecardGetEntryInfoFromHashFileSystemPartitionByName(u8 hfs_partition_type, const char *name, u64 *out_offset, u64 *out_size)
|
||||
{
|
||||
bool ret = false;
|
||||
u32 hfs_partition_idx = 0, fs_entry_idx = 0;
|
||||
GameCardHashFileSystemHeader *fs_header = NULL;
|
||||
GameCardHashFileSystemEntry *fs_entry = NULL;
|
||||
|
||||
mtx_lock(&g_gameCardSharedDataMutex);
|
||||
|
||||
if (g_gameCardInserted && g_gameCardInfoLoaded && (out_offset || out_size))
|
||||
{
|
||||
fs_header = gamecardGetHashFileSystemPartitionHeader(hfs_partition_type, &hfs_partition_idx);
|
||||
if (!fs_header)
|
||||
{
|
||||
LOGFILE("Failed to retrieve hash FS partition header!");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!gamecardGetHashFileSystemEntryIndexByName(fs_header, name, &fs_entry_idx))
|
||||
{
|
||||
LOGFILE("Failed to retrieve hash FS partition entry index by name!");
|
||||
goto out;
|
||||
}
|
||||
|
||||
fs_entry = gamecardGetHashFileSystemEntryByIndex(fs_header, fs_entry_idx);
|
||||
if (!fs_entry)
|
||||
{
|
||||
LOGFILE("Failed to retrieve hash FS partition entry by index!");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (out_offset)
|
||||
{
|
||||
if (hfs_partition_type == GameCardHashFileSystemPartitionType_Root)
|
||||
{
|
||||
*out_offset = g_gameCardHfsPartitions[fs_entry_idx].offset; /* No need to recalculate what we already have */
|
||||
} else {
|
||||
*out_offset = (g_gameCardHfsPartitions[hfs_partition_idx].offset + g_gameCardHfsPartitions[hfs_partition_idx].header_size + fs_entry->offset);
|
||||
}
|
||||
}
|
||||
|
||||
if (out_size) *out_size = fs_entry->size;
|
||||
|
||||
ret = true;
|
||||
}
|
||||
|
||||
out:
|
||||
mtx_unlock(&g_gameCardSharedDataMutex);
|
||||
|
||||
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
NX_INLINE Result _gamecardInitialize(void)
|
||||
{
|
||||
|
@ -902,44 +1006,66 @@ static inline u64 gamecardGetCapacityFromRomSizeValue(u8 rom_size)
|
|||
return capacity;
|
||||
}
|
||||
|
||||
static bool gamecardGetHashFileSystemPartitionIndexByType(u8 type, u32 *out)
|
||||
static GameCardHashFileSystemHeader *gamecardGetHashFileSystemPartitionHeader(u8 hfs_partition_type, u32 *out_hfs_partition_idx)
|
||||
{
|
||||
if (type > GameCardHashFileSystemPartitionType_Secure || !out) return false;
|
||||
if (hfs_partition_type > GameCardHashFileSystemPartitionType_Secure) return NULL;
|
||||
|
||||
char *entry_name = NULL;
|
||||
GameCardHashFileSystemEntry *fs_entry = NULL;
|
||||
u32 hfs_partition_idx = 0;
|
||||
GameCardHashFileSystemHeader *fs_header = (GameCardHashFileSystemHeader*)g_gameCardHfsRootHeader;
|
||||
|
||||
if (hfs_partition_type != GameCardHashFileSystemPartitionType_Root)
|
||||
{
|
||||
if (gamecardGetHashFileSystemEntryIndexByName(fs_header, GAMECARD_HFS_PARTITION_NAME(hfs_partition_type), &hfs_partition_idx))
|
||||
{
|
||||
fs_header = (GameCardHashFileSystemHeader*)g_gameCardHfsPartitions[hfs_partition_idx].header;
|
||||
if (out_hfs_partition_idx) *out_hfs_partition_idx = hfs_partition_idx;
|
||||
} else {
|
||||
fs_header = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return fs_header;
|
||||
}
|
||||
|
||||
static 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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
GameCardHashFileSystemEntry *fs_entry = gamecardGetHashFileSystemEntryByIndex(header, idx);
|
||||
char *name_table = gamecardGetHashFileSystemNameTable(header);
|
||||
if (!fs_entry || !name_table) return NULL;
|
||||
return (name_table + fs_entry->name_offset);
|
||||
}
|
||||
|
||||
static inline bool gamecardGetHashFileSystemEntryIndexByName(void *header, const char *name, u32 *out_idx)
|
||||
{
|
||||
size_t name_len = 0;
|
||||
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++)
|
||||
{
|
||||
fs_entry = gamecardGetHashFileSystemEntryByIndex(fs_header, i);
|
||||
GameCardHashFileSystemEntry *fs_entry = gamecardGetHashFileSystemEntryByIndex(header, i);
|
||||
if (!fs_entry) continue;
|
||||
|
||||
entry_name = gamecardGetHashFileSystemEntryName(fs_header, fs_entry->name_offset);
|
||||
if (!entry_name) continue;
|
||||
|
||||
if (!strcasecmp(entry_name, GAMECARD_HFS_PARTITION_NAME(type)))
|
||||
if (!strncmp(name_table + fs_entry->name_offset, name, name_len))
|
||||
{
|
||||
*out = i;
|
||||
*out_idx = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline GameCardHashFileSystemEntry *gamecardGetHashFileSystemEntryByIndex(void *hfs_header, u32 idx)
|
||||
{
|
||||
if (!hfs_header || idx >= ((GameCardHashFileSystemHeader*)hfs_header)->entry_count) return NULL;
|
||||
return (GameCardHashFileSystemEntry*)((u8*)hfs_header + sizeof(GameCardHashFileSystemHeader) + (idx * sizeof(GameCardHashFileSystemEntry)));
|
||||
}
|
||||
|
||||
static inline char *gamecardGetHashFileSystemEntryName(void *hfs_header, u32 name_offset)
|
||||
{
|
||||
if (!hfs_header) return NULL;
|
||||
|
||||
GameCardHashFileSystemHeader *header = (GameCardHashFileSystemHeader*)hfs_header;
|
||||
if (!header->entry_count || name_offset >= header->name_table_size) return NULL;
|
||||
|
||||
return ((char*)hfs_header + sizeof(GameCardHashFileSystemHeader) + (header->entry_count * sizeof(GameCardHashFileSystemEntry)) + name_offset);
|
||||
}
|
||||
|
|
|
@ -23,12 +23,12 @@
|
|||
|
||||
#define GAMECARD_HEAD_MAGIC 0x48454144 /* "HEAD" */
|
||||
#define GAMECARD_CERT_MAGIC 0x43455254 /* "CERT" */
|
||||
#define GAMECARD_HFS0_MAGIC 0x48465330 /* "HFS0" */
|
||||
|
||||
#define GAMECARD_MEDIA_UNIT_SIZE 0x200
|
||||
|
||||
#define GAMECARD_HFS_PARTITION_NAME(x) ((x) == GameCardHashFileSystemPartitionType_Update ? "update" : ((x) == GameCardHashFileSystemPartitionType_Logo ? "logo" : \
|
||||
((x) == GameCardHashFileSystemPartitionType_Normal ? "normal" : ((x) == GameCardHashFileSystemPartitionType_Secure ? "secure" : "unknown"))))
|
||||
#define GAMECARD_HFS_PARTITION_NAME(x) ((x) == GameCardHashFileSystemPartitionType_Root ? "root" : ((x) == GameCardHashFileSystemPartitionType_Update ? "update" : \
|
||||
((x) == GameCardHashFileSystemPartitionType_Logo ? "logo" : ((x) == GameCardHashFileSystemPartitionType_Normal ? "normal" : \
|
||||
((x) == GameCardHashFileSystemPartitionType_Secure ? "secure" : "unknown")))))
|
||||
|
||||
typedef enum {
|
||||
GameCardKekIndex_Version0 = 0,
|
||||
|
@ -118,27 +118,12 @@ typedef struct {
|
|||
GameCardExtendedHeader extended_header; ///< Encrypted using AES-128-CBC with 'xci_header_key', which can't dumped through current methods.
|
||||
} GameCardHeader;
|
||||
|
||||
typedef struct {
|
||||
u32 magic; ///< "HFS0".
|
||||
u32 entry_count;
|
||||
u32 name_table_size;
|
||||
u8 reserved[0x4];
|
||||
} GameCardHashFileSystemHeader;
|
||||
|
||||
typedef struct {
|
||||
u64 offset;
|
||||
u64 size;
|
||||
u32 name_offset;
|
||||
u32 hash_target_size;
|
||||
u64 hash_target_offset;
|
||||
u8 hash[SHA256_HASH_SIZE];
|
||||
} GameCardHashFileSystemEntry;
|
||||
|
||||
typedef enum {
|
||||
GameCardHashFileSystemPartitionType_Update = 0,
|
||||
GameCardHashFileSystemPartitionType_Logo = 1, ///< Only available in GameCardFwVersion_Since400NUP gamecards.
|
||||
GameCardHashFileSystemPartitionType_Normal = 2,
|
||||
GameCardHashFileSystemPartitionType_Secure = 3
|
||||
GameCardHashFileSystemPartitionType_Root = 0,
|
||||
GameCardHashFileSystemPartitionType_Update = 1,
|
||||
GameCardHashFileSystemPartitionType_Logo = 2, ///< Only available in GameCardFwVersion_Since400NUP gamecards.
|
||||
GameCardHashFileSystemPartitionType_Normal = 3,
|
||||
GameCardHashFileSystemPartitionType_Secure = 4
|
||||
} GameCardHashFileSystemPartitionType;
|
||||
|
||||
/// Initializes data needed to access raw gamecard storage areas.
|
||||
|
@ -154,10 +139,11 @@ bool gamecardIsReady(void);
|
|||
|
||||
/// Used to read data from the inserted gamecard.
|
||||
/// All required handles, changes between normal <-> secure storage areas and proper offset calculations are managed internally.
|
||||
/// offset + read_size should never exceed the value returned by gamecardGetTotalSize().
|
||||
bool gamecardRead(void *out, u64 read_size, u64 offset);
|
||||
/// 'offset' + 'read_size' must not exceed the value returned by gamecardGetTotalSize().
|
||||
bool gamecardReadStorage(void *out, u64 read_size, u64 offset);
|
||||
|
||||
/// Miscellaneous functions.
|
||||
|
||||
bool gamecardGetHeader(GameCardHeader *out);
|
||||
bool gamecardGetTotalSize(u64 *out);
|
||||
bool gamecardGetTrimmedSize(u64 *out);
|
||||
|
@ -165,6 +151,16 @@ bool gamecardGetRomCapacity(u64 *out); ///< Not the same as gamecardGetTotalSize
|
|||
bool gamecardGetCertificate(FsGameCardCertificate *out);
|
||||
bool gamecardGetBundledFirmwareUpdateVersion(u32 *out);
|
||||
|
||||
bool gamecardGetOffsetAndSizeFromHashFileSystemPartitionEntryByName(u8 hfs_partition_type, const char *name, u64 *out_offset, u64 *out_size); ///< GameCardHashFileSystemPartitionType.
|
||||
/// Retrieves the entry count from a hash FS partition.
|
||||
bool gamecardGetEntryCountFromHashFileSystemPartition(u8 hfs_partition_type, u32 *out_count);
|
||||
|
||||
/// Retrieves info from a hash FS partition entry using an entry index.
|
||||
/// 'out_offset', 'out_size' or 'out_name' may be set to NULL, but at least one of them must be a valid pointer.
|
||||
/// If 'out_name' != NULL and the function call succeeds, a pointer to a heap allocated buffer is returned.
|
||||
bool gamecardGetEntryInfoFromHashFileSystemPartitionByIndex(u8 hfs_partition_type, u32 idx, u64 *out_offset, u64 *out_size, char **out_name);
|
||||
|
||||
/// Retrieves info from a hash FS partition entry using an entry name.
|
||||
/// 'out_offset' or 'out_size' may be set to NULL, but at least one of them must be a valid pointer.
|
||||
bool gamecardGetEntryInfoFromHashFileSystemPartitionByName(u8 hfs_partition_type, const char *name, u64 *out_offset, u64 *out_size);
|
||||
|
||||
#endif /* __GAMECARD_H__ */
|
||||
|
|
438
source/main.c
438
source/main.c
|
@ -22,12 +22,14 @@
|
|||
//#include "lvgl_helper.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include "gamecard.h"
|
||||
#include "nca.h"
|
||||
#include "cert.h"
|
||||
|
||||
#include <dirent.h>
|
||||
|
||||
#include "nca.h"
|
||||
#include "pfs0.h"
|
||||
|
||||
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
(void)argc;
|
||||
|
@ -51,229 +53,25 @@ int main(int argc, char *argv[])
|
|||
if (lvglHelperGetExitFlag()) break;
|
||||
}*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
consoleInit(NULL);
|
||||
|
||||
printf("waiting...\n");
|
||||
printf("initializing...\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
while(appletMainLoop())
|
||||
{
|
||||
if (gamecardIsReady()) break;
|
||||
}
|
||||
|
||||
u8 *buf = NULL;
|
||||
FILE *tmp_file = NULL;
|
||||
GameCardHeader header = {0};
|
||||
FsGameCardCertificate cert = {0};
|
||||
u64 total_size = 0, trimmed_size = 0;
|
||||
u32 update_version = 0;
|
||||
u64 nca_offset = 0, nca_size = 0;
|
||||
|
||||
Ticket tik = {0};
|
||||
NcaContext *nca_ctx = NULL;
|
||||
NcmContentStorage ncm_storage = {0};
|
||||
|
||||
Result rc = 0;
|
||||
|
||||
mkdir("sdmc:/nxdt_test", 0744);
|
||||
|
||||
|
||||
if (gamecardGetHeader(&header))
|
||||
{
|
||||
printf("header success\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
tmp_file = fopen("sdmc:/nxdt_test/header.bin", "wb");
|
||||
if (tmp_file)
|
||||
{
|
||||
fwrite(&header, 1, sizeof(GameCardHeader), tmp_file);
|
||||
fclose(tmp_file);
|
||||
tmp_file = NULL;
|
||||
printf("header saved\n");
|
||||
} else {
|
||||
printf("header not saved\n");
|
||||
}
|
||||
} else {
|
||||
printf("header failed\n");
|
||||
}
|
||||
|
||||
consoleUpdate(NULL);
|
||||
|
||||
if (gamecardGetTotalSize(&total_size))
|
||||
{
|
||||
printf("total_size: 0x%lX\n", total_size);
|
||||
} else {
|
||||
printf("total_size failed\n");
|
||||
}
|
||||
|
||||
consoleUpdate(NULL);
|
||||
|
||||
if (gamecardGetTrimmedSize(&trimmed_size))
|
||||
{
|
||||
printf("trimmed_size: 0x%lX\n", trimmed_size);
|
||||
} else {
|
||||
printf("trimmed_size failed\n");
|
||||
}
|
||||
|
||||
consoleUpdate(NULL);
|
||||
|
||||
if (gamecardGetCertificate(&cert))
|
||||
{
|
||||
printf("gamecard cert success\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
tmp_file = fopen("sdmc:/nxdt_test/cert.bin", "wb");
|
||||
if (tmp_file)
|
||||
{
|
||||
fwrite(&cert, 1, sizeof(FsGameCardCertificate), tmp_file);
|
||||
fclose(tmp_file);
|
||||
tmp_file = NULL;
|
||||
printf("gamecard cert saved\n");
|
||||
} else {
|
||||
printf("gamecard cert not saved\n");
|
||||
}
|
||||
} else {
|
||||
printf("gamecard cert failed\n");
|
||||
}
|
||||
|
||||
consoleUpdate(NULL);
|
||||
|
||||
if (gamecardGetBundledFirmwareUpdateVersion(&update_version))
|
||||
{
|
||||
printf("update_version: %u\n", update_version);
|
||||
} else {
|
||||
printf("update_version failed\n");
|
||||
}
|
||||
|
||||
consoleUpdate(NULL);
|
||||
|
||||
u8 *buf = malloc((u64)0x400300); // 4 MiB + 512 bytes + 256 bytes
|
||||
if (buf)
|
||||
{
|
||||
printf("buf succeeded\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
if (gamecardRead(buf, (u64)0x400300, (u64)0x16F18100)) // force unaligned read that spans both storage areas
|
||||
{
|
||||
u32 crc = crc32Calculate(buf, (u64)0x400300);
|
||||
|
||||
printf("read succeeded: %08X\n", crc);
|
||||
consoleUpdate(NULL);
|
||||
|
||||
tmp_file = fopen("sdmc:/nxdt_test/data.bin", "wb");
|
||||
if (tmp_file)
|
||||
{
|
||||
fwrite(buf, 1, (u64)0x400300, tmp_file);
|
||||
fclose(tmp_file);
|
||||
tmp_file = NULL;
|
||||
printf("data saved\n");
|
||||
} else {
|
||||
printf("data not saved\n");
|
||||
}
|
||||
} else {
|
||||
printf("read failed\n");
|
||||
}
|
||||
} else {
|
||||
printf("buf failed\n");
|
||||
}
|
||||
|
||||
consoleUpdate(NULL);
|
||||
|
||||
// Should match 0x1657F5E00
|
||||
if (gamecardGetOffsetAndSizeFromHashFileSystemPartitionEntryByName(GameCardHashFileSystemPartitionType_Secure, "7e86768383cfabb30f1b58d2373fed07.nca", &nca_offset, &nca_size))
|
||||
{
|
||||
printf("nca_offset: 0x%lX | nca_size: 0x%lX\n", nca_offset, nca_size);
|
||||
} else {
|
||||
printf("nca_offset + nca_size failed\n");
|
||||
}
|
||||
|
||||
consoleUpdate(NULL);
|
||||
|
||||
Ticket tik = {0};
|
||||
TikCommonBlock *tik_common_blk = NULL;
|
||||
|
||||
u8 *cert_chain = NULL;
|
||||
u64 cert_chain_size = 0;
|
||||
|
||||
FsRightsId rights_id = {
|
||||
/*FsRightsId rights_id = {
|
||||
.c = { 0x01, 0x00, 0x82, 0x40, 0x0B, 0xCC, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 } // Untitled Goose Game
|
||||
};
|
||||
|
||||
if (tikRetrieveTicketByRightsId(&tik, &rights_id, false))
|
||||
{
|
||||
printf("tik succeeded\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
tmp_file = fopen("sdmc:/nxdt_test/tik.bin", "wb");
|
||||
if (tmp_file)
|
||||
{
|
||||
fwrite(&tik, 1, sizeof(Ticket), tmp_file);
|
||||
fclose(tmp_file);
|
||||
tmp_file = NULL;
|
||||
printf("tik saved\n");
|
||||
} else {
|
||||
printf("tik not saved\n");
|
||||
}
|
||||
|
||||
consoleUpdate(NULL);
|
||||
|
||||
/*tikConvertPersonalizedTicketToCommonTicket(&tik);
|
||||
|
||||
printf("common tik generated\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
tmp_file = fopen("sdmc:/nxdt_test/common_tik.bin", "wb");
|
||||
if (tmp_file)
|
||||
{
|
||||
fwrite(&tik, 1, sizeof(Ticket), tmp_file);
|
||||
fclose(tmp_file);
|
||||
tmp_file = NULL;
|
||||
printf("common tik saved\n");
|
||||
} else {
|
||||
printf("common tik not saved\n");
|
||||
}
|
||||
|
||||
consoleUpdate(NULL);*/
|
||||
|
||||
tik_common_blk = tikGetCommonBlockFromTicket(&tik);
|
||||
|
||||
if (tik_common_blk)
|
||||
{
|
||||
cert_chain = certGenerateRawCertificateChainBySignatureIssuer(tik_common_blk->issuer, &cert_chain_size);
|
||||
if (cert_chain)
|
||||
{
|
||||
printf("cert chain succeeded | size: 0x%lX\n", cert_chain_size);
|
||||
consoleUpdate(NULL);
|
||||
|
||||
tmp_file = fopen("sdmc:/nxdt_test/chain.bin", "wb");
|
||||
if (tmp_file)
|
||||
{
|
||||
fwrite(cert_chain, 1, cert_chain_size, tmp_file);
|
||||
fclose(tmp_file);
|
||||
tmp_file = NULL;
|
||||
printf("cert chain saved\n");
|
||||
} else {
|
||||
printf("cert chain not saved\n");
|
||||
}
|
||||
} else {
|
||||
printf("cert chain failed\n");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
printf("tik failed\n");
|
||||
}
|
||||
|
||||
consoleUpdate(NULL);
|
||||
|
||||
NcaContext *nca_ctx = calloc(1, sizeof(NcaContext));
|
||||
if (nca_ctx)
|
||||
{
|
||||
printf("nca ctx buf succeeded\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
NcmContentStorage ncm_storage = {0};
|
||||
if (R_SUCCEEDED(ncmOpenContentStorage(&ncm_storage, NcmStorageId_SdCard)))
|
||||
{
|
||||
printf("ncm open storage succeeded\n");
|
||||
consoleUpdate(NULL);
|
||||
};*/
|
||||
|
||||
// Untitled Goose Game
|
||||
NcmPackagedContentInfo content_info = {
|
||||
|
@ -293,11 +91,45 @@ int main(int argc, char *argv[])
|
|||
}
|
||||
};
|
||||
|
||||
if (ncaInitializeContext(nca_ctx, NcmStorageId_SdCard, &ncm_storage, 0, &content_info, &tik))
|
||||
PartitionFileSystemContext pfs0_ctx = {0};
|
||||
PartitionFileSystemEntry *pfs0_entry = NULL;
|
||||
|
||||
buf = malloc(0x400000);
|
||||
if (!buf)
|
||||
{
|
||||
printf("nca initialize ctx succeeded\n");
|
||||
printf("read buf failed\n");
|
||||
goto out2;
|
||||
}
|
||||
|
||||
printf("read buf succeeded\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
nca_ctx = calloc(1, sizeof(NcaContext));
|
||||
if (!nca_ctx)
|
||||
{
|
||||
printf("nca ctx buf failed\n");
|
||||
goto out2;
|
||||
}
|
||||
|
||||
printf("nca ctx buf succeeded\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
rc = ncmOpenContentStorage(&ncm_storage, NcmStorageId_SdCard);
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
printf("ncm open storage failed\n");
|
||||
goto out2;
|
||||
}
|
||||
|
||||
printf("ncm open storage succeeded\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
if (!ncaInitializeContext(nca_ctx, NcmStorageId_SdCard, &ncm_storage, 0, &content_info, &tik))
|
||||
{
|
||||
printf("nca initialize ctx failed\n");
|
||||
goto out2;
|
||||
}
|
||||
|
||||
tmp_file = fopen("sdmc:/nxdt_test/nca_ctx.bin", "wb");
|
||||
if (tmp_file)
|
||||
{
|
||||
|
@ -314,13 +146,12 @@ int main(int argc, char *argv[])
|
|||
tmp_file = fopen("sdmc:/nxdt_test/section0.bin", "wb");
|
||||
if (tmp_file)
|
||||
{
|
||||
printf("nca section0 created: 0x%lX\n", nca_ctx->fs_contexts[0].section_size);
|
||||
consoleUpdate(NULL);
|
||||
|
||||
u64 curpos = 0;
|
||||
u64 blksize = (u64)0x400000;
|
||||
u64 blksize = 0x400000;
|
||||
u64 total = nca_ctx->fs_contexts[0].section_size;
|
||||
|
||||
printf("nca section0 created: 0x%lX\n", total);
|
||||
consoleUpdate(NULL);
|
||||
|
||||
for(u64 curpos = 0; curpos < total; curpos += blksize)
|
||||
{
|
||||
if (blksize > (total - curpos)) blksize = (total - curpos);
|
||||
|
@ -328,87 +159,122 @@ int main(int argc, char *argv[])
|
|||
if (!ncaReadFsSection(&(nca_ctx->fs_contexts[0]), buf, blksize, curpos))
|
||||
{
|
||||
printf("nca read section failed\n");
|
||||
consoleUpdate(NULL);
|
||||
break;
|
||||
goto out2;
|
||||
}
|
||||
|
||||
fwrite(buf, 1, blksize, tmp_file);
|
||||
|
||||
if (curpos == 0)
|
||||
{
|
||||
u8 cryptobuf[0x1E0] = {0};
|
||||
u64 block_size = 0, block_offset = 0;
|
||||
FILE *blktest = NULL;
|
||||
|
||||
u8 *block_data = ncaGenerateEncryptedFsSectionBlock(&(nca_ctx->fs_contexts[0]), buf + 0x809C, 0x1CE, 0x809C, &block_size, &block_offset);
|
||||
if (block_data)
|
||||
{
|
||||
printf("nca generate encrypted block success\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
blktest = fopen("sdmc:/nxdt_test/blktest.bin", "wb");
|
||||
if (blktest)
|
||||
{
|
||||
fwrite(block_data, 1, block_size, blktest);
|
||||
fclose(blktest);
|
||||
blktest = NULL;
|
||||
}
|
||||
|
||||
free(block_data);
|
||||
}
|
||||
|
||||
if (ncaReadContentFile(nca_ctx, cryptobuf, 0x1E0, nca_ctx->fs_contexts[0].section_offset + 0x8090))
|
||||
{
|
||||
printf("nca read encrypted block success\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
blktest = fopen("sdmc:/nxdt_test/crytobuf.bin", "wb");
|
||||
if (blktest)
|
||||
{
|
||||
fwrite(cryptobuf, 1, 0x1E0, blktest);
|
||||
fclose(blktest);
|
||||
blktest = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (curpos >= total)
|
||||
{
|
||||
printf("nca read section success\n");
|
||||
consoleUpdate(NULL);
|
||||
}
|
||||
|
||||
fclose(tmp_file);
|
||||
tmp_file = NULL;
|
||||
|
||||
printf("nca read section0 success\n");
|
||||
} else {
|
||||
printf("nca section0 not created\n");
|
||||
}
|
||||
} else {
|
||||
printf("nca initialize ctx failed\n");
|
||||
}
|
||||
|
||||
consoleUpdate(NULL);
|
||||
|
||||
ncmContentStorageClose(&ncm_storage);
|
||||
} else {
|
||||
printf("ncm open storage failed\n");
|
||||
}
|
||||
|
||||
free(nca_ctx);
|
||||
} else {
|
||||
printf("nca ctx buf failed\n");
|
||||
}
|
||||
|
||||
consoleUpdate(NULL);
|
||||
|
||||
|
||||
while(true)
|
||||
if (!pfs0InitializeContext(&pfs0_ctx, &(nca_ctx->fs_contexts[0])))
|
||||
{
|
||||
printf("pfs0 initialize ctx failed\n");
|
||||
goto out2;
|
||||
}
|
||||
|
||||
printf("pfs0 initialize ctx succeeded\n");
|
||||
consoleUpdate(NULL);
|
||||
|
||||
tmp_file = fopen("sdmc:/nxdt_test/pfs0_ctx.bin", "wb");
|
||||
if (tmp_file)
|
||||
{
|
||||
fwrite(&pfs0_ctx, 1, sizeof(PartitionFileSystemContext), tmp_file);
|
||||
fclose(tmp_file);
|
||||
tmp_file = NULL;
|
||||
printf("pfs0 ctx saved\n");
|
||||
} else {
|
||||
printf("pfs0 ctx not saved\n");
|
||||
}
|
||||
|
||||
consoleUpdate(NULL);
|
||||
|
||||
tmp_file = fopen("sdmc:/nxdt_test/pfs0_header.bin", "wb");
|
||||
if (tmp_file)
|
||||
{
|
||||
fwrite(pfs0_ctx.header, 1, pfs0_ctx.header_size, tmp_file);
|
||||
fclose(tmp_file);
|
||||
tmp_file = NULL;
|
||||
printf("pfs0 header saved\n");
|
||||
} else {
|
||||
printf("pfs0 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);
|
||||
|
||||
u64 main_npdm_offset = 0;
|
||||
if (!pfs0GetEntryDataOffset(&pfs0_ctx, pfs0_entry, &main_npdm_offset))
|
||||
{
|
||||
printf("pfs0 get entry data offset failed\n");
|
||||
goto out2;
|
||||
}
|
||||
|
||||
printf("main.npdm offset = 0x%lX\n", main_npdm_offset);
|
||||
consoleUpdate(NULL);
|
||||
|
||||
tmp_file = fopen("sdmc:/nxdt_test/main.npdm", "wb");
|
||||
if (tmp_file)
|
||||
{
|
||||
u64 blksize = 0x400000;
|
||||
u64 total = pfs0_entry->size;
|
||||
|
||||
printf("main.npdm created: 0x%lX\n", total);
|
||||
consoleUpdate(NULL);
|
||||
|
||||
for(u64 curpos = 0; curpos < total; curpos += blksize)
|
||||
{
|
||||
if (blksize > (total - curpos)) blksize = (total - curpos);
|
||||
|
||||
if (!ncaReadFsSection(pfs0_ctx.nca_fs_ctx, buf, blksize, main_npdm_offset + curpos))
|
||||
{
|
||||
printf("nca read section failed\n");
|
||||
goto out2;
|
||||
}
|
||||
|
||||
fwrite(buf, 1, blksize, tmp_file);
|
||||
}
|
||||
|
||||
fclose(tmp_file);
|
||||
tmp_file = NULL;
|
||||
|
||||
printf("nca read main.npdm success\n");
|
||||
} else {
|
||||
printf("main.npdm not created\n");
|
||||
}
|
||||
|
||||
out2:
|
||||
while(appletMainLoop())
|
||||
{
|
||||
consoleUpdate(NULL);
|
||||
hidScanInput();
|
||||
if (utilsHidKeysAllDown() & KEY_A) break;
|
||||
}
|
||||
|
||||
if (tmp_file) fclose(tmp_file);
|
||||
|
||||
pfs0FreeContext(&pfs0_ctx);
|
||||
|
||||
if (serviceIsActive(&(ncm_storage.s))) ncmContentStorageClose(&ncm_storage);
|
||||
|
||||
if (nca_ctx) free(nca_ctx);
|
||||
|
||||
if (buf) free(buf);
|
||||
|
||||
|
|
|
@ -222,7 +222,7 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, NcmContentStorage *ncm
|
|||
char nca_filename[0x30] = {0};
|
||||
sprintf(nca_filename, "%s.%s", out->content_id_str, out->content_type == NcmContentType_Meta ? "cnmt.nca" : "nca");
|
||||
|
||||
if (!gamecardGetOffsetAndSizeFromHashFileSystemPartitionEntryByName(hfs_partition_type, nca_filename, &(out->gamecard_offset), NULL))
|
||||
if (!gamecardGetEntryInfoFromHashFileSystemPartitionByName(hfs_partition_type, nca_filename, &(out->gamecard_offset), NULL))
|
||||
{
|
||||
LOGFILE("Error retrieving offset for \"%s\" entry in secure hash FS partition!", nca_filename);
|
||||
return false;
|
||||
|
@ -380,7 +380,7 @@ bool ncaReadContentFile(NcaContext *ctx, void *out, u64 read_size, u64 offset)
|
|||
} else {
|
||||
/* Retrieve NCA data using raw gamecard reads */
|
||||
/* Fixes NCA read issues with gamecards under HOS < 4.0.0 when using ncmContentStorageReadContentIdFile() */
|
||||
ret = gamecardRead(out, read_size, ctx->gamecard_offset + offset);
|
||||
ret = gamecardReadStorage(out, read_size, ctx->gamecard_offset + offset);
|
||||
if (!ret) LOGFILE("Failed to read 0x%lX bytes block at offset 0x%lX from NCA \"%s\"! (gamecard)", read_size, offset, ctx->content_id_str);
|
||||
}
|
||||
|
||||
|
|
14
source/nca.h
14
source/nca.h
|
@ -292,7 +292,7 @@ typedef struct {
|
|||
bool ncaAllocateCryptoBuffer(void);
|
||||
void ncaFreeCryptoBuffer(void);
|
||||
|
||||
/// Initializes a valid NCA context.
|
||||
/// Initializes a NCA context.
|
||||
/// If 'storage_id' != NcmStorageId_GameCard, the 'ncm_storage' argument must point to a valid NcmContentStorage instance, previously opened using the same NcmStorageId value.
|
||||
/// If 'storage_id' == NcmStorageId_GameCard, the 'hfs_partition_type' argument must be a valid GameCardHashFileSystemPartitionType value.
|
||||
/// If the NCA holds a populated Rights ID field, and if the Ticket object pointed to by 'tik' hasn't been filled, ticket data will be retrieved.
|
||||
|
@ -327,11 +327,9 @@ bool ncaEncryptHeader(NcaContext *ctx);
|
|||
|
||||
static inline void ncaConvertNcmContentSizeToU64(const u8 *size, u64 *out)
|
||||
{
|
||||
if (size && out)
|
||||
{
|
||||
if (!size || !out) return;
|
||||
*out = 0;
|
||||
memcpy(out, size, 6);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void ncaConvertU64ToNcmContentSize(const u64 *size, u8 *out)
|
||||
|
@ -341,20 +339,16 @@ static inline void ncaConvertU64ToNcmContentSize(const u64 *size, u8 *out)
|
|||
|
||||
static inline void ncaSetDownloadDistributionType(NcaContext *ctx)
|
||||
{
|
||||
if (ctx && ctx->header.distribution_type != NcaDistributionType_Download)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (ctx)
|
||||
{
|
||||
if (!ctx || !ctx->rights_id_available) return;
|
||||
memset(&(ctx->header.rights_id), 0, sizeof(FsRightsId));
|
||||
ctx->dirty_header = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* __NCA_H__ */
|
||||
|
|
107
source/pfs0.c
Normal file
107
source/pfs0.c
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* 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};
|
||||
|
||||
u64 main_npdm_offset = 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 && pfs0GetEntryDataOffset(out, main_npdm_entry, &main_npdm_offset) && \
|
||||
ncaReadFsSection(out->nca_fs_ctx, &magic, sizeof(u32), main_npdm_offset) && __builtin_bswap32(magic) == NPDM_META_MAGIC) out->is_exefs = true;
|
||||
|
||||
return true;
|
||||
}
|
127
source/pfs0.h
Normal file
127
source/pfs0.h
Normal file
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* 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));
|
||||
}
|
||||
|
||||
/// 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 bool pfs0GetEntryDataOffset(PartitionFileSystemContext *ctx, PartitionFileSystemEntry *fs_entry, u64 *out_offset)
|
||||
{
|
||||
if (!ctx || !ctx->header_size || !ctx->header || !fs_entry || !out_offset) return false;
|
||||
*out_offset = (ctx->offset + ctx->header_size + fs_entry->offset); /* Relative to the start of the NCA FS section */
|
||||
return true;
|
||||
}
|
||||
|
||||
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__ */
|
|
@ -204,7 +204,7 @@ static bool tikRetrieveTicketFromGameCardByRightsId(Ticket *dst, const FsRightsI
|
|||
utilsGenerateHexStringFromData(tik_filename, sizeof(tik_filename), id->c, 0x10);
|
||||
strcat(tik_filename, ".tik");
|
||||
|
||||
if (!gamecardGetOffsetAndSizeFromHashFileSystemPartitionEntryByName(GameCardHashFileSystemPartitionType_Secure, tik_filename, &tik_offset, &tik_size))
|
||||
if (!gamecardGetEntryInfoFromHashFileSystemPartitionByName(GameCardHashFileSystemPartitionType_Secure, tik_filename, &tik_offset, &tik_size))
|
||||
{
|
||||
LOGFILE("Error retrieving offset and size for \"%s\" entry in secure hash FS partition!");
|
||||
return false;
|
||||
|
@ -216,7 +216,7 @@ static bool tikRetrieveTicketFromGameCardByRightsId(Ticket *dst, const FsRightsI
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!gamecardRead(dst->data, tik_size, tik_offset))
|
||||
if (!gamecardReadStorage(dst->data, tik_size, tik_offset))
|
||||
{
|
||||
LOGFILE("Failed to read \"%s\" data from the inserted gamecard!", tik_filename);
|
||||
return false;
|
||||
|
|
Loading…
Reference in a new issue