1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-11-22 18:26:39 +00:00

PFS0 context.

This commit is contained in:
Pablo Curiel 2020-04-24 05:38:13 -04:00
parent 76550adab8
commit 7c8bf5c831
9 changed files with 663 additions and 447 deletions

View file

@ -1,7 +1,7 @@
todo:
hfs0 methods
pfs0: full header aligned to 0x20
pfs0: full header aligned to 0x20 (nsp)

View file

@ -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!");
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, 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))
fs_header = gamecardGetHashFileSystemPartitionHeader(hfs_partition_type, &hfs_partition_idx);
if (!fs_header)
{
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 header!");
goto out;
}
fs_entry = gamecardGetHashFileSystemEntryByIndex(fs_header, 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[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);
}

View file

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

View file

@ -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,364 +53,228 @@ 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
};*/
// Untitled Goose Game
NcmPackagedContentInfo content_info = {
.hash = {
0x8E, 0xF9, 0x20, 0xD4, 0x5E, 0xE1, 0x9E, 0xD1, 0xD2, 0x04, 0xC4, 0xC8, 0x22, 0x50, 0x79, 0xE8,
0x8E, 0xF9, 0x20, 0xD4, 0x5E, 0xE1, 0x9E, 0xD1, 0xD2, 0x04, 0xC4, 0xC8, 0x22, 0x50, 0x79, 0xE8
},
.info = {
.content_id = {
.c = { 0x8E, 0xF9, 0x20, 0xD4, 0x5E, 0xE1, 0x9E, 0xD1, 0xD2, 0x04, 0xC4, 0xC8, 0x22, 0x50, 0x79, 0xE8 }
},
.size = {
0x00, 0x40, 0xAD, 0x31, 0x00, 0x00
},
.content_type = NcmContentType_Program,
.id_offset = 0
}
};
if (tikRetrieveTicketByRightsId(&tik, &rights_id, false))
PartitionFileSystemContext pfs0_ctx = {0};
PartitionFileSystemEntry *pfs0_entry = NULL;
buf = malloc(0x400000);
if (!buf)
{
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");
printf("read buf failed\n");
goto out2;
}
printf("read buf succeeded\n");
consoleUpdate(NULL);
NcaContext *nca_ctx = calloc(1, sizeof(NcaContext));
if (nca_ctx)
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 = {
.hash = {
0x8E, 0xF9, 0x20, 0xD4, 0x5E, 0xE1, 0x9E, 0xD1, 0xD2, 0x04, 0xC4, 0xC8, 0x22, 0x50, 0x79, 0xE8,
0x8E, 0xF9, 0x20, 0xD4, 0x5E, 0xE1, 0x9E, 0xD1, 0xD2, 0x04, 0xC4, 0xC8, 0x22, 0x50, 0x79, 0xE8
},
.info = {
.content_id = {
.c = { 0x8E, 0xF9, 0x20, 0xD4, 0x5E, 0xE1, 0x9E, 0xD1, 0xD2, 0x04, 0xC4, 0xC8, 0x22, 0x50, 0x79, 0xE8 }
},
.size = {
0x00, 0x40, 0xAD, 0x31, 0x00, 0x00
},
.content_type = NcmContentType_Program,
.id_offset = 0
}
};
if (ncaInitializeContext(nca_ctx, NcmStorageId_SdCard, &ncm_storage, 0, &content_info, &tik))
{
printf("nca initialize ctx succeeded\n");
consoleUpdate(NULL);
tmp_file = fopen("sdmc:/nxdt_test/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");
}
consoleUpdate(NULL);
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 total = nca_ctx->fs_contexts[0].section_size;
for(u64 curpos = 0; curpos < total; curpos += blksize)
{
if (blksize > (total - curpos)) blksize = (total - curpos);
if (!ncaReadFsSection(&(nca_ctx->fs_contexts[0]), buf, blksize, curpos))
{
printf("nca read section failed\n");
consoleUpdate(NULL);
break;
}
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;
} 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");
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)
{
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");
}
consoleUpdate(NULL);
while(true)
tmp_file = fopen("sdmc:/nxdt_test/section0.bin", "wb");
if (tmp_file)
{
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);
if (!ncaReadFsSection(&(nca_ctx->fs_contexts[0]), buf, blksize, curpos))
{
printf("nca read section failed\n");
goto out2;
}
fwrite(buf, 1, blksize, tmp_file);
}
fclose(tmp_file);
tmp_file = NULL;
printf("nca read section0 success\n");
} else {
printf("nca section0 not created\n");
}
consoleUpdate(NULL);
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);

View file

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

View file

@ -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)
{
*out = 0;
memcpy(out, size, 6);
}
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)
{
ctx->header.distribution_type = NcaDistributionType_Download;
ctx->dirty_header = true;
}
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)
{
memset(&(ctx->header.rights_id), 0, sizeof(FsRightsId));
ctx->dirty_header = true;
}
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
View 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
View 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__ */

View file

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