1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-11-08 11:51:48 +00:00

Many changes.

* AES: moved CTR initializing/updating functions here from nca.c.
* BKTR/RomFS/PFS: check if we're dealing with a NCA with titlekey crypto and the titlekey hasn't been retrieved.
* BFTTF: use void pointers for output font data.
* Mem: Only exclude Unmapped/Uo/ThreadLocal/Reserved memory pages if dealing with FS.
* NCA: use content type context pointers inside NCA contexts to manage ContentMeta, ProgramInfo, Nacp and LegalInfo contexts.
* NCA: added 'written' bool elements to patch structs to indicate patch write completion.
* NPDM: remove unnecessary inline functions, generate PFS patch right after changing ACID data, add a pfsWriteEntryPatchToMemoryBuffer wrapper.
* PFS: added PartitionFileSystemFileContext and related functions to deal with NSP headers.
* ProgramInfo: removed unnecessary inline functions.
* Save: added commented code to dump a full system savefile - will probably use it down the road.
* Tik: added support for volatile tickets (thanks to @shchmue and @Whovian9369!), added a rights ID string representation to the Ticket struct, clear Volatile and ELicenseRequired flags in conversions to common tickets.
* Title: added a function to calculate the number of titles (current + siblings) from a TItleInfo block.
* Utils: added a function to generate a dynamically allocated path string using a prefix, a filename and a extension.
* Removed explicit offset checks throughout all the code.
* Codestyle fixes.
* Updated to-do.
This commit is contained in:
Pablo Curiel 2020-10-21 00:27:48 -04:00
parent bd98fd4c32
commit 15431ec2c8
32 changed files with 687 additions and 265 deletions

1
.gitignore vendored
View file

@ -7,6 +7,7 @@ build
/*.pfs0
/*.lst
/*.tar.bz2
/code_templates/nsp_dumper.c
/code_templates/tmp/*
/source/main.c
/*.log

View file

@ -21,13 +21,12 @@ for f in ./code_templates/*.c; do
rm -f ./source/main.c
cp $f ./source/main.c
{
make clean
make -j 12 BUILD_TYPE="$filename"
}
make clean &> /dev/null
make -j 12 BUILD_TYPE="$filename"
mkdir ./code_templates/tmp/$filename
cp ./nxdumptool-rewrite.nro ./code_templates/tmp/$filename/nxdumptool-rewrite.nro
#cp ./nxdumptool-rewrite.elf ./code_templates/tmp/$filename/nxdumptool-rewrite.elf
done
cd ./code_templates/tmp

View file

@ -17,7 +17,7 @@
g_titleInfo[i].version.minor_relstep);
fprintf(title_infos_txt, "Type: 0x%02X\r\n", g_titleInfo[i].meta_key.type);
fprintf(title_infos_txt, "Install Type: 0x%02X\r\n", g_titleInfo[i].meta_key.install_type);
fprintf(title_infos_txt, "Title Size: %s (0x%lX)\r\n", g_titleInfo[i].title_size_str, g_titleInfo[i].title_size);
fprintf(title_infos_txt, "Title Size: %s (0x%lX)\r\n", g_titleInfo[i].size_str, g_titleInfo[i].size);
fprintf(title_infos_txt, "Content Count: %u\r\n", g_titleInfo[i].content_count);
for(u32 j = 0; j < g_titleInfo[i].content_count; j++)

View file

@ -28,4 +28,56 @@
/// 'dst' and 'src' can both point to the same address.
size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const void *src, size_t size, u64 sector, size_t sector_size, bool encrypt);
/// Initializes an output AES partial counter using an initial CTR value and an offset.
/// The size for both 'out' and 'ctr' should be at least AES_BLOCK_SIZE.
NX_INLINE void aes128CtrInitializePartialCtr(u8 *out, const u8 *ctr, u64 offset)
{
if (!out || !ctr) return;
offset >>= 4;
for(u8 i = 0; i < 8; i++)
{
out[i] = ctr[0x8 - i - 1];
out[0x10 - i - 1] = (u8)(offset & 0xFF);
offset >>= 8;
}
}
/// Updates the provided AES partial counter using an offset.
/// 'out' size should be at least AES_BLOCK_SIZE.
NX_INLINE void aes128CtrUpdatePartialCtr(u8 *ctr, u64 offset)
{
if (!ctr) return;
offset >>= 4;
for(u8 i = 0; i < 8; i++)
{
ctr[0x10 - i - 1] = (u8)(offset & 0xFF);
offset >>= 8;
}
}
/// Updates the provided AES partial counter using an offset and a 32-bit CTR value.
/// 'out' size should be at least AES_BLOCK_SIZE.
NX_INLINE void aes128CtrUpdatePartialCtrEx(u8 *ctr, u32 ctr_val, u64 offset)
{
if (!ctr) return;
offset >>= 4;
for(u8 i = 0; i < 8; i++)
{
ctr[0x10 - i - 1] = (u8)(offset & 0xFF);
offset >>= 8;
}
for(u8 i = 0; i < 4; i++)
{
ctr[0x8 - i - 1] = (u8)(ctr_val & 0xFF);
ctr_val >>= 8;
}
}
#endif /* __AES_H__ */

View file

@ -40,7 +40,7 @@ typedef enum {
typedef struct {
u8 type; ///< BfttfFontType.
u32 size; ///< Decoded BFTFF font size.
u8 *ptr; ///< Pointer to font data.
void *ptr; ///< Pointer to font data.
} BfttfFontData;
/// Initializes the BFTTF interface.

View file

@ -44,7 +44,8 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct
__builtin_bswap32(update_nca_fs_ctx->header.patch_info.indirect_bucket.header.magic) != NCA_BKTR_MAGIC || \
__builtin_bswap32(update_nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket.header.magic) != NCA_BKTR_MAGIC || \
(update_nca_fs_ctx->header.patch_info.indirect_bucket.offset + update_nca_fs_ctx->header.patch_info.indirect_bucket.size) != update_nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket.offset || \
(update_nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket.offset + update_nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket.size) != update_nca_fs_ctx->section_size)
(update_nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket.offset + update_nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket.size) != update_nca_fs_ctx->section_size || \
(base_nca_ctx->rights_id_available && !base_nca_ctx->titlekey_retrieved) || (update_nca_ctx->rights_id_available && !update_nca_ctx->titlekey_retrieved))
{
LOGFILE("Invalid parameters!");
return false;
@ -218,7 +219,7 @@ end:
bool bktrReadFileSystemData(BktrContext *ctx, void *out, u64 read_size, u64 offset)
{
if (!ctx || !ctx->size || !out || !read_size || offset >= ctx->size || (offset + read_size) > ctx->size)
if (!ctx || !ctx->size || !out || !read_size || (offset + read_size) > ctx->size)
{
LOGFILE("Invalid parameters!");
return false;
@ -236,8 +237,7 @@ bool bktrReadFileSystemData(BktrContext *ctx, void *out, u64 read_size, u64 offs
bool bktrReadFileEntryData(BktrContext *ctx, RomFileSystemFileEntry *file_entry, void *out, u64 read_size, u64 offset)
{
if (!ctx || !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)
if (!ctx || !ctx->body_offset || !file_entry || !file_entry->size || (file_entry->offset + file_entry->size) > ctx->size || !out || !read_size || (offset + read_size) > file_entry->size)
{
LOGFILE("Invalid parameters!");
return false;
@ -255,7 +255,7 @@ bool bktrReadFileEntryData(BktrContext *ctx, RomFileSystemFileEntry *file_entry,
bool bktrIsFileEntryUpdated(BktrContext *ctx, RomFileSystemFileEntry *file_entry, bool *out)
{
if (!ctx || !ctx->body_offset || !ctx->indirect_block || !file_entry || !file_entry->size || file_entry->offset >= ctx->size || (file_entry->offset + file_entry->size) > ctx->size || !out)
if (!ctx || !ctx->body_offset || !ctx->indirect_block || !file_entry || !file_entry->size || (file_entry->offset + file_entry->size) > ctx->size || !out)
{
LOGFILE("Invalid parameters!");
return false;

View file

@ -90,17 +90,6 @@ typedef struct {
/// Initializes a BKTR context.
bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ctx, NcaFsSectionContext *update_nca_fs_ctx);
/// Cleanups a previously initialized BKTR context.
NX_INLINE void bktrFreeContext(BktrContext *ctx)
{
if (!ctx) return;
romfsFreeContext(&(ctx->base_romfs_ctx));
romfsFreeContext(&(ctx->patch_romfs_ctx));
if (ctx->indirect_block) free(ctx->indirect_block);
if (ctx->aes_ctr_ex_block) free(ctx->aes_ctr_ex_block);
memset(ctx, 0, sizeof(BktrContext));
}
/// Reads raw filesystem data using a BKTR context.
/// Input offset must be relative to the start of the patched RomFS image.
bool bktrReadFileSystemData(BktrContext *ctx, void *out, u64 read_size, u64 offset);
@ -113,46 +102,55 @@ bool bktrReadFileEntryData(BktrContext *ctx, RomFileSystemFileEntry *file_entry,
bool bktrIsFileEntryUpdated(BktrContext *ctx, RomFileSystemFileEntry *file_entry, bool *out);
/// Miscellaneous functions.
/// These are just wrappers for RomFS functions.
NX_INLINE void bktrFreeContext(BktrContext *ctx)
{
if (!ctx) return;
romfsFreeContext(&(ctx->base_romfs_ctx));
romfsFreeContext(&(ctx->patch_romfs_ctx));
if (ctx->indirect_block) free(ctx->indirect_block);
if (ctx->aes_ctr_ex_block) free(ctx->aes_ctr_ex_block);
memset(ctx, 0, sizeof(BktrContext));
}
NX_INLINE RomFileSystemDirectoryEntry *bktrGetDirectoryEntryByOffset(BktrContext *ctx, u32 dir_entry_offset)
{
return (ctx != NULL ? romfsGetDirectoryEntryByOffset(&(ctx->patch_romfs_ctx), dir_entry_offset) : NULL);
return (ctx ? romfsGetDirectoryEntryByOffset(&(ctx->patch_romfs_ctx), dir_entry_offset) : NULL);
}
NX_INLINE RomFileSystemFileEntry *bktrGetFileEntryByOffset(BktrContext *ctx, u32 file_entry_offset)
{
return (ctx != NULL ? romfsGetFileEntryByOffset(&(ctx->patch_romfs_ctx), file_entry_offset) : NULL);
return (ctx ? romfsGetFileEntryByOffset(&(ctx->patch_romfs_ctx), file_entry_offset) : NULL);
}
NX_INLINE bool bktrGetTotalDataSize(BktrContext *ctx, u64 *out_size)
{
return (ctx != NULL ? romfsGetTotalDataSize(&(ctx->patch_romfs_ctx), out_size) : false);
return (ctx ? romfsGetTotalDataSize(&(ctx->patch_romfs_ctx), out_size) : false);
}
NX_INLINE bool bktrGetDirectoryDataSize(BktrContext *ctx, RomFileSystemDirectoryEntry *dir_entry, u64 *out_size)
{
return (ctx != NULL ? romfsGetDirectoryDataSize(&(ctx->patch_romfs_ctx), dir_entry, out_size) : false);
return (ctx ? romfsGetDirectoryDataSize(&(ctx->patch_romfs_ctx), dir_entry, out_size) : false);
}
NX_INLINE RomFileSystemDirectoryEntry *bktrGetDirectoryEntryByPath(BktrContext *ctx, const char *path)
{
return (ctx != NULL ? romfsGetDirectoryEntryByPath(&(ctx->patch_romfs_ctx), path) : NULL);
return (ctx ? romfsGetDirectoryEntryByPath(&(ctx->patch_romfs_ctx), path) : NULL);
}
NX_INLINE RomFileSystemFileEntry *bktrGetFileEntryByPath(BktrContext *ctx, const char *path)
{
return (ctx != NULL ? romfsGetFileEntryByPath(&(ctx->patch_romfs_ctx), path) : NULL);
return (ctx ? romfsGetFileEntryByPath(&(ctx->patch_romfs_ctx), path) : NULL);
}
NX_INLINE bool bktrGeneratePathFromDirectoryEntry(BktrContext *ctx, RomFileSystemDirectoryEntry *dir_entry, char *out_path, size_t out_path_size, u8 illegal_char_replace_type)
{
return (ctx != NULL ? romfsGeneratePathFromDirectoryEntry(&(ctx->patch_romfs_ctx), dir_entry, out_path, out_path_size, illegal_char_replace_type) : false);
return (ctx ? romfsGeneratePathFromDirectoryEntry(&(ctx->patch_romfs_ctx), dir_entry, out_path, out_path_size, illegal_char_replace_type) : false);
}
NX_INLINE bool bktrGeneratePathFromFileEntry(BktrContext *ctx, RomFileSystemFileEntry *file_entry, char *out_path, size_t out_path_size, u8 illegal_char_replace_type)
{
return (ctx != NULL ? romfsGeneratePathFromFileEntry(&(ctx->patch_romfs_ctx), file_entry, out_path, out_path_size, illegal_char_replace_type) : false);
return (ctx ? romfsGeneratePathFromFileEntry(&(ctx->patch_romfs_ctx), file_entry, out_path, out_path_size, illegal_char_replace_type) : false);
}
#endif /* __BKTR_H__ */

View file

@ -124,9 +124,6 @@ bool cnmtInitializeContext(ContentMetaContext *out, NcaContext *nca_ctx)
/* Calculate SHA-256 checksum for the whole raw CNMT. */
sha256CalculateHash(out->raw_data_hash, out->raw_data, out->raw_data_size);
/* Save pointer to NCA context to the output CNMT context. */
out->nca_ctx = nca_ctx;
/* Verify packaged header. */
out->packaged_header = (ContentMetaPackagedContentMetaHeader*)out->raw_data;
cur_offset += sizeof(ContentMetaPackagedContentMetaHeader);
@ -235,6 +232,13 @@ bool cnmtInitializeContext(ContentMetaContext *out, NcaContext *nca_ctx)
goto end;
}
/* Update output context. */
out->nca_ctx = nca_ctx;
/* Update content type context info in NCA context. */
nca_ctx->content_type_ctx = out;
nca_ctx->content_type_ctx_patch = false;
success = true;
end:

View file

@ -102,7 +102,7 @@ static u8 g_gameCardStorageCurrentArea = GameCardStorageArea_None;
static u8 *g_gameCardReadBuf = NULL;
static GameCardHeader g_gameCardHeader = {0};
static u64 g_gameCardStorageNormalAreaSize = 0, g_gameCardStorageSecureAreaSize = 0;
static u64 g_gameCardStorageNormalAreaSize = 0, g_gameCardStorageSecureAreaSize = 0, g_gameCardStorageTotalSize = 0;
static u64 g_gameCardCapacity = 0;
static u8 *g_gameCardHfsRootHeader = NULL; /// GameCardHashFileSystemHeader + (entry_count * GameCardHashFileSystemEntry) + Name Table.
@ -325,7 +325,7 @@ bool gamecardGetTotalSize(u64 *out)
{
mutexLock(&g_gamecardMutex);
bool ret = (g_gameCardInserted && g_gameCardInfoLoaded && out);
if (ret) *out = (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize);
if (ret) *out = g_gameCardStorageTotalSize;
mutexUnlock(&g_gamecardMutex);
return ret;
}
@ -744,8 +744,7 @@ static void gamecardFreeInfo(void)
{
memset(&g_gameCardHeader, 0, sizeof(GameCardHeader));
g_gameCardStorageNormalAreaSize = 0;
g_gameCardStorageSecureAreaSize = 0;
g_gameCardStorageNormalAreaSize = g_gameCardStorageSecureAreaSize = g_gameCardStorageTotalSize = 0;
g_gameCardCapacity = 0;
@ -807,6 +806,8 @@ static bool gamecardReadInitialData(GameCardKeyArea *out)
/* Look for the initial data block in the FS memory dump using the package ID and the initial data hash from the gamecard header. */
for(u64 offset = 0; offset < g_fsProgramMemory.data_size; offset++)
{
if ((g_fsProgramMemory.data_size - offset) < sizeof(GameCardInitialData)) break;
if (memcmp(g_fsProgramMemory.data + offset, &(g_gameCardHeader.package_id), sizeof(g_gameCardHeader.package_id)) != 0) continue;
sha256CalculateHash(tmp_hash, g_fsProgramMemory.data + offset, sizeof(GameCardInitialData));
@ -910,8 +911,7 @@ static bool gamecardReadStorageArea(void *out, u64 read_size, u64 offset, bool l
bool success = false;
if (!g_gameCardInserted || !g_gameCardStorageNormalAreaSize || !g_gameCardStorageSecureAreaSize || !out || !read_size || \
offset >= (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize) || (offset + read_size) > (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize))
if (!g_gameCardInserted || !g_gameCardStorageNormalAreaSize || !g_gameCardStorageSecureAreaSize || !out || !read_size || (offset + read_size) > g_gameCardStorageTotalSize)
{
LOGFILE("Invalid parameters!");
goto end;
@ -1026,7 +1026,7 @@ static bool gamecardGetStorageAreasSizes(void)
if (R_FAILED(rc) || !area_size)
{
LOGFILE("fsStorageGetSize failed to retrieve %s storage area size! (0x%08X).", GAMECARD_STORAGE_AREA_NAME(area), rc);
g_gameCardStorageNormalAreaSize = g_gameCardStorageSecureAreaSize = 0;
g_gameCardStorageNormalAreaSize = g_gameCardStorageSecureAreaSize = g_gameCardStorageTotalSize = 0;
return false;
}
@ -1038,6 +1038,8 @@ static bool gamecardGetStorageAreasSizes(void)
}
}
g_gameCardStorageTotalSize = (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize);
return true;
}

View file

@ -59,7 +59,7 @@ typedef struct {
u8 eticket_rsa_kek_personalized[0x10]; ///< eTicket RSA kek (console-specific).
u8 titlekeks[0x20][0x10]; ///< Titlekey encryption keys.
///< Needed to reencrypt the NCA key area for tik-less NSP dumps. Retrieved from the Lockpick_RCM keys file.
///< Needed to reencrypt the key area from NCAs with titlekey crypto removed. Retrieved from the Lockpick_RCM keys file.
u8 key_area_keys[0x20][3][0x10]; ///< Key area encryption keys.
} keysNcaKeyset;
@ -518,7 +518,7 @@ static bool keysReadKeysFromFile(void)
FILE *keys_file = NULL;
char *key = NULL, *value = NULL;
char test_name[0x40] = {0};
bool parse_fail = false;
bool parse_fail = false, eticket_rsa_kek_available = false;
keys_file = fopen(KEYS_FILE_PATH, "rb");
if (!keys_file)
@ -538,12 +538,14 @@ static bool keysReadKeysFromFile(void)
if (strlen(key) == 15 && !strcasecmp(key, "eticket_rsa_kek"))
{
if ((parse_fail = !keysParseHexKey(g_ncaKeyset.eticket_rsa_kek, key, value, sizeof(g_ncaKeyset.eticket_rsa_kek)))) break;
eticket_rsa_kek_available = true;
key_count++;
} else
if (strlen(key) == 28 && !strcasecmp(key, "eticket_rsa_kek_personalized"))
{
/* This only appears on consoles that use the new PRODINFO key generation scheme. */
if ((parse_fail = !keysParseHexKey(g_ncaKeyset.eticket_rsa_kek_personalized, key, value, sizeof(g_ncaKeyset.eticket_rsa_kek_personalized)))) break;
eticket_rsa_kek_available = true;
key_count++;
} else {
for(u32 i = 0; i < 0x20; i++)
@ -593,5 +595,11 @@ static bool keysReadKeysFromFile(void)
return false;
}
if (!eticket_rsa_kek_available)
{
LOGFILE("\"eticket_rsa_kek\" unavailable in \"%s\"!", KEYS_FILE_PATH);
return false;
}
return true;
}

View file

@ -81,6 +81,10 @@ bool legalInfoInitializeContext(LegalInfoContext *out, NcaContext *nca_ctx)
/* Update NCA context pointer in output context. */
out->nca_ctx = nca_ctx;
/* Update content type context info in NCA context. */
nca_ctx->content_type_ctx = out;
nca_ctx->content_type_ctx_patch = false;
success = true;
end:

View file

@ -128,8 +128,9 @@ static bool memRetrieveProgramMemory(MemoryLocation *location, bool is_segment)
mem_type = (u8)(mem_info.type & 0xFF);
/* Code to allow for bitmasking segments. */
if ((mem_info.perm & Perm_R) && ((!is_segment && mem_type != MemType_Unmapped && mem_type != MemType_Io && mem_type != MemType_ThreadLocal && mem_type != MemType_Reserved && !mem_info.attr) || \
(is_segment && (mem_type == MemType_CodeStatic || mem_type == MemType_CodeMutable) && (((segment <<= 1) >> 1) & location->mask))))
if ((mem_info.perm & Perm_R) && ((!is_segment && !mem_info.attr && (location->program_id != FS_SYSMODULE_TID || (location->program_id == FS_SYSMODULE_TID && mem_type != MemType_Unmapped && \
mem_type != MemType_Io && mem_type != MemType_ThreadLocal && mem_type != MemType_Reserved))) || (is_segment && (mem_type == MemType_CodeStatic || mem_type == MemType_CodeMutable) && \
(((segment <<= 1) >> 1) & location->mask))))
{
/* If location->data == NULL, realloc() will essentially act as a malloc(). */
tmp = realloc(location->data, location->data_size + mem_info.size);

View file

@ -27,6 +27,7 @@
#define FS_SYSMODULE_TID (u64)0x0100000000000000
#define BOOT_SYSMODULE_TID (u64)0x0100000000000005
#define SPL_SYSMODULE_TID (u64)0x0100000000000028
#define ES_SYSMODULE_TID (u64)0x0100000000000033
typedef enum {
MemoryProgramSegmentType_Text = BIT(0),
@ -46,8 +47,8 @@ typedef struct {
bool memRetrieveProgramMemorySegment(MemoryLocation *location);
/// Retrieves full memory data from a running program.
/// These are any type of memory pages with read permission (Perm_R) enabled.
/// MemType_Unmapped, MemType_Io, MemType_ThreadLocal and MemType_Reserved memory pages are excluded, as well as memory pages with a populated MemoryAttribute value.
/// These are any type of memory pages with read permission (Perm_R) enabled and no MemoryAttribute flag set.
/// MemType_Unmapped, MemType_Io, MemType_ThreadLocal and MemType_Reserved memory pages are excluded if FS program memory is being retrieved, in order to avoid hangs.
bool memRetrieveFullProgramMemory(MemoryLocation *location);
/// Frees a populated MemoryLocation element.

View file

@ -255,9 +255,6 @@ bool nacpInitializeContext(NacpContext *out, NcaContext *nca_ctx)
/* Calculate SHA-256 checksum for the whole NACP. */
sha256CalculateHash(out->data_hash, out->data, sizeof(_NacpStruct));
/* Save pointer to NCA context to the output NACP context. */
out->nca_ctx = nca_ctx;
/* Retrieve NACP icon data. */
for(u8 i = 0; i < NacpSupportedLanguage_Count; i++)
{
@ -317,6 +314,13 @@ bool nacpInitializeContext(NacpContext *out, NcaContext *nca_ctx)
out->icon_count++;
}
/* Update NCA context pointer in output context. */
out->nca_ctx = nca_ctx;
/* Update content type context info in NCA context. */
nca_ctx->content_type_ctx = out;
nca_ctx->content_type_ctx_patch = false;
success = true;
end:

View file

@ -51,15 +51,11 @@ NX_INLINE bool ncaIsVersion0KeyAreaEncrypted(NcaContext *ctx);
NX_INLINE u8 ncaGetKeyGenerationValue(NcaContext *ctx);
NX_INLINE bool ncaCheckRightsIdAvailability(NcaContext *ctx);
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);
static bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, u32 ctr_val, bool lock);
static bool ncaGenerateHashDataPatch(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, void *out, bool is_integrity_patch);
static void ncaWritePatchToMemoryBuffer(NcaContext *ctx, const void *patch, u64 patch_offset, u64 patch_size, void *buf, u64 buf_offset, u64 buf_size);
static bool ncaWritePatchToMemoryBuffer(NcaContext *ctx, const void *patch, u64 patch_size, u64 patch_offset, void *buf, u64 buf_size, u64 buf_offset);
static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset, bool lock);
@ -217,8 +213,8 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type,
if ((!out->rights_id_available || (out->rights_id_available && out->titlekey_retrieved)) && fs_ctx->encryption_type > NcaEncryptionType_None && \
fs_ctx->encryption_type <= NcaEncryptionType_AesCtrEx)
{
/* Initialize section CTR. */
ncaInitializeAesCtrIv(fs_ctx->ctr, fs_ctx->header.aes_ctr_upper_iv.value, fs_ctx->section_offset);
/* Initialize the partial AES counter for this section. */
aes128CtrInitializePartialCtr(fs_ctx->ctr, fs_ctx->header.aes_ctr_upper_iv.value, fs_ctx->section_offset);
/* Initialize AES context. */
if (out->rights_id_available)
@ -249,7 +245,7 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type,
bool ncaReadContentFile(NcaContext *ctx, void *out, u64 read_size, u64 offset)
{
if (!ctx || !*(ctx->content_id_str) || (ctx->storage_id != NcmStorageId_GameCard && !ctx->ncm_storage) || (ctx->storage_id == NcmStorageId_GameCard && !ctx->gamecard_offset) || !out || \
!read_size || offset >= ctx->content_size || (offset + read_size) > ctx->content_size)
!read_size || (offset + read_size) > ctx->content_size)
{
LOGFILE("Invalid parameters!");
return false;
@ -297,13 +293,18 @@ bool ncaGenerateHierarchicalSha256Patch(NcaFsSectionContext *ctx, const void *da
void ncaWriteHierarchicalSha256PatchToMemoryBuffer(NcaContext *ctx, NcaHierarchicalSha256Patch *patch, void *buf, u64 buf_size, u64 buf_offset)
{
if (!ctx || !*(ctx->content_id_str) || ctx->content_size < NCA_FULL_HEADER_LENGTH || !patch || memcmp(patch->content_id.c, ctx->content_id.c, 0x10) != 0 || !patch->hash_region_count || \
patch->hash_region_count > NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT || !buf || !buf_size || buf_offset >= ctx->content_size || (buf_offset + buf_size) > ctx->content_size) return;
if (!ctx || !*(ctx->content_id_str) || ctx->content_size < NCA_FULL_HEADER_LENGTH || !patch || patch->written || memcmp(patch->content_id.c, ctx->content_id.c, 0x10) != 0 || \
!patch->hash_region_count || patch->hash_region_count > NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT || !buf || !buf_size || (buf_offset + buf_size) > ctx->content_size) return;
patch->written = true;
for(u32 i = 0; i < patch->hash_region_count; i++)
{
NcaHashDataPatch *hash_region_patch = &(patch->hash_region_patch[i]);
ncaWritePatchToMemoryBuffer(ctx, hash_region_patch->data, hash_region_patch->offset, hash_region_patch->size, buf, buf_offset, buf_size);
if (hash_region_patch->written) continue;
hash_region_patch->written = ncaWritePatchToMemoryBuffer(ctx, hash_region_patch->data, hash_region_patch->size, hash_region_patch->offset, buf, buf_size, buf_offset);
if (!hash_region_patch->written) patch->written = false;
}
}
@ -314,19 +315,34 @@ bool ncaGenerateHierarchicalIntegrityPatch(NcaFsSectionContext *ctx, const void
void ncaWriteHierarchicalIntegrityPatchToMemoryBuffer(NcaContext *ctx, NcaHierarchicalIntegrityPatch *patch, void *buf, u64 buf_size, u64 buf_offset)
{
if (!ctx || !*(ctx->content_id_str) || ctx->content_size < NCA_FULL_HEADER_LENGTH || !patch || memcmp(patch->content_id.c, ctx->content_id.c, 0x10) != 0 || !buf || !buf_size || \
buf_offset >= ctx->content_size || (buf_offset + buf_size) > ctx->content_size) return;
if (!ctx || !*(ctx->content_id_str) || ctx->content_size < NCA_FULL_HEADER_LENGTH || !patch || patch->written || memcmp(patch->content_id.c, ctx->content_id.c, 0x10) != 0 || !buf || !buf_size || \
(buf_offset + buf_size) > ctx->content_size) return;
patch->written = true;
for(u32 i = 0; i < NCA_IVFC_LEVEL_COUNT; i++)
{
NcaHashDataPatch *hash_level_patch = &(patch->hash_level_patch[i]);
ncaWritePatchToMemoryBuffer(ctx, hash_level_patch->data, hash_level_patch->offset, hash_level_patch->size, buf, buf_offset, buf_size);
if (hash_level_patch->written) continue;
hash_level_patch->written = ncaWritePatchToMemoryBuffer(ctx, hash_level_patch->data, hash_level_patch->size, hash_level_patch->offset, buf, buf_size, buf_offset);
if (!hash_level_patch->written) patch->written = false;
}
}
void ncaSetDownloadDistributionType(NcaContext *ctx)
{
if (!ctx || ctx->content_size < NCA_FULL_HEADER_LENGTH || !*(ctx->content_id_str) || ctx->content_type > NcmContentType_DeltaFragment || \
ctx->header.distribution_type == NcaDistributionType_Download) return;
LOGFILE("Setting download distribution type to %s NCA \"%s\".", titleGetNcmContentTypeName(ctx->content_type), ctx->content_id_str);
ctx->header.distribution_type = NcaDistributionType_Download;
}
void ncaRemoveTitlekeyCrypto(NcaContext *ctx)
{
if (!ctx || ctx->content_size < NCA_FULL_HEADER_LENGTH || !ctx->rights_id_available || !ctx->titlekey_retrieved) return;
if (!ctx || ctx->content_size < NCA_FULL_HEADER_LENGTH || !*(ctx->content_id_str) || ctx->content_type > NcmContentType_DeltaFragment || !ctx->rights_id_available || !ctx->titlekey_retrieved) return;
LOGFILE("Removing titlekey crypto from %s NCA \"%s\".", titleGetNcmContentTypeName(ctx->content_type), ctx->content_id_str);
/* Copy decrypted titlekey to the decrypted NCA key area. */
/* This will be reencrypted at a later stage. */
@ -413,11 +429,12 @@ void ncaWriteEncryptedHeaderDataToMemoryBuffer(NcaContext *ctx, void *buf, u64 b
{
/* Return right away if we're dealing with invalid parameters, or if the buffer data is not part of the range covered by the encrypted header data (NCA2/3 optimization, last condition). */
/* In order to avoid taking up too much execution time when this function is called (ideally inside a loop), we won't use ncaIsHeaderDirty() here. Let the user take care of it instead. */
if (!ctx || !*(ctx->content_id_str) || ctx->content_size < NCA_FULL_HEADER_LENGTH || !buf || !buf_size || buf_offset >= ctx->content_size || \
(buf_offset + buf_size) > ctx->content_size || (ctx->format_version != NcaVersion_Nca0 && buf_offset >= NCA_FULL_HEADER_LENGTH)) return;
if (!ctx || ctx->content_size < NCA_FULL_HEADER_LENGTH || !buf || !buf_size || (buf_offset + buf_size) > ctx->content_size || \
(ctx->format_version != NcaVersion_Nca0 && buf_offset >= NCA_FULL_HEADER_LENGTH)) return;
/* Attempt to write the NCA header. */
if (buf_offset < sizeof(NcaHeader)) ncaWritePatchToMemoryBuffer(ctx, &(ctx->encrypted_header), 0, sizeof(NcaHeader), buf, buf_offset, buf_size);
/* Return right away if the NCA header was only partially written. */
if (buf_offset < sizeof(NcaHeader) && !ncaWritePatchToMemoryBuffer(ctx, &(ctx->encrypted_header), sizeof(NcaHeader), 0, buf, buf_size, buf_offset)) return;
/* Attempt to write NCA FS section headers. */
for(u8 i = 0; i < NCA_FS_HEADER_COUNT; i++)
@ -426,7 +443,7 @@ void ncaWriteEncryptedHeaderDataToMemoryBuffer(NcaContext *ctx, void *buf, u64 b
if (!fs_ctx->enabled) continue;
u64 fs_header_offset = (ctx->format_version != NcaVersion_Nca0 ? (sizeof(NcaHeader) + (i * sizeof(NcaFsHeader))) : fs_ctx->section_offset);
ncaWritePatchToMemoryBuffer(ctx, &(fs_ctx->encrypted_header), fs_header_offset, sizeof(NcaFsHeader), buf, buf_offset, buf_size);
ncaWritePatchToMemoryBuffer(ctx, &(fs_ctx->encrypted_header), sizeof(NcaFsHeader), fs_header_offset, buf, buf_size, buf_offset);
}
}
@ -677,52 +694,6 @@ NX_INLINE bool ncaCheckRightsIdAvailability(NcaContext *ctx)
return rights_id_available;
}
NX_INLINE void ncaInitializeAesCtrIv(u8 *out, const u8 *ctr, u64 offset)
{
if (!out || !ctr) return;
offset >>= 4;
for(u8 i = 0; i < 8; i++)
{
out[i] = ctr[0x8 - i - 1];
out[0x10 - i - 1] = (u8)(offset & 0xFF);
offset >>= 8;
}
}
NX_INLINE void ncaUpdateAesCtrIv(u8 *ctr, u64 offset)
{
if (!ctr) return;
offset >>= 4;
for(u8 i = 0; i < 8; i++)
{
ctr[0x10 - i - 1] = (u8)(offset & 0xFF);
offset >>= 8;
}
}
NX_INLINE void ncaUpdateAesCtrExIv(u8 *ctr, u32 ctr_val, u64 offset)
{
if (!ctr) return;
offset >>= 4;
for(u8 i = 0; i < 8; i++)
{
ctr[0x10 - i - 1] = (u8)(offset & 0xFF);
offset >>= 8;
}
for(u8 i = 0; i < 4; i++)
{
ctr[0x8 - i - 1] = (u8)(ctr_val & 0xFF);
ctr_val >>= 8;
}
}
static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, bool lock)
{
if (lock) mutexLock(&g_ncaCryptoBufferMutex);
@ -731,7 +702,7 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size
if (!g_ncaCryptoBuffer || !ctx || !ctx->enabled || !ctx->nca_ctx || ctx->section_num >= NCA_FS_HEADER_COUNT || ctx->section_offset < sizeof(NcaHeader) || \
ctx->section_type >= NcaFsSectionType_Invalid || ctx->encryption_type == NcaEncryptionType_Auto || ctx->encryption_type > NcaEncryptionType_AesCtrEx || !out || !read_size || \
offset >= ctx->section_size || (offset + read_size) > ctx->section_size)
(offset + read_size) > ctx->section_size)
{
LOGFILE("Invalid NCA FS section header parameters!");
goto end;
@ -747,8 +718,7 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size
u64 data_start_offset = 0, chunk_size = 0, out_chunk_size = 0;
if (!*(nca_ctx->content_id_str) || (nca_ctx->storage_id != NcmStorageId_GameCard && !nca_ctx->ncm_storage) || (nca_ctx->storage_id == NcmStorageId_GameCard && !nca_ctx->gamecard_offset) || \
(nca_ctx->format_version != NcaVersion_Nca0 && nca_ctx->format_version != NcaVersion_Nca2 && nca_ctx->format_version != NcaVersion_Nca3) || content_offset >= nca_ctx->content_size || \
(content_offset + read_size) > nca_ctx->content_size)
(nca_ctx->format_version != NcaVersion_Nca0 && nca_ctx->format_version != NcaVersion_Nca2 && nca_ctx->format_version != NcaVersion_Nca3) || (content_offset + read_size) > nca_ctx->content_size)
{
LOGFILE("Invalid NCA header parameters!");
goto end;
@ -788,7 +758,7 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size
} else
if (ctx->encryption_type == NcaEncryptionType_AesCtr || ctx->encryption_type == NcaEncryptionType_AesCtrEx)
{
ncaUpdateAesCtrIv(ctx->ctr, content_offset);
aes128CtrUpdatePartialCtr(ctx->ctr, content_offset);
aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
aes128CtrCrypt(&(ctx->ctr_ctx), out, out, read_size);
}
@ -829,7 +799,7 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size
} else
if (ctx->encryption_type == NcaEncryptionType_AesCtr || ctx->encryption_type == NcaEncryptionType_AesCtrEx)
{
ncaUpdateAesCtrIv(ctx->ctr, block_start_offset);
aes128CtrUpdatePartialCtr(ctx->ctr, block_start_offset);
aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
aes128CtrCrypt(&(ctx->ctr_ctx), g_ncaCryptoBuffer, g_ncaCryptoBuffer, chunk_size);
}
@ -852,8 +822,7 @@ static bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, voi
bool ret = false;
if (!g_ncaCryptoBuffer || !ctx || !ctx->enabled || !ctx->nca_ctx || ctx->section_num >= NCA_FS_HEADER_COUNT || ctx->section_offset < sizeof(NcaHeader) || \
ctx->section_type != NcaFsSectionType_PatchRomFs || ctx->encryption_type != NcaEncryptionType_AesCtrEx || !out || !read_size || offset >= ctx->section_size || \
(offset + read_size) > ctx->section_size)
ctx->section_type != NcaFsSectionType_PatchRomFs || ctx->encryption_type != NcaEncryptionType_AesCtrEx || !out || !read_size || (offset + read_size) > ctx->section_size)
{
LOGFILE("Invalid NCA FS section header parameters!");
goto end;
@ -866,7 +835,7 @@ static bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, voi
u64 data_start_offset = 0, chunk_size = 0, out_chunk_size = 0;
if (!*(nca_ctx->content_id_str) || (nca_ctx->storage_id != NcmStorageId_GameCard && !nca_ctx->ncm_storage) || (nca_ctx->storage_id == NcmStorageId_GameCard && !nca_ctx->gamecard_offset) || \
content_offset >= nca_ctx->content_size || (content_offset + read_size) > nca_ctx->content_size)
(content_offset + read_size) > nca_ctx->content_size)
{
LOGFILE("Invalid NCA header parameters!");
goto end;
@ -883,7 +852,7 @@ static bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, voi
}
/* Decrypt data */
ncaUpdateAesCtrExIv(ctx->ctr, ctr_val, content_offset);
aes128CtrUpdatePartialCtrEx(ctx->ctr, ctr_val, content_offset);
aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
aes128CtrCrypt(&(ctx->ctr_ctx), out, out, read_size);
@ -909,7 +878,7 @@ static bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, voi
}
/* Decrypt data. */
ncaUpdateAesCtrExIv(ctx->ctr, ctr_val, block_start_offset);
aes128CtrUpdatePartialCtrEx(ctx->ctr, ctr_val, block_start_offset);
aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
aes128CtrCrypt(&(ctx->ctr_ctx), g_ncaCryptoBuffer, g_ncaCryptoBuffer, chunk_size);
@ -949,7 +918,7 @@ static bool ncaGenerateHashDataPatch(NcaFsSectionContext *ctx, const void *data,
(is_integrity_patch && (ctx->header.hash_type != NcaHashType_HierarchicalIntegrity || \
!(layer_count = (ctx->header.hash_data.integrity_meta_info.info_level_hash.max_level_count - 1)) || layer_count != NCA_IVFC_LEVEL_COUNT || \
!(last_layer_size = ctx->header.hash_data.integrity_meta_info.info_level_hash.level_information[NCA_IVFC_LEVEL_COUNT - 1].size))) || !data || !data_size || \
data_offset >= last_layer_size || (data_offset + data_size) > last_layer_size || !out)
(data_offset + data_size) > last_layer_size || !out)
{
LOGFILE("Invalid parameters!");
goto end;
@ -998,8 +967,8 @@ static bool ncaGenerateHashDataPatch(NcaFsSectionContext *ctx, const void *data,
}
/* Validate layer properties. */
if (hash_block_size <= 1 || cur_layer_offset >= ctx->section_size || !cur_layer_size || (cur_layer_offset + cur_layer_size) > ctx->section_size || \
(i > 1 && (parent_layer_offset >= ctx->section_size || !parent_layer_size || (parent_layer_offset + parent_layer_size) > ctx->section_size)))
if (hash_block_size <= 1 || !cur_layer_size || (cur_layer_offset + cur_layer_size) > ctx->section_size || (i > 1 && (!parent_layer_size || \
(parent_layer_offset + parent_layer_size) > ctx->section_size)))
{
LOGFILE("Invalid hierarchical parent/child layer!");
goto end;
@ -1143,11 +1112,11 @@ end:
return success;
}
static void ncaWritePatchToMemoryBuffer(NcaContext *ctx, const void *patch, u64 patch_offset, u64 patch_size, void *buf, u64 buf_offset, u64 buf_size)
static bool ncaWritePatchToMemoryBuffer(NcaContext *ctx, const void *patch, u64 patch_size, u64 patch_offset, void *buf, u64 buf_size, u64 buf_offset)
{
/* Return right away if we're dealing with invalid parameters, or if the buffer data is not part of the range covered by the patch (last two conditions). */
if (!ctx || !patch || patch_offset >= ctx->content_size || !patch_size || (patch_offset + patch_size) > ctx->content_size || (buf_offset + buf_size) <= patch_offset || \
(patch_offset + patch_size) <= buf_offset) return;
if (!ctx || !patch || !patch_size || (patch_offset + patch_size) > ctx->content_size || (buf_offset + buf_size) <= patch_offset || \
(patch_offset + patch_size) <= buf_offset) return false;
/* Overwrite buffer data using patch data. */
u64 patch_block_offset = (patch_offset < buf_offset ? (buf_offset - patch_offset) : 0);
@ -1161,6 +1130,8 @@ static void ncaWritePatchToMemoryBuffer(NcaContext *ctx, const void *patch, u64
memcpy((u8*)buf + buf_block_offset, (const u8*)patch + patch_block_offset, buf_block_size);
LOGFILE("Overwrote 0x%lX bytes block at offset 0x%lX from raw NCA \"%s\" buffer (size 0x%lX, NCA offset 0x%lX).", buf_block_size, buf_block_offset, ctx->content_id_str, buf_size, buf_offset);
return ((patch_block_offset + buf_block_size) == patch_size);
}
static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset, bool lock)
@ -1172,7 +1143,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const
if (!g_ncaCryptoBuffer || !ctx || !ctx->enabled || !ctx->nca_ctx || ctx->section_num >= NCA_FS_HEADER_COUNT || ctx->section_offset < sizeof(NcaHeader) || \
ctx->section_type >= NcaFsSectionType_Invalid || ctx->encryption_type == NcaEncryptionType_Auto || ctx->encryption_type >= NcaEncryptionType_AesCtrEx || !data || !data_size || \
data_offset >= ctx->section_size || (data_offset + data_size) > ctx->section_size || !out_block_size || !out_block_offset)
(data_offset + data_size) > ctx->section_size || !out_block_size || !out_block_offset)
{
LOGFILE("Invalid NCA FS section header parameters!");
goto end;
@ -1188,8 +1159,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const
u64 plain_chunk_offset = 0;
if (!*(nca_ctx->content_id_str) || (nca_ctx->storage_id != NcmStorageId_GameCard && !nca_ctx->ncm_storage) || (nca_ctx->storage_id == NcmStorageId_GameCard && !nca_ctx->gamecard_offset) || \
(nca_ctx->format_version != NcaVersion_Nca0 && nca_ctx->format_version != NcaVersion_Nca2 && nca_ctx->format_version != NcaVersion_Nca3) || content_offset >= nca_ctx->content_size || \
(content_offset + data_size) > nca_ctx->content_size)
(nca_ctx->format_version != NcaVersion_Nca0 && nca_ctx->format_version != NcaVersion_Nca2 && nca_ctx->format_version != NcaVersion_Nca3) || (content_offset + data_size) > nca_ctx->content_size)
{
LOGFILE("Invalid NCA header parameters!");
goto end;
@ -1225,7 +1195,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const
} else
if (ctx->encryption_type == NcaEncryptionType_AesCtr)
{
ncaUpdateAesCtrIv(ctx->ctr, content_offset);
aes128CtrUpdatePartialCtr(ctx->ctr, content_offset);
aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
aes128CtrCrypt(&(ctx->ctr_ctx), out, out, data_size);
}
@ -1277,7 +1247,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const
} else
if (ctx->encryption_type == NcaEncryptionType_AesCtr)
{
ncaUpdateAesCtrIv(ctx->ctr, content_offset);
aes128CtrUpdatePartialCtr(ctx->ctr, content_offset);
aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
aes128CtrCrypt(&(ctx->ctr_ctx), out, out, block_size);
}

View file

@ -325,21 +325,26 @@ typedef struct {
NcaFsSectionContext fs_ctx[NCA_FS_HEADER_COUNT];
NcaDecryptedKeyArea decrypted_key_area;
void *content_type_ctx; ///< Pointer to a content type context (e.g. ContentMetaContext, ProgramInfoContext, NacpContext, LegalInfoContext). Set to NULL if unused.
bool content_type_ctx_patch; ///< Set to true if a NCA patch generated by the content type context is needed and hasn't been completely writen yet.
u32 content_type_ctx_data_idx; ///< Start index for the data generated by the content type context. Used while creating NSPs.
} NcaContext;
typedef struct {
u64 offset; ///< New data offset (relative to the start of the NCA content file).
u64 size; ///< New data size.
u8 *data; ///< New data.
bool written; ///< Set to true if this patch has already been written.
u64 offset; ///< New data offset (relative to the start of the NCA content file).
u64 size; ///< New data size.
u8 *data; ///< New data.
} NcaHashDataPatch;
typedef struct {
bool written; ///< Set to true if all hash region patches have been written.
NcmContentId content_id;
u32 hash_region_count;
NcaHashDataPatch hash_region_patch[NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT];
} NcaHierarchicalSha256Patch;
typedef struct {
bool written; ///< Set to true if all hash level patches have been written.
NcmContentId content_id;
NcaHashDataPatch hash_level_patch[NCA_IVFC_LEVEL_COUNT];
} NcaHierarchicalIntegrityPatch;
@ -386,6 +391,7 @@ bool ncaGenerateHierarchicalSha256Patch(NcaFsSectionContext *ctx, const void *da
/// Overwrites block(s) from a buffer holding raw NCA data using previously initialized NcaContext and NcaHierarchicalSha256Patch.
/// 'buf_offset' must hold the raw NCA offset where the data stored in 'buf' was read from.
/// The 'written' fields from the input NcaHierarchicalSha256Patch and its underlying NcaHashDataPatch elements are updated by this function.
void ncaWriteHierarchicalSha256PatchToMemoryBuffer(NcaContext *ctx, NcaHierarchicalSha256Patch *patch, void *buf, u64 buf_size, u64 buf_offset);
/// Generates HierarchicalIntegrity FS section patch data, which can be used to seamlessly replace NCA data.
@ -396,8 +402,13 @@ bool ncaGenerateHierarchicalIntegrityPatch(NcaFsSectionContext *ctx, const void
/// Overwrites block(s) from a buffer holding raw NCA data using a previously initialized NcaContext and NcaHierarchicalIntegrityPatch.
/// 'buf_offset' must hold the raw NCA offset where the data stored in 'buf' was read from.
/// The 'written' fields from the input NcaHierarchicalIntegrityPatch and its underlying NcaHashDataPatch elements are updated by this function.
void ncaWriteHierarchicalIntegrityPatchToMemoryBuffer(NcaContext *ctx, NcaHierarchicalIntegrityPatch *patch, void *buf, u64 buf_size, u64 buf_offset);
/// Sets the distribution type field from the underlying NCA header in the provided NCA context to NcaDistributionType_Download.
/// Needed for NSP dumps from gamecard titles.
void ncaSetDownloadDistributionType(NcaContext *ctx);
/// Removes titlekey crypto dependency from a NCA context by wiping the Rights ID from the underlying NCA header and copying the decrypted titlekey to the NCA key area.
void ncaRemoveTitlekeyCrypto(NcaContext *ctx);
@ -420,12 +431,6 @@ const char *ncaGetFsSectionTypeName(NcaFsSectionContext *ctx);
/// Helper inline functions.
NX_INLINE void ncaSetDownloadDistributionType(NcaContext *ctx)
{
if (!ctx || ctx->header.distribution_type == NcaDistributionType_Download) return;
ctx->header.distribution_type = NcaDistributionType_Download;
}
NX_INLINE bool ncaIsHeaderDirty(NcaContext *ctx)
{
if (!ctx) return false;
@ -442,7 +447,7 @@ NX_INLINE bool ncaValidateHierarchicalSha256Offsets(NcaHierarchicalSha256Data *h
for(u32 i = 0; i < hierarchical_sha256_data->hash_region_count; i++)
{
NcaRegion *hash_region = &(hierarchical_sha256_data->hash_region[i]);
if (hash_region->offset >= section_size || !hash_region->size || (hash_region->offset + hash_region->size) > section_size) return false;
if (!hash_region->size || (hash_region->offset + hash_region->size) > section_size) return false;
}
return true;
@ -456,7 +461,7 @@ NX_INLINE bool ncaValidateHierarchicalIntegrityOffsets(NcaIntegrityMetaInfo *int
for(u32 i = 0; i < NCA_IVFC_LEVEL_COUNT; i++)
{
NcaHierarchicalIntegrityVerificationLevelInformation *level_information = &(integrity_meta_info->info_level_hash.level_information[i]);
if (level_information->offset >= section_size || !level_information->size || !level_information->block_order || (level_information->offset + level_information->size) > section_size) return false;
if (!level_information->size || !level_information->block_order || (level_information->offset + level_information->size) > section_size) return false;
}
return true;

View file

@ -70,9 +70,6 @@ bool npdmInitializeContext(NpdmContext *out, PartitionFileSystemContext *pfs_ctx
goto end;
}
/* Calculate SHA-256 checksum for the whole raw NPDM. */
sha256CalculateHash(out->raw_data_hash, out->raw_data, out->raw_data_size);
/* Verify meta header. */
out->meta_header = (NpdmMetaHeader*)out->raw_data;
cur_offset += sizeof(NpdmMetaHeader);
@ -281,5 +278,34 @@ bool npdmChangeAcidPublicKeyAndNcaSignature(NpdmContext *npdm_ctx)
return false;
}
/* Generate Partition FS entry patch. */
if (!pfsGenerateEntryPatch(npdm_ctx->pfs_ctx, npdm_ctx->pfs_entry, npdm_ctx->raw_data, npdm_ctx->raw_data_size, 0, &(npdm_ctx->nca_patch)))
{
LOGFILE("Failed to generate Partition FS entry patch!");
return false;
}
/* Update NCA content type context patch status. */
nca_ctx->content_type_ctx_patch = true;
return true;
}
void npdmWriteNcaPatch(NpdmContext *npdm_ctx, void *buf, u64 buf_size, u64 buf_offset)
{
NcaContext *nca_ctx = NULL;
/* Using npdmIsValidContext() here would probably take up precious CPU cycles. */
if (!npdm_ctx || !npdm_ctx->pfs_ctx || !npdm_ctx->pfs_ctx->nca_fs_ctx || !(nca_ctx = (NcaContext*)npdm_ctx->pfs_ctx->nca_fs_ctx->nca_ctx) || nca_ctx->content_type != NcmContentType_Program || \
!nca_ctx->content_type_ctx_patch || npdm_ctx->nca_patch.written) return;
/* Attempt to write Partition FS entry. */
pfsWriteEntryPatchToMemoryBuffer(npdm_ctx->pfs_ctx, &(npdm_ctx->nca_patch), buf, buf_size, buf_offset);
/* Check if we need to update the NCA content type context patch status. */
if (npdm_ctx->nca_patch.written)
{
nca_ctx->content_type_ctx_patch = false;
LOGFILE("NPDM Partition FS file entry patch successfully written to NCA \"%s\"!", nca_ctx->content_id_str);
}
}

View file

@ -540,7 +540,6 @@ typedef struct {
///< Bear in mind that generating a patch modifies the NCA context.
u8 *raw_data; ///< Pointer to a dynamically allocated buffer that holds the raw NPDM.
u64 raw_data_size; ///< Raw NPDM size. Kept here for convenience - this is part of 'pfs_entry'.
u8 raw_data_hash[SHA256_HASH_SIZE]; ///< SHA-256 checksum calculated over the whole raw NPDM. Used to determine if NcaHierarchicalSha256Patch generation is truly needed.
NpdmMetaHeader *meta_header; ///< Pointer to the NpdmMetaHeader within 'raw_data'.
NpdmAcidHeader *acid_header; ///< Pointer to the NpdmAcidHeader within 'raw_data'.
NpdmFsAccessControlDescriptor *acid_fac_descriptor; ///< Pointer to the NpdmFsAccessControlDescriptor within the NPDM ACID section.
@ -555,9 +554,12 @@ typedef struct {
/// Initializes a NpdmContext using a previously initialized PartitionFileSystemContext (which must belong to the ExeFS from a Program NCA).
bool npdmInitializeContext(NpdmContext *out, PartitionFileSystemContext *pfs_ctx);
/// Changes the ACID public key from the NPDM in the input NpdmContext and updates the ACID signature from the NCA header in the underlying NCA context.
/// Changes the ACID public key from the NPDM in the input NpdmContext, updates the ACID signature from the NCA header in the underlying NCA context and generates a Partition FS entry patch.
bool npdmChangeAcidPublicKeyAndNcaSignature(NpdmContext *npdm_ctx);
/// Writes data from the Partition FS patch in the input NpdmContext to the provided buffer.
void npdmWriteNcaPatch(NpdmContext *npdm_ctx, void *buf, u64 buf_size, u64 buf_offset);
/// Helper inline functions.
NX_INLINE void npdmFreeContext(NpdmContext *npdm_ctx)
@ -578,19 +580,6 @@ NX_INLINE bool npdmIsValidContext(NpdmContext *npdm_ctx)
((npdm_ctx->aci_header->kernel_capability_size && npdm_ctx->aci_kc_descriptor) || (!npdm_ctx->aci_header->kernel_capability_size && !npdm_ctx->aci_kc_descriptor)));
}
NX_INLINE bool npdmIsNcaPatchRequired(NpdmContext *npdm_ctx)
{
if (!npdmIsValidContext(npdm_ctx)) return false;
u8 tmp_hash[SHA256_HASH_SIZE] = {0};
sha256CalculateHash(tmp_hash, npdm_ctx->raw_data, npdm_ctx->raw_data_size);
return (memcmp(tmp_hash, npdm_ctx->raw_data_hash, SHA256_HASH_SIZE) != 0);
}
NX_INLINE bool npdmGenerateNcaPatch(NpdmContext *npdm_ctx)
{
return (npdmIsValidContext(npdm_ctx) && pfsGenerateEntryPatch(npdm_ctx->pfs_ctx, npdm_ctx->pfs_entry, npdm_ctx->raw_data, npdm_ctx->raw_data_size, 0, &(npdm_ctx->nca_patch)));
}
NX_INLINE u32 npdmGetKernelCapabilityDescriptorEntryValue(NpdmKernelCapabilityDescriptorEntry *entry)
{
return (entry ? (((entry->value + 1) & ~entry->value) - 1) : 0);

View file

@ -22,15 +22,11 @@
#include "pfs.h"
#include "npdm.h"
#define PFS_FULL_HEADER_ALIGNMENT 0x20
bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *nca_fs_ctx)
{
if (!out || !nca_fs_ctx || !nca_fs_ctx->enabled || nca_fs_ctx->section_type != NcaFsSectionType_PartitionFs || nca_fs_ctx->header.fs_type != NcaFsType_PartitionFs || \
nca_fs_ctx->header.hash_type != NcaHashType_HierarchicalSha256)
{
LOGFILE("Invalid parameters!");
return false;
}
NcaContext *nca_ctx = NULL;
u32 magic = 0;
PartitionFileSystemHeader pfs_header = {0};
@ -39,6 +35,13 @@ bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *
u32 hash_region_count = 0;
NcaRegion *hash_region = NULL;
if (!out || !nca_fs_ctx || !nca_fs_ctx->enabled || nca_fs_ctx->section_type != NcaFsSectionType_PartitionFs || nca_fs_ctx->header.fs_type != NcaFsType_PartitionFs || \
nca_fs_ctx->header.hash_type != NcaHashType_HierarchicalSha256 || !(nca_ctx = (NcaContext*)nca_fs_ctx->nca_ctx) || (nca_ctx->rights_id_available && !nca_ctx->titlekey_retrieved))
{
LOGFILE("Invalid parameters!");
return false;
}
/* Free output context beforehand. */
pfsFreeContext(out);
@ -105,7 +108,7 @@ bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *
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)
if (!ctx || !ctx->nca_fs_ctx || !ctx->size || !out || !read_size || (offset + read_size) > ctx->size)
{
LOGFILE("Invalid parameters!");
return false;
@ -123,8 +126,7 @@ bool pfsReadPartitionData(PartitionFileSystemContext *ctx, void *out, u64 read_s
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)
if (!ctx || !fs_entry || !fs_entry->size || (fs_entry->offset + fs_entry->size) > ctx->size || !out || !read_size || (offset + read_size) > fs_entry->size)
{
LOGFILE("Invalid parameters!");
return false;
@ -204,8 +206,8 @@ bool pfsGetTotalDataSize(PartitionFileSystemContext *ctx, u64 *out_size)
bool pfsGenerateEntryPatch(PartitionFileSystemContext *ctx, PartitionFileSystemEntry *fs_entry, const void *data, u64 data_size, u64 data_offset, NcaHierarchicalSha256Patch *out)
{
if (!ctx || !ctx->nca_fs_ctx || !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)
if (!ctx || !ctx->nca_fs_ctx || !ctx->header_size || !ctx->header || !fs_entry || !fs_entry->size || (fs_entry->offset + fs_entry->size) > ctx->size || !data || !data_size || \
(data_offset + data_size) > fs_entry->size || !out)
{
LOGFILE("Invalid parameters!");
return false;
@ -221,3 +223,142 @@ bool pfsGenerateEntryPatch(PartitionFileSystemContext *ctx, PartitionFileSystemE
return true;
}
bool pfsAddEntryInformationToFileContext(PartitionFileSystemFileContext *ctx, const char *entry_name, u64 entry_size, u32 *out_entry_idx)
{
if (!ctx || !entry_name || !*entry_name)
{
LOGFILE("Invalid parameters!");
return false;
}
PartitionFileSystemHeader *header = &(ctx->header);
PartitionFileSystemEntry *tmp_pfs_entries = NULL, *cur_pfs_entry = NULL, *prev_pfs_entry = NULL;
u64 tmp_pfs_entries_size = ((header->entry_count + 1) * sizeof(PartitionFileSystemEntry));
char *tmp_name_table = NULL;
u32 tmp_name_table_size = (header->name_table_size + strlen(entry_name) + 1);
/* Reallocate Partition FS entries. */
if (!(tmp_pfs_entries = realloc(ctx->entries, tmp_pfs_entries_size)))
{
LOGFILE("Failed to reallocate Partition FS entries!");
return false;
}
ctx->entries = tmp_pfs_entries;
tmp_pfs_entries = NULL;
/* Update Partition FS entry information. */
cur_pfs_entry = &(ctx->entries[header->entry_count]);
prev_pfs_entry = (header->entry_count ? &(ctx->entries[header->entry_count - 1]) : NULL);
memset(cur_pfs_entry, 0, sizeof(PartitionFileSystemEntry));
cur_pfs_entry->offset = (prev_pfs_entry ? (prev_pfs_entry->offset + prev_pfs_entry->size) : 0);
cur_pfs_entry->size = entry_size;
cur_pfs_entry->name_offset = header->name_table_size;
/* Reallocate Partition FS name table. */
if (!(tmp_name_table = realloc(ctx->name_table, tmp_name_table_size)))
{
LOGFILE("Failed to reallocate Partition FS name table!");
return false;
}
ctx->name_table = tmp_name_table;
tmp_name_table = NULL;
/* Update Partition FS name table. */
sprintf(ctx->name_table + header->name_table_size, "%s", entry_name);
header->name_table_size = tmp_name_table_size;
/* Update output entry index. */
if (out_entry_idx) *out_entry_idx = header->entry_count;
/* Update Partition FS entry count, name table size and data size. */
header->entry_count++;
ctx->fs_size += entry_size;
return true;
}
bool pfsUpdateEntryNameFromFileContext(PartitionFileSystemFileContext *ctx, u32 entry_idx, const char *new_entry_name)
{
if (!ctx || !ctx->header.entry_count || !ctx->header.name_table_size || !ctx->entries || !ctx->name_table || entry_idx >= ctx->header.entry_count || !new_entry_name || !*new_entry_name)
{
LOGFILE("Invalid parameters!");
return false;
}
PartitionFileSystemEntry *pfs_entry = &(ctx->entries[entry_idx]);
char *name_table_entry = (ctx->name_table + pfs_entry->name_offset);
size_t new_entry_name_len = strlen(new_entry_name);
size_t cur_entry_name_len = strlen(name_table_entry);
if (new_entry_name_len > cur_entry_name_len)
{
LOGFILE("New entry name length exceeds previous entry name length! (0x%lX > 0x%lX).", new_entry_name_len, cur_entry_name_len);
return false;
}
memcpy(name_table_entry, new_entry_name, new_entry_name_len);
return true;
}
bool pfsWriteFileContextHeaderToMemoryBuffer(PartitionFileSystemFileContext *ctx, void *buf, u64 buf_size, u64 *out_header_size)
{
if (!ctx || !ctx->header.entry_count || !ctx->header.name_table_size || !ctx->entries || !ctx->name_table || !buf || !out_header_size)
{
LOGFILE("Invalid parameters!");
return false;
}
PartitionFileSystemHeader *header = &(ctx->header);
u8 *buf_u8 = (u8*)buf;
u64 header_size = 0, full_header_size = 0, block_offset = 0, block_size = 0;
u32 padding_size = 0;
/* Calculate header size. */
header_size = (sizeof(PartitionFileSystemHeader) + (header->entry_count * sizeof(PartitionFileSystemEntry)) + header->name_table_size);
/* Calculate full header size and padding size. */
full_header_size = (IS_ALIGNED(header_size, PFS_FULL_HEADER_ALIGNMENT) ? ALIGN_UP(header_size + 1, PFS_FULL_HEADER_ALIGNMENT) : ALIGN_UP(header_size, PFS_FULL_HEADER_ALIGNMENT));
padding_size = (u32)(full_header_size - header_size);
/* Check buffer size. */
if (buf_size < full_header_size)
{
LOGFILE("Not enough space available in input buffer to write full Partition FS header! (got 0x%lX, need 0x%lX).", buf_size, full_header_size);
return false;
}
/* Update name table size in Partition FS header to make it reflect the padding. */
header->name_table_size += padding_size;
/* Write full header. */
block_size = sizeof(PartitionFileSystemHeader);
memcpy(buf_u8 + block_offset, header, block_size);
block_offset += block_size;
block_size = (header->entry_count * sizeof(PartitionFileSystemEntry));
memcpy(buf_u8 + block_offset, ctx->entries, block_size);
block_offset += block_size;
block_size = header->name_table_size;
memcpy(buf_u8 + block_offset, ctx->name_table, block_size);
block_offset += block_size;
memset(buf_u8 + block_offset, 0, padding_size);
/* Update output header size. */
*out_header_size = full_header_size;
/* Restore name table size in Partition FS header. */
header->name_table_size -= padding_size;
return true;
}

View file

@ -41,6 +41,7 @@ typedef struct {
u8 reserved[0x4];
} PartitionFileSystemEntry;
/// Used with Partition FS sections from NCAs.
typedef struct {
NcaFsSectionContext *nca_fs_ctx; ///< Used to read NCA FS section data.
u64 offset; ///< Partition offset (relative to the start of the NCA FS section).
@ -50,6 +51,14 @@ typedef struct {
u8 *header; ///< PartitionFileSystemHeader + (PartitionFileSystemEntry * entry_count) + Name Table.
} PartitionFileSystemContext;
/// Used with Partition FS images (e.g. NSPs).
typedef struct {
PartitionFileSystemHeader header; ///< Partition FS header. Holds the entry count and name table size.
PartitionFileSystemEntry *entries; ///< Partition FS entries.
char *name_table; ///< Name table.
u64 fs_size; ///< Partition FS data size. Updated each time a new entry is added.
} PartitionFileSystemFileContext;
/// Initializes a Partition FS context.
bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *nca_fs_ctx);
@ -73,6 +82,16 @@ bool pfsGetTotalDataSize(PartitionFileSystemContext *ctx, u64 *out_size);
/// Use the pfsWriteEntryPatchToMemoryBuffer() wrapper to write patch data generated by this function.
bool pfsGenerateEntryPatch(PartitionFileSystemContext *ctx, PartitionFileSystemEntry *fs_entry, const void *data, u64 data_size, u64 data_offset, NcaHierarchicalSha256Patch *out);
/// Adds a new Partition FS entry to an existing PartitionFileSystemFileContext, using the provided entry name and size.
/// If 'out_entry_idx' is a valid pointer, the index to the new Partition FS entry will be saved to it.
bool pfsAddEntryInformationToFileContext(PartitionFileSystemFileContext *ctx, const char *entry_name, u64 entry_size, u32 *out_entry_idx);
/// Updates the name from a Partition FS entry in an existing PartitionFileSystemFileContext, using an entry index and the new entry name.
bool pfsUpdateEntryNameFromFileContext(PartitionFileSystemFileContext *ctx, u32 entry_idx, const char *new_entry_name);
/// Generates a full Partition FS header from an existing PartitionFileSystemFileContext and writes it to the provided memory buffer.
bool pfsWriteFileContextHeaderToMemoryBuffer(PartitionFileSystemFileContext *ctx, void *buf, u64 buf_size, u64 *out_header_size);
/// Miscellaneous functions.
NX_INLINE void pfsFreeContext(PartitionFileSystemContext *ctx)
@ -134,4 +153,19 @@ NX_INLINE void pfsFreeEntryPatch(NcaHierarchicalSha256Patch *patch)
ncaFreeHierarchicalSha256Patch(patch);
}
NX_INLINE void pfsFreeFileContext(PartitionFileSystemFileContext *ctx)
{
if (!ctx) return;
if (ctx->entries) free(ctx->entries);
if (ctx->name_table) free(ctx->name_table);
memset(ctx, 0, sizeof(PartitionFileSystemFileContext));
}
NX_INLINE void pfsInitializeFileContext(PartitionFileSystemFileContext *ctx)
{
if (!ctx) return;
pfsFreeFileContext(ctx);
ctx->header.magic = __builtin_bswap32(PFS0_MAGIC);
}
#endif /* __PFS_H__ */

View file

@ -140,6 +140,10 @@ bool programInfoInitializeContext(ProgramInfoContext *out, NcaContext *nca_ctx)
/* Update output context. */
out->nca_ctx = nca_ctx;
/* Update content type context info in NCA context. */
nca_ctx->content_type_ctx = out;
nca_ctx->content_type_ctx_patch = false;
success = true;
end:

View file

@ -75,14 +75,9 @@ NX_INLINE bool programInfoChangeAcidPublicKeyAndNcaSignature(ProgramInfoContext
return (programInfoIsValidContext(program_info_ctx) && npdmChangeAcidPublicKeyAndNcaSignature(&(program_info_ctx->npdm_ctx)));
}
NX_INLINE bool programInfoIsNcaPatchRequired(ProgramInfoContext *program_info_ctx)
NX_INLINE void programInfoWriteNcaPatch(ProgramInfoContext *program_info_ctx, void *buf, u64 buf_size, u64 buf_offset)
{
return (programInfoIsValidContext(program_info_ctx) && npdmIsNcaPatchRequired(&(program_info_ctx->npdm_ctx)));
}
NX_INLINE bool programInfoGenerateNcaPatch(ProgramInfoContext *program_info_ctx)
{
return (programInfoIsValidContext(program_info_ctx) && npdmGenerateNcaPatch(&(program_info_ctx->npdm_ctx)));
if (program_info_ctx) npdmWriteNcaPatch(&(program_info_ctx->npdm_ctx), buf, buf_size, buf_offset);
}
#endif /* __PROGRAM_INFO_H__ */

View file

@ -33,7 +33,7 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *nca_
if (!out || !nca_fs_ctx || !nca_fs_ctx->enabled || !(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)))
(nca_fs_ctx->section_type != NcaFsSectionType_RomFs || nca_fs_ctx->header.hash_type != NcaHashType_HierarchicalIntegrity)) || (nca_ctx->rights_id_available && !nca_ctx->titlekey_retrieved))
{
LOGFILE("Invalid parameters!");
return false;
@ -96,7 +96,7 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *nca_
dir_table_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 (!out->dir_table_size || dir_table_offset >= out->size || (dir_table_offset + out->dir_table_size) > out->size)
if (!out->dir_table_size || (dir_table_offset + out->dir_table_size) > out->size)
{
LOGFILE("Invalid RomFS directory entries table!");
return false;
@ -119,7 +119,7 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *nca_
file_table_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 (!out->file_table_size || file_table_offset >= out->size || (file_table_offset + out->file_table_size) > out->size)
if (!out->file_table_size || (file_table_offset + out->file_table_size) > out->size)
{
LOGFILE("Invalid RomFS file entries table!");
goto end;
@ -156,7 +156,7 @@ end:
bool romfsReadFileSystemData(RomFileSystemContext *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)
if (!ctx || !ctx->nca_fs_ctx || !ctx->size || !out || !read_size || (offset + read_size) > ctx->size)
{
LOGFILE("Invalid parameters!");
return false;
@ -174,8 +174,7 @@ bool romfsReadFileSystemData(RomFileSystemContext *ctx, void *out, u64 read_size
bool romfsReadFileEntryData(RomFileSystemContext *ctx, RomFileSystemFileEntry *file_entry, void *out, u64 read_size, u64 offset)
{
if (!ctx || !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)
if (!ctx || !ctx->body_offset || !file_entry || !file_entry->size || (file_entry->offset + file_entry->size) > ctx->size || !out || !read_size || (offset + read_size) > file_entry->size)
{
LOGFILE("Invalid parameters!");
return false;
@ -507,8 +506,7 @@ bool romfsGeneratePathFromFileEntry(RomFileSystemContext *ctx, RomFileSystemFile
bool romfsGenerateFileEntryPatch(RomFileSystemContext *ctx, RomFileSystemFileEntry *file_entry, const void *data, u64 data_size, u64 data_offset, RomFileSystemFileEntryPatch *out)
{
if (!ctx || !ctx->nca_fs_ctx || !ctx->body_offset || (ctx->nca_fs_ctx->section_type != NcaFsSectionType_Nca0RomFs && ctx->nca_fs_ctx->section_type != NcaFsSectionType_RomFs) || !file_entry || \
!file_entry->size || file_entry->offset >= ctx->size || (file_entry->offset + file_entry->size) > ctx->size || !data || !data_size || data_offset >= file_entry->size || \
(data_offset + data_size) > file_entry->size || !out)
!file_entry->size || (file_entry->offset + file_entry->size) > ctx->size || !data || !data_size || (data_offset + data_size) > file_entry->size || !out)
{
LOGFILE("Invalid parameters!");
return false;

View file

@ -1746,6 +1746,32 @@ save_ctx_t *save_open_savefile(const char *path, u32 action)
open_savefile = true;
/* Code to dump the requested file in its entirety. Useful to retrieve protected system savefiles without exiting HOS. */
/*char sd_path[FS_MAX_PATH] = {0};
sprintf(sd_path, "sdmc:/%s", strrchr(path, '/') + 1);
UINT blksize = 0x100000;
u8 *buf = malloc(blksize);
FILE *fd = fopen(sd_path, "wb");
if (buf && fd)
{
u64 size = f_size(save_fd);
UINT br = 0;
for(u64 i = 0; i < size; i += blksize)
{
if ((size - i) < blksize) blksize = (size - i);
if (f_read(save_fd, buf, blksize, &br) != FR_OK || br != blksize) break;
fwrite(buf, 1, blksize, fd);
}
f_rewind(save_fd);
}
if (fd) fclose(fd);
if (buf) free(buf);*/
save_ctx = calloc(1, sizeof(save_ctx_t));
if (!save_ctx)
{

View file

@ -1,7 +1,7 @@
/*
* tik.c
*
* Copyright (c) 2019, shchmue.
* Copyright (c) 2019-2020, shchmue.
* Copyright (c) 2020, DarkMatterCore <pabloacurielz@gmail.com>.
*
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
@ -27,6 +27,8 @@
#include "keys.h"
#include "rsa.h"
#include "gamecard.h"
#include "mem.h"
#include "aes.h"
#define TIK_COMMON_SAVEFILE_PATH BIS_SYSTEM_PARTITION_MOUNT_NAME "/save/80000000000000e1"
#define TIK_PERSONALIZED_SAVEFILE_PATH BIS_SYSTEM_PARTITION_MOUNT_NAME "/save/80000000000000e2"
@ -34,6 +36,8 @@
#define ETICKET_DEVKEY_PUBLIC_EXPONENT 0x10001
#define ES_CTRKEY_ENTRY_ALIGNMENT 0x8
/* Type definitions. */
/// Everything after the AES CTR is encrypted.
@ -47,6 +51,22 @@ typedef struct {
u8 ghash[0x10];
} tikEticketDeviceKeyData;
/// 9.x+ CTR key entry in ES .data segment. Used to store CTR key/IV data for encrypted volatile tickets in ticket.bin and/or encrypted entries in ticket_list.bin.
/// This is always stored in pairs. The first entry holds the key/IV for the encrypted volatile ticket, while the second entry holds the key/IV for the encrypted entry in ticket_list.bin.
/// First index in this list is always 0, and it's aligned to ES_CTRKEY_ENTRY_ALIGNMENT.
typedef struct {
u32 idx; ///< Entry index.
u8 key[AES_BLOCK_SIZE]; ///< AES-128-CTR key.
u8 ctr[AES_BLOCK_SIZE]; ///< AES-128-CTR counter/IV. Always zeroed out.
} tikEsCtrKeyEntry9x;
/// Lookup pattern for tikEsCtrKeyEntry9x.
typedef struct {
u32 idx1; ///< Always set to 0 (first entry).
u8 ctrdata[AES_BLOCK_SIZE * 2];
u32 idx2; ///< Always set to 1 (second entry).
} tikEsCtrKeyPattern9x;
/* Global variables. */
static SetCalRsa2048DeviceKey g_eTicketDeviceKey = {0};
@ -59,6 +79,18 @@ static const u8 g_nullHash[0x20] = {
0x27, 0xAE, 0x41, 0xE4, 0x64, 0x9B, 0x93, 0x4C, 0xA4, 0x95, 0x99, 0x1B, 0x78, 0x52, 0xB8, 0x55
};
static const char *g_tikTitleKeyTypeStrings[] = {
[TikTitleKeyType_Common] = "common",
[TikTitleKeyType_Personalized] = "personalized"
};
static MemoryLocation g_esMemoryLocation = {
.program_id = ES_SYSMODULE_TID,
.mask = 0,
.data = NULL,
.data_size = 0
};
/* Function prototypes. */
static bool tikRetrieveTicketFromGameCardByRightsId(Ticket *dst, const FsRightsId *id);
@ -69,6 +101,7 @@ static bool tikGetTitleKekDecryptedTitleKey(void *dst, const void *src, u8 key_g
static bool tikGetTitleKeyTypeFromRightsId(const FsRightsId *id, u8 *out);
static bool tikRetrieveRightsIdsByTitleKeyType(FsRightsId **out, u32 *out_count, bool personalized);
static u8 *tikRetrieveTicketEntryFromTicketBin(allocation_table_storage_ctx_t *fat_storage, u64 ticket_bin_size, u8 *buf, u64 buf_size, const FsRightsId *id, u8 titlekey_type);
static bool tikGetTicketTypeAndSize(void *data, u64 data_size, u8 *out_type, u64 *out_size);
@ -83,10 +116,12 @@ bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id, bool use_gam
return false;
}
TikCommonBlock *tik_common_block = NULL;
/* Check if this ticket has already been retrieved. */
if (dst->type > TikType_None && dst->type <= TikType_SigHmac160 && dst->size >= SIGNED_TIK_MIN_SIZE && dst->size <= SIGNED_TIK_MAX_SIZE)
{
TikCommonBlock *tik_common_block = tikGetCommonBlock(dst->data);
tik_common_block = tikGetCommonBlock(dst->data);
if (tik_common_block && !memcmp(tik_common_block->rights_id.c, id->c, 0x10)) return true;
}
@ -118,6 +153,10 @@ bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id, bool use_gam
return false;
}
/* Generate rights ID string. */
tik_common_block = tikGetCommonBlock(dst->data);
utilsGenerateHexStringFromData(dst->rights_id_str, sizeof(dst->rights_id_str), tik_common_block->rights_id.c, sizeof(tik_common_block->rights_id.c));
return true;
}
@ -178,6 +217,7 @@ bool tikConvertPersonalizedTicketToCommonTicket(Ticket *tik, u8 **out_raw_cert_c
/* Update the rest of the ticket fields. */
tik_common_block->titlekey_type = TikTitleKeyType_Common;
tik_common_block->property_mask &= ~(TikPropertyMask_ELicenseRequired | TikPropertyMask_Volatile);
tik_common_block->ticket_id = 0;
tik_common_block->device_id = 0;
tik_common_block->account_id = 0;
@ -245,7 +285,6 @@ static bool tikRetrieveTicketFromEsSaveDataByRightsId(Ticket *dst, const FsRight
return false;
}
u32 i;
u8 titlekey_type = 0;
save_ctx_t *save_ctx = NULL;
@ -253,10 +292,9 @@ static bool tikRetrieveTicketFromEsSaveDataByRightsId(Ticket *dst, const FsRight
u64 ticket_bin_size = 0;
u64 buf_size = (SIGNED_TIK_MAX_SIZE * 0x10);
u64 br = 0, total_br = 0;
u8 *ticket_bin_buf = NULL;
u8 *buf = NULL, *ticket_entry = NULL;
bool found_tik = false, success = false;
bool success = false;
if (!tikGetTitleKeyTypeFromRightsId(id, &titlekey_type))
{
@ -267,77 +305,47 @@ static bool tikRetrieveTicketFromEsSaveDataByRightsId(Ticket *dst, const FsRight
save_ctx = save_open_savefile(titlekey_type == TikTitleKeyType_Common ? TIK_COMMON_SAVEFILE_PATH : TIK_PERSONALIZED_SAVEFILE_PATH, 0);
if (!save_ctx)
{
LOGFILE("Failed to open ES %s ticket system savefile!", titlekey_type == TikTitleKeyType_Common ? "common" : "personalized");
LOGFILE("Failed to open ES %s ticket system savefile!", g_tikTitleKeyTypeStrings[titlekey_type]);
return false;
}
if (!save_get_fat_storage_from_file_entry_by_path(save_ctx, TIK_SAVEFILE_STORAGE_PATH, &fat_storage, &ticket_bin_size))
{
LOGFILE("Failed to locate \"%s\" in ES %s ticket system save!", TIK_SAVEFILE_STORAGE_PATH, titlekey_type == TikTitleKeyType_Common ? "common" : "personalized");
LOGFILE("Failed to locate \"%s\" in ES %s ticket system save!", TIK_SAVEFILE_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type]);
goto end;
}
if (ticket_bin_size < SIGNED_TIK_MIN_SIZE || (ticket_bin_size % SIGNED_TIK_MAX_SIZE) != 0)
{
LOGFILE("Invalid size for \"%s\"! (0x%lX).", TIK_SAVEFILE_STORAGE_PATH, ticket_bin_size);
LOGFILE("Invalid size for \"%s\" in ES %s ticket system save! (0x%lX).", TIK_SAVEFILE_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type], ticket_bin_size);
goto end;
}
ticket_bin_buf = malloc(buf_size);
if (!ticket_bin_buf)
buf = malloc(buf_size);
if (!buf)
{
LOGFILE("Unable to allocate 0x%lX bytes block for temporary read buffer!", buf_size);
goto end;
}
while(total_br < ticket_bin_size)
if (!(ticket_entry = tikRetrieveTicketEntryFromTicketBin(&fat_storage, ticket_bin_size, buf, buf_size, id, titlekey_type)))
{
if (buf_size > (ticket_bin_size - total_br)) buf_size = (ticket_bin_size - total_br);
br = save_allocation_table_storage_read(&fat_storage, ticket_bin_buf, total_br, buf_size);
if (br != buf_size)
{
LOGFILE("Failed to read 0x%lX bytes chunk at offset 0x%lX from \"%s\" in ES %s ticket system save!", buf_size, total_br, TIK_SAVEFILE_STORAGE_PATH, \
(titlekey_type == TikTitleKeyType_Common ? "common" : "personalized"));
goto end;
}
total_br += br;
for(i = 0; i < buf_size; i += SIGNED_TIK_MAX_SIZE)
{
if ((buf_size - i) < SIGNED_TIK_MIN_SIZE) break;
TikCommonBlock *tik_common_block = tikGetCommonBlock(ticket_bin_buf + i);
if (tik_common_block && !memcmp(tik_common_block->rights_id.c, id->c, 0x10))
{
/* Jackpot. */
found_tik = true;
break;
}
}
if (found_tik) break;
}
if (!found_tik)
{
LOGFILE("Unable to find a matching ticket entry for the provided Rights ID!");
LOGFILE("Unable to find a matching %s ticket entry for the provided Rights ID!", g_tikTitleKeyTypeStrings[titlekey_type]);
goto end;
}
if (!tikGetTicketTypeAndSize(ticket_bin_buf + i, SIGNED_TIK_MAX_SIZE, &(dst->type), &(dst->size)))
if (!tikGetTicketTypeAndSize(ticket_entry, SIGNED_TIK_MAX_SIZE, &(dst->type), &(dst->size)))
{
LOGFILE("Unable to determine ticket type and size!");
goto end;
}
memcpy(dst->data, ticket_bin_buf + i, dst->size);
memcpy(dst->data, ticket_entry, dst->size);
success = true;
end:
if (ticket_bin_buf) free(ticket_bin_buf);
if (buf) free(buf);
if (save_ctx) save_close_savefile(save_ctx);
@ -438,7 +446,7 @@ static bool tikGetTitleKeyTypeFromRightsId(const FsRightsId *id, u8 *out)
if (!tikRetrieveRightsIdsByTitleKeyType(&rights_ids, &count, i == 1))
{
LOGFILE("Unable to retrieve %s rights IDs!", i == 0 ? "common" : "personalized");
LOGFILE("Unable to retrieve %s rights IDs!", g_tikTitleKeyTypeStrings[i]);
continue;
}
@ -473,6 +481,7 @@ static bool tikRetrieveRightsIdsByTitleKeyType(FsRightsId **out, u32 *out_count,
Result rc = 0;
u32 count = 0, ids_written = 0;
FsRightsId *rights_ids = NULL;
u8 str_idx = (personalized ? TikTitleKeyType_Personalized : TikTitleKeyType_Common);
*out = NULL;
*out_count = 0;
@ -480,27 +489,27 @@ static bool tikRetrieveRightsIdsByTitleKeyType(FsRightsId **out, u32 *out_count,
rc = (personalized ? esCountPersonalizedTicket((s32*)&count) : esCountCommonTicket((s32*)&count));
if (R_FAILED(rc))
{
LOGFILE("esCount%sTicket failed! (0x%08X).", personalized ? "Personalized" : "Common", rc);
LOGFILE("esCount%c%sTicket failed! (0x%08X).", toupper(g_tikTitleKeyTypeStrings[str_idx][0]), g_tikTitleKeyTypeStrings[str_idx] + 1);
return false;
}
if (!count)
{
LOGFILE("No %s tickets available!", personalized ? "personalized" : "common");
LOGFILE("No %s tickets available!", g_tikTitleKeyTypeStrings[str_idx]);
return true;
}
rights_ids = calloc(count, sizeof(FsRightsId));
if (!rights_ids)
{
LOGFILE("Unable to allocate memory for %s rights IDs!", personalized ? "personalized" : "common");
LOGFILE("Unable to allocate memory for %s rights IDs!", g_tikTitleKeyTypeStrings[str_idx]);
return false;
}
rc = (personalized ? esListPersonalizedTicket((s32*)&ids_written, rights_ids, (s32)count) : esListCommonTicket((s32*)&ids_written, rights_ids, (s32)count));
if (R_FAILED(rc) || ids_written != count)
{
LOGFILE("esList%sTicket failed! (0x%08X). Wrote %u entries, expected %u entries.", personalized ? "Personalized" : "Common", rc, ids_written, count);
LOGFILE("esList%c%sTicket failed! (0x%08X). Wrote %u entries, expected %u entries.", toupper(g_tikTitleKeyTypeStrings[str_idx][0]), g_tikTitleKeyTypeStrings[str_idx] + 1, rc, ids_written, count);
free(rights_ids);
return false;
}
@ -511,6 +520,106 @@ static bool tikRetrieveRightsIdsByTitleKeyType(FsRightsId **out, u32 *out_count,
return true;
}
static u8 *tikRetrieveTicketEntryFromTicketBin(allocation_table_storage_ctx_t *fat_storage, u64 ticket_bin_size, u8 *buf, u64 buf_size, const FsRightsId *id, u8 titlekey_type)
{
if (!fat_storage || ticket_bin_size < SIGNED_TIK_MIN_SIZE || (ticket_bin_size % SIGNED_TIK_MAX_SIZE) != 0 || !buf || !buf_size || (buf_size % SIGNED_TIK_MAX_SIZE) != 0 || !id)
{
LOGFILE("Invalid parameters!");
return NULL;
}
u64 br = 0, total_br = 0;
u8 *out_tik = NULL;
Aes128CtrContext ctr_ctx = {0};
u8 null_ctr[AES_BLOCK_SIZE] = {0}, ctr[AES_BLOCK_SIZE] = {0}, dec_tik[SIGNED_TIK_MAX_SIZE] = {0};
bool is_9x = hosversionAtLeast(9, 0, 0);
if (is_9x && !memRetrieveFullProgramMemory(&g_esMemoryLocation))
{
LOGFILE("Failed to retrieve ES program memory!");
return NULL;
}
while(total_br < ticket_bin_size)
{
if (buf_size > (ticket_bin_size - total_br)) buf_size = (ticket_bin_size - total_br);
br = save_allocation_table_storage_read(fat_storage, buf, total_br, buf_size);
if (br != buf_size)
{
LOGFILE("Failed to read 0x%lX bytes chunk at offset 0x%lX from \"%s\" in ES %s ticket system save!", buf_size, total_br, TIK_SAVEFILE_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type]);
break;
}
for(u64 i = 0; i < buf_size; i += SIGNED_TIK_MAX_SIZE)
{
if ((buf_size - i) < SIGNED_TIK_MIN_SIZE) break;
u8 *cur_tik = (buf + i);
u64 tik_offset = (total_br + i);
TikCommonBlock *tik_common_block = tikGetCommonBlock(cur_tik);
if (!tik_common_block)
{
/* Check if we're dealing with a padding block. */
if (!memcmp(cur_tik, null_ctr, sizeof(null_ctr))) continue;
/* We're most likely dealing with an encrypted ticket. Don't proceed if HOS version isn't at least 9.0.0. */
if (!is_9x) continue;
/* Sad path. We need to retrieve the CTR key/IV from ES program memory in order to decrypt this ticket. */
for(u64 j = 0; j < g_esMemoryLocation.data_size; j += ES_CTRKEY_ENTRY_ALIGNMENT)
{
if ((g_esMemoryLocation.data_size - j) < (sizeof(tikEsCtrKeyEntry9x) * 2)) break;
/* Check if the key indexes are valid. idx2 should always be an odd number.*/
tikEsCtrKeyPattern9x *pattern = (tikEsCtrKeyPattern9x*)(g_esMemoryLocation.data + j);
if (pattern->idx2 != (pattern->idx1 + 1) || !(pattern->idx2 & 1)) continue;
/* Seems like indexes are valid. Check if the key is not null and if the CTR is. */
tikEsCtrKeyEntry9x *key_entry = (tikEsCtrKeyEntry9x*)pattern;
if (!memcmp(key_entry->key, null_ctr, sizeof(null_ctr)) || memcmp(key_entry->ctr, null_ctr, sizeof(null_ctr)) != 0) continue;
/* Check if we can decrypt the current ticket with this data. */
memset(&ctr_ctx, 0, sizeof(Aes128CtrContext));
aes128CtrInitializePartialCtr(ctr, key_entry->ctr, tik_offset);
aes128CtrContextCreate(&ctr_ctx, key_entry->key, ctr);
aes128CtrCrypt(&ctr_ctx, dec_tik, cur_tik, SIGNED_TIK_MAX_SIZE);
if ((tik_common_block = tikGetCommonBlock(dec_tik)) != NULL && !strncmp(tik_common_block->issuer, "Root", 4))
{
/* Ticket successfully decrypted. */
memcpy(cur_tik, dec_tik, SIGNED_TIK_MAX_SIZE);
tik_common_block = tikGetCommonBlock(cur_tik);
break;
}
}
/* Don't proceed if we couldn't decrypt the ticket. */
if (!tik_common_block || strncmp(tik_common_block->issuer, "Root", 4) != 0) continue;
}
/* Check if the rights ID from the ticket common block matches the one we're looking for. */
if (!memcmp(tik_common_block->rights_id.c, id->c, 0x10))
{
/* Jackpot. */
out_tik = cur_tik;
break;
}
}
total_br += br;
if (out_tik) break;
}
if (is_9x) memFreeMemoryLocation(&g_esMemoryLocation);
return out_tik;
}
static bool tikGetTicketTypeAndSize(void *data, u64 data_size, u8 *out_type, u64 *out_size)
{
u32 sig_type = 0;

View file

@ -55,8 +55,8 @@ typedef enum {
TikPropertyMask_SharedTitle = BIT(1),
TikPropertyMask_AllContents = BIT(2),
TikPropertyMask_DeviceLinkIndepedent = BIT(3),
TikPropertyMask_Volatile = BIT(4),
TikPropertyMask_ELicenseRequired = BIT(5)
TikPropertyMask_Volatile = BIT(4), ///< Used to determine if the ticket copy inside ticket.bin should be encrypted or not.
TikPropertyMask_ELicenseRequired = BIT(5) ///< Used to determine if the console should connect to the Internet to perform elicense verification.
} TikPropertyMask;
/// Placed after the ticket signature block.
@ -167,6 +167,7 @@ typedef struct {
u8 data[SIGNED_TIK_MAX_SIZE]; ///< Raw ticket data.
u8 enc_titlekey[0x10]; ///< Titlekey with titlekek crypto (RSA-OAEP unwrapped if dealing with a TikTitleKeyType_Personalized ticket).
u8 dec_titlekey[0x10]; ///< Titlekey without titlekek crypto. Ready to use for NCA FS section decryption.
char rights_id_str[0x21]; ///< Character string representation of the rights ID from the ticket.
} Ticket;
/// Retrieves a ticket from either the ES ticket system savedata file (eMMC BIS System partition) or the secure hash FS partition from an inserted gamecard, using a Rights ID value.

View file

@ -1377,12 +1377,12 @@ static bool titleRetrieveContentMetaKeysFromDatabase(u8 storage_id)
for(u32 j = 0; j < cur_title_info->content_count; j++)
{
titleConvertNcmContentSizeToU64(cur_title_info->content_infos[j].size, &tmp_size);
cur_title_info->title_size += tmp_size;
cur_title_info->size += tmp_size;
}
}
/* Generate formatted title size string. */
utilsGenerateFormattedSizeString(cur_title_info->title_size, cur_title_info->title_size_str, sizeof(cur_title_info->title_size_str));
utilsGenerateFormattedSizeString(cur_title_info->size, cur_title_info->size_str, sizeof(cur_title_info->size_str));
}
/* Update title info count. */

View file

@ -47,8 +47,8 @@ typedef struct _TitleInfo {
VersionType1 version; ///< Holds the same value from meta_key.version.
u32 content_count; ///< Content info count.
NcmContentInfo *content_infos; ///< Content info entries from this title.
u64 title_size; ///< Total title size.
char title_size_str[32]; ///< Total title size string.
u64 size; ///< Total title size.
char size_str[32]; ///< Total title size string.
TitleApplicationMetadata *app_metadata; ///< Only available for system titles and applications.
struct _TitleInfo *parent, *previous, *next; ///< Used with TitleInfo entries from user applications, patches and add-on contents. The parent pointer is unused in user applications.
} TitleInfo;
@ -222,4 +222,28 @@ NX_INLINE NcmContentInfo *titleGetContentInfoByTypeAndIdOffset(TitleInfo *info,
return NULL;
}
NX_INLINE u32 titleGetCountFromInfoBlock(TitleInfo *title_info)
{
if (!title_info) return 0;
u32 count = 1;
TitleInfo *cur_info = title_info->previous;
while(cur_info)
{
count++;
cur_info = cur_info->previous;
}
cur_info = title_info->next;
while(cur_info)
{
count++;
cur_info = cur_info->next;
}
return count;
}
#endif /* __TITLE_H__ */

View file

@ -672,7 +672,7 @@ static bool usbInitializeComms(void)
goto end;
}
if (hosversionAtLeast(5,0,0))
if (hosversionAtLeast(5, 0, 0))
{
u8 manufacturer = 0, product = 0, serial_number = 0;
static const u16 supported_langs[1] = { 0x0409 };
@ -805,7 +805,7 @@ static bool usbInitializeComms(void)
goto end;
}
if (hosversionAtLeast(5,0,0))
if (hosversionAtLeast(5, 0, 0))
{
rc = usbDsEnable();
if (R_FAILED(rc))
@ -856,7 +856,7 @@ static void usbFreeDeviceInterface(void)
NX_INLINE bool usbInitializeDeviceInterface(void)
{
return (hosversionAtLeast(5,0,0) ? usbInitializeDeviceInterface5x() : usbInitializeDeviceInterface1x());
return (hosversionAtLeast(5, 0, 0) ? usbInitializeDeviceInterface5x() : usbInitializeDeviceInterface1x());
}
static bool usbInitializeDeviceInterface5x(void)

View file

@ -680,6 +680,28 @@ void utilsCreateDirectoryTree(const char *path, bool create_last_element)
utilsCommitFileSystemChangesByPath(path);
}
char *utilsGeneratePath(const char *prefix, const char *filename, const char *extension)
{
if (!prefix || !*prefix || !filename || !*filename || !extension || !*extension)
{
LOGFILE("Invalid parameters!");
return NULL;
}
char *path = NULL;
size_t path_len = (strlen(prefix) + strlen(filename) + strlen(extension) + 1);
if (!(path = calloc(path_len, sizeof(char))))
{
LOGFILE("Failed to allocate 0x%lX bytes for output path!", path_len);
return NULL;
}
sprintf(path, "%s%s%s", prefix, filename, extension);
return path;
}
bool utilsAppletModeCheck(void)
{
return (g_programAppletType != AppletType_Application && g_programAppletType != AppletType_SystemApplication);

View file

@ -115,6 +115,8 @@ bool utilsCreateConcatenationFile(const char *path);
void utilsCreateDirectoryTree(const char *path, bool create_last_element);
char *utilsGeneratePath(const char *prefix, const char *filename, const char *extension);
bool utilsAppletModeCheck(void);
void utilsChangeHomeButtonBlockStatus(bool block);

View file

@ -11,24 +11,23 @@ list of top level functions designed to alter nca data in order of (possible) us
* calls pfsGenerateEntryPatch
* calls ncaGenerateHierarchicalSha256Patch
* cnmtIsNcaPatchRequired -> not sure if i'll keep this
* missing wrapper for pfsWriteEntryPatchToMemoryBuffer
* missing wrapper for pfsWriteEntryPatchToMemoryBuffer !!!
* programInfoChangeAcidPublicKeyAndNcaSignature (Program)
* calls npdmChangeAcidPublicKeyAndNcaSignature (Program)
* requires programInfoGenerateNcaPatch to be effective
* calls npdmGenerateNcaPatch (Program)
* calls pfsGenerateEntryPatch
* calls ncaGenerateHierarchicalSha256Patch
* programInfoIsNcaPatchRequired -> not sure if i'll keep this
* calls npdmIsNcaPatchRequired -> not sure if i'll keep this
* call inside programInfoChangeAcidPublicKeyAndNcaSignature maybe ???
* missing wrapper for pfsWriteEntryPatchToMemoryBuffer
* calls npdmChangeAcidPublicKeyAndNcaSignature
* calls pfsGenerateEntryPatch
* calls ncaGenerateHierarchicalSha256Patch
* needs programInfoWriteNcaPatch to write patched data
* calls npdmWriteNcaPatch
* calls pfsWriteEntryPatchToMemoryBuffer
* calls ncaWriteHierarchicalSha256PatchToMemoryBuffer
* nacpGenerateNcaPatch (Control)
* calls romfsGenerateFileEntryPatch
* calls ncaGenerateHierarchicalSha256Patch / ncaGenerateHierarchicalIntegrityPatch
* nacpIsNcaPatchRequired -> not sure if i'll keep this
* missing wrapper for romfsWriteFileEntryPatchToMemoryBuffer
* nacpIsNcaPatchRequired is used to check if a nacp patch was applied
* missing wrapper for romfsWriteFileEntryPatchToMemoryBuffer !!!
* missing functions for nacp mods !!!
* ncaIsHeaderDirty (doesn't modify anything per se, but it's used to check if any of the functions above has been used, basically)
* ncaEncryptHeader (doesn't modify anything per se, but it's used to generate new encrypted header data if needed)
@ -58,9 +57,11 @@ minor steps to take into account:
todo:
nca: functions for fs section lookup? (could just let the user choose...)
nca: support for compressed fs sections?
nca: support for sparse sections?
tik: option to wipe elicense property mask (otherwise, the console will attempt to connect to the Internet to perform elicense verification before launching the title the ticket belongs to)
tik: option to wipe volatile property mask (otherwise, the imported ticket will use an additional aes-ctr crypto layer in ticket.bin)
tik: automatically dump tickets to the SD card?
tik: use dumped tickets when the original ones can't be found in the ES savefile?
@ -73,6 +74,7 @@ todo:
bktr: functions to display filelist (wrappers for romfs functions tbh)
title: fix titleinfo issue
title: more functions for title lookup? (filters, patches / aoc, etc.)
title: more functions for content lookup? (based on id)
title: parse the update partition from gamecards (if available) to generate ncmcontentinfo data for all update titles