mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-26 04:02:11 +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:
parent
bd98fd4c32
commit
15431ec2c8
32 changed files with 687 additions and 265 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -7,6 +7,7 @@ build
|
||||||
/*.pfs0
|
/*.pfs0
|
||||||
/*.lst
|
/*.lst
|
||||||
/*.tar.bz2
|
/*.tar.bz2
|
||||||
|
/code_templates/nsp_dumper.c
|
||||||
/code_templates/tmp/*
|
/code_templates/tmp/*
|
||||||
/source/main.c
|
/source/main.c
|
||||||
/*.log
|
/*.log
|
||||||
|
|
7
build.sh
7
build.sh
|
@ -21,13 +21,12 @@ for f in ./code_templates/*.c; do
|
||||||
rm -f ./source/main.c
|
rm -f ./source/main.c
|
||||||
cp $f ./source/main.c
|
cp $f ./source/main.c
|
||||||
|
|
||||||
{
|
make clean &> /dev/null
|
||||||
make clean
|
make -j 12 BUILD_TYPE="$filename"
|
||||||
make -j 12 BUILD_TYPE="$filename"
|
|
||||||
}
|
|
||||||
|
|
||||||
mkdir ./code_templates/tmp/$filename
|
mkdir ./code_templates/tmp/$filename
|
||||||
cp ./nxdumptool-rewrite.nro ./code_templates/tmp/$filename/nxdumptool-rewrite.nro
|
cp ./nxdumptool-rewrite.nro ./code_templates/tmp/$filename/nxdumptool-rewrite.nro
|
||||||
|
#cp ./nxdumptool-rewrite.elf ./code_templates/tmp/$filename/nxdumptool-rewrite.elf
|
||||||
done
|
done
|
||||||
|
|
||||||
cd ./code_templates/tmp
|
cd ./code_templates/tmp
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
g_titleInfo[i].version.minor_relstep);
|
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, "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, "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);
|
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++)
|
for(u32 j = 0; j < g_titleInfo[i].content_count; j++)
|
||||||
|
|
52
source/aes.h
52
source/aes.h
|
@ -28,4 +28,56 @@
|
||||||
/// 'dst' and 'src' can both point to the same address.
|
/// '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);
|
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__ */
|
#endif /* __AES_H__ */
|
||||||
|
|
|
@ -40,7 +40,7 @@ typedef enum {
|
||||||
typedef struct {
|
typedef struct {
|
||||||
u8 type; ///< BfttfFontType.
|
u8 type; ///< BfttfFontType.
|
||||||
u32 size; ///< Decoded BFTFF font size.
|
u32 size; ///< Decoded BFTFF font size.
|
||||||
u8 *ptr; ///< Pointer to font data.
|
void *ptr; ///< Pointer to font data.
|
||||||
} BfttfFontData;
|
} BfttfFontData;
|
||||||
|
|
||||||
/// Initializes the BFTTF interface.
|
/// Initializes the BFTTF interface.
|
||||||
|
|
|
@ -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.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 || \
|
__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.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!");
|
LOGFILE("Invalid parameters!");
|
||||||
return false;
|
return false;
|
||||||
|
@ -218,7 +219,7 @@ end:
|
||||||
|
|
||||||
bool bktrReadFileSystemData(BktrContext *ctx, void *out, u64 read_size, u64 offset)
|
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!");
|
LOGFILE("Invalid parameters!");
|
||||||
return false;
|
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)
|
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 || \
|
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)
|
||||||
!out || !read_size || offset >= file_entry->size || (offset + read_size) > file_entry->size)
|
|
||||||
{
|
{
|
||||||
LOGFILE("Invalid parameters!");
|
LOGFILE("Invalid parameters!");
|
||||||
return false;
|
return false;
|
||||||
|
@ -255,7 +255,7 @@ bool bktrReadFileEntryData(BktrContext *ctx, RomFileSystemFileEntry *file_entry,
|
||||||
|
|
||||||
bool bktrIsFileEntryUpdated(BktrContext *ctx, RomFileSystemFileEntry *file_entry, bool *out)
|
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!");
|
LOGFILE("Invalid parameters!");
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -90,17 +90,6 @@ typedef struct {
|
||||||
/// Initializes a BKTR context.
|
/// Initializes a BKTR context.
|
||||||
bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ctx, NcaFsSectionContext *update_nca_fs_ctx);
|
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.
|
/// Reads raw filesystem data using a BKTR context.
|
||||||
/// Input offset must be relative to the start of the patched RomFS image.
|
/// Input offset must be relative to the start of the patched RomFS image.
|
||||||
bool bktrReadFileSystemData(BktrContext *ctx, void *out, u64 read_size, u64 offset);
|
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);
|
bool bktrIsFileEntryUpdated(BktrContext *ctx, RomFileSystemFileEntry *file_entry, bool *out);
|
||||||
|
|
||||||
/// Miscellaneous functions.
|
/// 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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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__ */
|
#endif /* __BKTR_H__ */
|
||||||
|
|
|
@ -124,9 +124,6 @@ bool cnmtInitializeContext(ContentMetaContext *out, NcaContext *nca_ctx)
|
||||||
/* Calculate SHA-256 checksum for the whole raw CNMT. */
|
/* Calculate SHA-256 checksum for the whole raw CNMT. */
|
||||||
sha256CalculateHash(out->raw_data_hash, out->raw_data, out->raw_data_size);
|
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. */
|
/* Verify packaged header. */
|
||||||
out->packaged_header = (ContentMetaPackagedContentMetaHeader*)out->raw_data;
|
out->packaged_header = (ContentMetaPackagedContentMetaHeader*)out->raw_data;
|
||||||
cur_offset += sizeof(ContentMetaPackagedContentMetaHeader);
|
cur_offset += sizeof(ContentMetaPackagedContentMetaHeader);
|
||||||
|
@ -235,6 +232,13 @@ bool cnmtInitializeContext(ContentMetaContext *out, NcaContext *nca_ctx)
|
||||||
goto end;
|
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;
|
success = true;
|
||||||
|
|
||||||
end:
|
end:
|
||||||
|
|
|
@ -102,7 +102,7 @@ static u8 g_gameCardStorageCurrentArea = GameCardStorageArea_None;
|
||||||
static u8 *g_gameCardReadBuf = NULL;
|
static u8 *g_gameCardReadBuf = NULL;
|
||||||
|
|
||||||
static GameCardHeader g_gameCardHeader = {0};
|
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 u64 g_gameCardCapacity = 0;
|
||||||
|
|
||||||
static u8 *g_gameCardHfsRootHeader = NULL; /// GameCardHashFileSystemHeader + (entry_count * GameCardHashFileSystemEntry) + Name Table.
|
static u8 *g_gameCardHfsRootHeader = NULL; /// GameCardHashFileSystemHeader + (entry_count * GameCardHashFileSystemEntry) + Name Table.
|
||||||
|
@ -325,7 +325,7 @@ bool gamecardGetTotalSize(u64 *out)
|
||||||
{
|
{
|
||||||
mutexLock(&g_gamecardMutex);
|
mutexLock(&g_gamecardMutex);
|
||||||
bool ret = (g_gameCardInserted && g_gameCardInfoLoaded && out);
|
bool ret = (g_gameCardInserted && g_gameCardInfoLoaded && out);
|
||||||
if (ret) *out = (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize);
|
if (ret) *out = g_gameCardStorageTotalSize;
|
||||||
mutexUnlock(&g_gamecardMutex);
|
mutexUnlock(&g_gamecardMutex);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -744,8 +744,7 @@ static void gamecardFreeInfo(void)
|
||||||
{
|
{
|
||||||
memset(&g_gameCardHeader, 0, sizeof(GameCardHeader));
|
memset(&g_gameCardHeader, 0, sizeof(GameCardHeader));
|
||||||
|
|
||||||
g_gameCardStorageNormalAreaSize = 0;
|
g_gameCardStorageNormalAreaSize = g_gameCardStorageSecureAreaSize = g_gameCardStorageTotalSize = 0;
|
||||||
g_gameCardStorageSecureAreaSize = 0;
|
|
||||||
|
|
||||||
g_gameCardCapacity = 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. */
|
/* 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++)
|
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;
|
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));
|
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;
|
bool success = false;
|
||||||
|
|
||||||
if (!g_gameCardInserted || !g_gameCardStorageNormalAreaSize || !g_gameCardStorageSecureAreaSize || !out || !read_size || \
|
if (!g_gameCardInserted || !g_gameCardStorageNormalAreaSize || !g_gameCardStorageSecureAreaSize || !out || !read_size || (offset + read_size) > g_gameCardStorageTotalSize)
|
||||||
offset >= (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize) || (offset + read_size) > (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize))
|
|
||||||
{
|
{
|
||||||
LOGFILE("Invalid parameters!");
|
LOGFILE("Invalid parameters!");
|
||||||
goto end;
|
goto end;
|
||||||
|
@ -1026,7 +1026,7 @@ static bool gamecardGetStorageAreasSizes(void)
|
||||||
if (R_FAILED(rc) || !area_size)
|
if (R_FAILED(rc) || !area_size)
|
||||||
{
|
{
|
||||||
LOGFILE("fsStorageGetSize failed to retrieve %s storage area size! (0x%08X).", GAMECARD_STORAGE_AREA_NAME(area), rc);
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1038,6 +1038,8 @@ static bool gamecardGetStorageAreasSizes(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g_gameCardStorageTotalSize = (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,7 @@ typedef struct {
|
||||||
u8 eticket_rsa_kek_personalized[0x10]; ///< eTicket RSA kek (console-specific).
|
u8 eticket_rsa_kek_personalized[0x10]; ///< eTicket RSA kek (console-specific).
|
||||||
u8 titlekeks[0x20][0x10]; ///< Titlekey encryption keys.
|
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.
|
u8 key_area_keys[0x20][3][0x10]; ///< Key area encryption keys.
|
||||||
} keysNcaKeyset;
|
} keysNcaKeyset;
|
||||||
|
|
||||||
|
@ -518,7 +518,7 @@ static bool keysReadKeysFromFile(void)
|
||||||
FILE *keys_file = NULL;
|
FILE *keys_file = NULL;
|
||||||
char *key = NULL, *value = NULL;
|
char *key = NULL, *value = NULL;
|
||||||
char test_name[0x40] = {0};
|
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");
|
keys_file = fopen(KEYS_FILE_PATH, "rb");
|
||||||
if (!keys_file)
|
if (!keys_file)
|
||||||
|
@ -538,12 +538,14 @@ static bool keysReadKeysFromFile(void)
|
||||||
if (strlen(key) == 15 && !strcasecmp(key, "eticket_rsa_kek"))
|
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;
|
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++;
|
key_count++;
|
||||||
} else
|
} else
|
||||||
if (strlen(key) == 28 && !strcasecmp(key, "eticket_rsa_kek_personalized"))
|
if (strlen(key) == 28 && !strcasecmp(key, "eticket_rsa_kek_personalized"))
|
||||||
{
|
{
|
||||||
/* This only appears on consoles that use the new PRODINFO key generation scheme. */
|
/* 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;
|
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++;
|
key_count++;
|
||||||
} else {
|
} else {
|
||||||
for(u32 i = 0; i < 0x20; i++)
|
for(u32 i = 0; i < 0x20; i++)
|
||||||
|
@ -593,5 +595,11 @@ static bool keysReadKeysFromFile(void)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!eticket_rsa_kek_available)
|
||||||
|
{
|
||||||
|
LOGFILE("\"eticket_rsa_kek\" unavailable in \"%s\"!", KEYS_FILE_PATH);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,6 +81,10 @@ bool legalInfoInitializeContext(LegalInfoContext *out, NcaContext *nca_ctx)
|
||||||
/* Update NCA context pointer in output context. */
|
/* Update NCA context pointer in output context. */
|
||||||
out->nca_ctx = nca_ctx;
|
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;
|
success = true;
|
||||||
|
|
||||||
end:
|
end:
|
||||||
|
|
|
@ -128,8 +128,9 @@ static bool memRetrieveProgramMemory(MemoryLocation *location, bool is_segment)
|
||||||
mem_type = (u8)(mem_info.type & 0xFF);
|
mem_type = (u8)(mem_info.type & 0xFF);
|
||||||
|
|
||||||
/* Code to allow for bitmasking segments. */
|
/* 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) || \
|
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 && \
|
||||||
(is_segment && (mem_type == MemType_CodeStatic || mem_type == MemType_CodeMutable) && (((segment <<= 1) >> 1) & location->mask))))
|
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(). */
|
/* If location->data == NULL, realloc() will essentially act as a malloc(). */
|
||||||
tmp = realloc(location->data, location->data_size + mem_info.size);
|
tmp = realloc(location->data, location->data_size + mem_info.size);
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
#define FS_SYSMODULE_TID (u64)0x0100000000000000
|
#define FS_SYSMODULE_TID (u64)0x0100000000000000
|
||||||
#define BOOT_SYSMODULE_TID (u64)0x0100000000000005
|
#define BOOT_SYSMODULE_TID (u64)0x0100000000000005
|
||||||
#define SPL_SYSMODULE_TID (u64)0x0100000000000028
|
#define SPL_SYSMODULE_TID (u64)0x0100000000000028
|
||||||
|
#define ES_SYSMODULE_TID (u64)0x0100000000000033
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
MemoryProgramSegmentType_Text = BIT(0),
|
MemoryProgramSegmentType_Text = BIT(0),
|
||||||
|
@ -46,8 +47,8 @@ typedef struct {
|
||||||
bool memRetrieveProgramMemorySegment(MemoryLocation *location);
|
bool memRetrieveProgramMemorySegment(MemoryLocation *location);
|
||||||
|
|
||||||
/// Retrieves full memory data from a running program.
|
/// Retrieves full memory data from a running program.
|
||||||
/// These are any type of memory pages with read permission (Perm_R) enabled.
|
/// 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, as well as memory pages with a populated MemoryAttribute value.
|
/// 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);
|
bool memRetrieveFullProgramMemory(MemoryLocation *location);
|
||||||
|
|
||||||
/// Frees a populated MemoryLocation element.
|
/// Frees a populated MemoryLocation element.
|
||||||
|
|
|
@ -255,9 +255,6 @@ bool nacpInitializeContext(NacpContext *out, NcaContext *nca_ctx)
|
||||||
/* Calculate SHA-256 checksum for the whole NACP. */
|
/* Calculate SHA-256 checksum for the whole NACP. */
|
||||||
sha256CalculateHash(out->data_hash, out->data, sizeof(_NacpStruct));
|
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. */
|
/* Retrieve NACP icon data. */
|
||||||
for(u8 i = 0; i < NacpSupportedLanguage_Count; i++)
|
for(u8 i = 0; i < NacpSupportedLanguage_Count; i++)
|
||||||
{
|
{
|
||||||
|
@ -317,6 +314,13 @@ bool nacpInitializeContext(NacpContext *out, NcaContext *nca_ctx)
|
||||||
out->icon_count++;
|
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;
|
success = true;
|
||||||
|
|
||||||
end:
|
end:
|
||||||
|
|
142
source/nca.c
142
source/nca.c
|
@ -51,15 +51,11 @@ NX_INLINE bool ncaIsVersion0KeyAreaEncrypted(NcaContext *ctx);
|
||||||
NX_INLINE u8 ncaGetKeyGenerationValue(NcaContext *ctx);
|
NX_INLINE u8 ncaGetKeyGenerationValue(NcaContext *ctx);
|
||||||
NX_INLINE bool ncaCheckRightsIdAvailability(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 _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 _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 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);
|
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 && \
|
if ((!out->rights_id_available || (out->rights_id_available && out->titlekey_retrieved)) && fs_ctx->encryption_type > NcaEncryptionType_None && \
|
||||||
fs_ctx->encryption_type <= NcaEncryptionType_AesCtrEx)
|
fs_ctx->encryption_type <= NcaEncryptionType_AesCtrEx)
|
||||||
{
|
{
|
||||||
/* Initialize section CTR. */
|
/* Initialize the partial AES counter for this section. */
|
||||||
ncaInitializeAesCtrIv(fs_ctx->ctr, fs_ctx->header.aes_ctr_upper_iv.value, fs_ctx->section_offset);
|
aes128CtrInitializePartialCtr(fs_ctx->ctr, fs_ctx->header.aes_ctr_upper_iv.value, fs_ctx->section_offset);
|
||||||
|
|
||||||
/* Initialize AES context. */
|
/* Initialize AES context. */
|
||||||
if (out->rights_id_available)
|
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)
|
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 || \
|
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!");
|
LOGFILE("Invalid parameters!");
|
||||||
return false;
|
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)
|
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 || \
|
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 > NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT || !buf || !buf_size || buf_offset >= ctx->content_size || (buf_offset + buf_size) > ctx->content_size) return;
|
!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++)
|
for(u32 i = 0; i < patch->hash_region_count; i++)
|
||||||
{
|
{
|
||||||
NcaHashDataPatch *hash_region_patch = &(patch->hash_region_patch[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)
|
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 || \
|
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 >= ctx->content_size || (buf_offset + buf_size) > ctx->content_size) return;
|
(buf_offset + buf_size) > ctx->content_size) return;
|
||||||
|
|
||||||
|
patch->written = true;
|
||||||
|
|
||||||
for(u32 i = 0; i < NCA_IVFC_LEVEL_COUNT; i++)
|
for(u32 i = 0; i < NCA_IVFC_LEVEL_COUNT; i++)
|
||||||
{
|
{
|
||||||
NcaHashDataPatch *hash_level_patch = &(patch->hash_level_patch[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)
|
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. */
|
/* Copy decrypted titlekey to the decrypted NCA key area. */
|
||||||
/* This will be reencrypted at a later stage. */
|
/* 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). */
|
/* 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. */
|
/* 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 || \
|
if (!ctx || ctx->content_size < NCA_FULL_HEADER_LENGTH || !buf || !buf_size || (buf_offset + buf_size) > ctx->content_size || \
|
||||||
(buf_offset + buf_size) > ctx->content_size || (ctx->format_version != NcaVersion_Nca0 && buf_offset >= NCA_FULL_HEADER_LENGTH)) return;
|
(ctx->format_version != NcaVersion_Nca0 && buf_offset >= NCA_FULL_HEADER_LENGTH)) return;
|
||||||
|
|
||||||
/* Attempt to write the NCA header. */
|
/* 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. */
|
/* Attempt to write NCA FS section headers. */
|
||||||
for(u8 i = 0; i < NCA_FS_HEADER_COUNT; i++)
|
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;
|
if (!fs_ctx->enabled) continue;
|
||||||
|
|
||||||
u64 fs_header_offset = (ctx->format_version != NcaVersion_Nca0 ? (sizeof(NcaHeader) + (i * sizeof(NcaFsHeader))) : fs_ctx->section_offset);
|
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;
|
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)
|
static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, bool lock)
|
||||||
{
|
{
|
||||||
if (lock) mutexLock(&g_ncaCryptoBufferMutex);
|
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) || \
|
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 || \
|
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!");
|
LOGFILE("Invalid NCA FS section header parameters!");
|
||||||
goto end;
|
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;
|
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) || \
|
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 || \
|
(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)
|
||||||
(content_offset + read_size) > nca_ctx->content_size)
|
|
||||||
{
|
{
|
||||||
LOGFILE("Invalid NCA header parameters!");
|
LOGFILE("Invalid NCA header parameters!");
|
||||||
goto end;
|
goto end;
|
||||||
|
@ -788,7 +758,7 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size
|
||||||
} else
|
} else
|
||||||
if (ctx->encryption_type == NcaEncryptionType_AesCtr || ctx->encryption_type == NcaEncryptionType_AesCtrEx)
|
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);
|
aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
|
||||||
aes128CtrCrypt(&(ctx->ctr_ctx), out, out, read_size);
|
aes128CtrCrypt(&(ctx->ctr_ctx), out, out, read_size);
|
||||||
}
|
}
|
||||||
|
@ -829,7 +799,7 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size
|
||||||
} else
|
} else
|
||||||
if (ctx->encryption_type == NcaEncryptionType_AesCtr || ctx->encryption_type == NcaEncryptionType_AesCtrEx)
|
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);
|
aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
|
||||||
aes128CtrCrypt(&(ctx->ctr_ctx), g_ncaCryptoBuffer, g_ncaCryptoBuffer, chunk_size);
|
aes128CtrCrypt(&(ctx->ctr_ctx), g_ncaCryptoBuffer, g_ncaCryptoBuffer, chunk_size);
|
||||||
}
|
}
|
||||||
|
@ -852,8 +822,7 @@ static bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, voi
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
|
|
||||||
if (!g_ncaCryptoBuffer || !ctx || !ctx->enabled || !ctx->nca_ctx || ctx->section_num >= NCA_FS_HEADER_COUNT || ctx->section_offset < sizeof(NcaHeader) || \
|
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 || \
|
ctx->section_type != NcaFsSectionType_PatchRomFs || ctx->encryption_type != NcaEncryptionType_AesCtrEx || !out || !read_size || (offset + read_size) > ctx->section_size)
|
||||||
(offset + read_size) > ctx->section_size)
|
|
||||||
{
|
{
|
||||||
LOGFILE("Invalid NCA FS section header parameters!");
|
LOGFILE("Invalid NCA FS section header parameters!");
|
||||||
goto end;
|
goto end;
|
||||||
|
@ -866,7 +835,7 @@ static bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, voi
|
||||||
u64 data_start_offset = 0, chunk_size = 0, out_chunk_size = 0;
|
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) || \
|
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!");
|
LOGFILE("Invalid NCA header parameters!");
|
||||||
goto end;
|
goto end;
|
||||||
|
@ -883,7 +852,7 @@ static bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, voi
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Decrypt data */
|
/* Decrypt data */
|
||||||
ncaUpdateAesCtrExIv(ctx->ctr, ctr_val, content_offset);
|
aes128CtrUpdatePartialCtrEx(ctx->ctr, ctr_val, content_offset);
|
||||||
aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
|
aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
|
||||||
aes128CtrCrypt(&(ctx->ctr_ctx), out, out, read_size);
|
aes128CtrCrypt(&(ctx->ctr_ctx), out, out, read_size);
|
||||||
|
|
||||||
|
@ -909,7 +878,7 @@ static bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, voi
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Decrypt data. */
|
/* Decrypt data. */
|
||||||
ncaUpdateAesCtrExIv(ctx->ctr, ctr_val, block_start_offset);
|
aes128CtrUpdatePartialCtrEx(ctx->ctr, ctr_val, block_start_offset);
|
||||||
aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
|
aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
|
||||||
aes128CtrCrypt(&(ctx->ctr_ctx), g_ncaCryptoBuffer, g_ncaCryptoBuffer, chunk_size);
|
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 || \
|
(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 || \
|
!(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 || \
|
!(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!");
|
LOGFILE("Invalid parameters!");
|
||||||
goto end;
|
goto end;
|
||||||
|
@ -998,8 +967,8 @@ static bool ncaGenerateHashDataPatch(NcaFsSectionContext *ctx, const void *data,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Validate layer properties. */
|
/* 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 || \
|
if (hash_block_size <= 1 || !cur_layer_size || (cur_layer_offset + cur_layer_size) > ctx->section_size || (i > 1 && (!parent_layer_size || \
|
||||||
(i > 1 && (parent_layer_offset >= ctx->section_size || !parent_layer_size || (parent_layer_offset + parent_layer_size) > ctx->section_size)))
|
(parent_layer_offset + parent_layer_size) > ctx->section_size)))
|
||||||
{
|
{
|
||||||
LOGFILE("Invalid hierarchical parent/child layer!");
|
LOGFILE("Invalid hierarchical parent/child layer!");
|
||||||
goto end;
|
goto end;
|
||||||
|
@ -1143,11 +1112,11 @@ end:
|
||||||
return success;
|
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). */
|
/* 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 || \
|
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;
|
(patch_offset + patch_size) <= buf_offset) return false;
|
||||||
|
|
||||||
/* Overwrite buffer data using patch data. */
|
/* Overwrite buffer data using patch data. */
|
||||||
u64 patch_block_offset = (patch_offset < buf_offset ? (buf_offset - patch_offset) : 0);
|
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);
|
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);
|
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)
|
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) || \
|
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 || \
|
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!");
|
LOGFILE("Invalid NCA FS section header parameters!");
|
||||||
goto end;
|
goto end;
|
||||||
|
@ -1188,8 +1159,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const
|
||||||
u64 plain_chunk_offset = 0;
|
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) || \
|
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 || \
|
(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)
|
||||||
(content_offset + data_size) > nca_ctx->content_size)
|
|
||||||
{
|
{
|
||||||
LOGFILE("Invalid NCA header parameters!");
|
LOGFILE("Invalid NCA header parameters!");
|
||||||
goto end;
|
goto end;
|
||||||
|
@ -1225,7 +1195,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const
|
||||||
} else
|
} else
|
||||||
if (ctx->encryption_type == NcaEncryptionType_AesCtr)
|
if (ctx->encryption_type == NcaEncryptionType_AesCtr)
|
||||||
{
|
{
|
||||||
ncaUpdateAesCtrIv(ctx->ctr, content_offset);
|
aes128CtrUpdatePartialCtr(ctx->ctr, content_offset);
|
||||||
aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
|
aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
|
||||||
aes128CtrCrypt(&(ctx->ctr_ctx), out, out, data_size);
|
aes128CtrCrypt(&(ctx->ctr_ctx), out, out, data_size);
|
||||||
}
|
}
|
||||||
|
@ -1277,7 +1247,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const
|
||||||
} else
|
} else
|
||||||
if (ctx->encryption_type == NcaEncryptionType_AesCtr)
|
if (ctx->encryption_type == NcaEncryptionType_AesCtr)
|
||||||
{
|
{
|
||||||
ncaUpdateAesCtrIv(ctx->ctr, content_offset);
|
aes128CtrUpdatePartialCtr(ctx->ctr, content_offset);
|
||||||
aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
|
aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
|
||||||
aes128CtrCrypt(&(ctx->ctr_ctx), out, out, block_size);
|
aes128CtrCrypt(&(ctx->ctr_ctx), out, out, block_size);
|
||||||
}
|
}
|
||||||
|
|
27
source/nca.h
27
source/nca.h
|
@ -325,21 +325,26 @@ typedef struct {
|
||||||
NcaFsSectionContext fs_ctx[NCA_FS_HEADER_COUNT];
|
NcaFsSectionContext fs_ctx[NCA_FS_HEADER_COUNT];
|
||||||
NcaDecryptedKeyArea decrypted_key_area;
|
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.
|
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;
|
} NcaContext;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
u64 offset; ///< New data offset (relative to the start of the NCA content file).
|
bool written; ///< Set to true if this patch has already been written.
|
||||||
u64 size; ///< New data size.
|
u64 offset; ///< New data offset (relative to the start of the NCA content file).
|
||||||
u8 *data; ///< New data.
|
u64 size; ///< New data size.
|
||||||
|
u8 *data; ///< New data.
|
||||||
} NcaHashDataPatch;
|
} NcaHashDataPatch;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
bool written; ///< Set to true if all hash region patches have been written.
|
||||||
NcmContentId content_id;
|
NcmContentId content_id;
|
||||||
u32 hash_region_count;
|
u32 hash_region_count;
|
||||||
NcaHashDataPatch hash_region_patch[NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT];
|
NcaHashDataPatch hash_region_patch[NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT];
|
||||||
} NcaHierarchicalSha256Patch;
|
} NcaHierarchicalSha256Patch;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
bool written; ///< Set to true if all hash level patches have been written.
|
||||||
NcmContentId content_id;
|
NcmContentId content_id;
|
||||||
NcaHashDataPatch hash_level_patch[NCA_IVFC_LEVEL_COUNT];
|
NcaHashDataPatch hash_level_patch[NCA_IVFC_LEVEL_COUNT];
|
||||||
} NcaHierarchicalIntegrityPatch;
|
} 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.
|
/// 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.
|
/// '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);
|
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.
|
/// 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.
|
/// 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.
|
/// '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);
|
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.
|
/// 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);
|
void ncaRemoveTitlekeyCrypto(NcaContext *ctx);
|
||||||
|
|
||||||
|
@ -420,12 +431,6 @@ const char *ncaGetFsSectionTypeName(NcaFsSectionContext *ctx);
|
||||||
|
|
||||||
/// Helper inline functions.
|
/// 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)
|
NX_INLINE bool ncaIsHeaderDirty(NcaContext *ctx)
|
||||||
{
|
{
|
||||||
if (!ctx) return false;
|
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++)
|
for(u32 i = 0; i < hierarchical_sha256_data->hash_region_count; i++)
|
||||||
{
|
{
|
||||||
NcaRegion *hash_region = &(hierarchical_sha256_data->hash_region[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;
|
return true;
|
||||||
|
@ -456,7 +461,7 @@ NX_INLINE bool ncaValidateHierarchicalIntegrityOffsets(NcaIntegrityMetaInfo *int
|
||||||
for(u32 i = 0; i < NCA_IVFC_LEVEL_COUNT; i++)
|
for(u32 i = 0; i < NCA_IVFC_LEVEL_COUNT; i++)
|
||||||
{
|
{
|
||||||
NcaHierarchicalIntegrityVerificationLevelInformation *level_information = &(integrity_meta_info->info_level_hash.level_information[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;
|
return true;
|
||||||
|
|
|
@ -70,9 +70,6 @@ bool npdmInitializeContext(NpdmContext *out, PartitionFileSystemContext *pfs_ctx
|
||||||
goto end;
|
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. */
|
/* Verify meta header. */
|
||||||
out->meta_header = (NpdmMetaHeader*)out->raw_data;
|
out->meta_header = (NpdmMetaHeader*)out->raw_data;
|
||||||
cur_offset += sizeof(NpdmMetaHeader);
|
cur_offset += sizeof(NpdmMetaHeader);
|
||||||
|
@ -281,5 +278,34 @@ bool npdmChangeAcidPublicKeyAndNcaSignature(NpdmContext *npdm_ctx)
|
||||||
return false;
|
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;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -540,7 +540,6 @@ typedef struct {
|
||||||
///< Bear in mind that generating a patch modifies the NCA context.
|
///< 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.
|
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'.
|
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'.
|
NpdmMetaHeader *meta_header; ///< Pointer to the NpdmMetaHeader within 'raw_data'.
|
||||||
NpdmAcidHeader *acid_header; ///< Pointer to the NpdmAcidHeader 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.
|
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).
|
/// 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);
|
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);
|
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.
|
/// Helper inline functions.
|
||||||
|
|
||||||
NX_INLINE void npdmFreeContext(NpdmContext *npdm_ctx)
|
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)));
|
((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)
|
NX_INLINE u32 npdmGetKernelCapabilityDescriptorEntryValue(NpdmKernelCapabilityDescriptorEntry *entry)
|
||||||
{
|
{
|
||||||
return (entry ? (((entry->value + 1) & ~entry->value) - 1) : 0);
|
return (entry ? (((entry->value + 1) & ~entry->value) - 1) : 0);
|
||||||
|
|
165
source/pfs.c
165
source/pfs.c
|
@ -22,15 +22,11 @@
|
||||||
#include "pfs.h"
|
#include "pfs.h"
|
||||||
#include "npdm.h"
|
#include "npdm.h"
|
||||||
|
|
||||||
|
#define PFS_FULL_HEADER_ALIGNMENT 0x20
|
||||||
|
|
||||||
bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *nca_fs_ctx)
|
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 || \
|
NcaContext *nca_ctx = NULL;
|
||||||
nca_fs_ctx->header.hash_type != NcaHashType_HierarchicalSha256)
|
|
||||||
{
|
|
||||||
LOGFILE("Invalid parameters!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 magic = 0;
|
u32 magic = 0;
|
||||||
|
|
||||||
PartitionFileSystemHeader pfs_header = {0};
|
PartitionFileSystemHeader pfs_header = {0};
|
||||||
|
@ -39,6 +35,13 @@ bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *
|
||||||
u32 hash_region_count = 0;
|
u32 hash_region_count = 0;
|
||||||
NcaRegion *hash_region = NULL;
|
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. */
|
/* Free output context beforehand. */
|
||||||
pfsFreeContext(out);
|
pfsFreeContext(out);
|
||||||
|
|
||||||
|
@ -105,7 +108,7 @@ bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *
|
||||||
|
|
||||||
bool pfsReadPartitionData(PartitionFileSystemContext *ctx, void *out, u64 read_size, u64 offset)
|
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!");
|
LOGFILE("Invalid parameters!");
|
||||||
return false;
|
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)
|
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 || \
|
if (!ctx || !fs_entry || !fs_entry->size || (fs_entry->offset + fs_entry->size) > ctx->size || !out || !read_size || (offset + read_size) > fs_entry->size)
|
||||||
(offset + read_size) > fs_entry->size)
|
|
||||||
{
|
{
|
||||||
LOGFILE("Invalid parameters!");
|
LOGFILE("Invalid parameters!");
|
||||||
return false;
|
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)
|
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 || \
|
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_size || data_offset >= fs_entry->size || (data_offset + data_size) > fs_entry->size || !out)
|
(data_offset + data_size) > fs_entry->size || !out)
|
||||||
{
|
{
|
||||||
LOGFILE("Invalid parameters!");
|
LOGFILE("Invalid parameters!");
|
||||||
return false;
|
return false;
|
||||||
|
@ -221,3 +223,142 @@ bool pfsGenerateEntryPatch(PartitionFileSystemContext *ctx, PartitionFileSystemE
|
||||||
|
|
||||||
return true;
|
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;
|
||||||
|
}
|
||||||
|
|
34
source/pfs.h
34
source/pfs.h
|
@ -41,6 +41,7 @@ typedef struct {
|
||||||
u8 reserved[0x4];
|
u8 reserved[0x4];
|
||||||
} PartitionFileSystemEntry;
|
} PartitionFileSystemEntry;
|
||||||
|
|
||||||
|
/// Used with Partition FS sections from NCAs.
|
||||||
typedef struct {
|
typedef struct {
|
||||||
NcaFsSectionContext *nca_fs_ctx; ///< Used to read NCA FS section data.
|
NcaFsSectionContext *nca_fs_ctx; ///< Used to read NCA FS section data.
|
||||||
u64 offset; ///< Partition offset (relative to the start of the NCA FS section).
|
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.
|
u8 *header; ///< PartitionFileSystemHeader + (PartitionFileSystemEntry * entry_count) + Name Table.
|
||||||
} PartitionFileSystemContext;
|
} 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.
|
/// Initializes a Partition FS context.
|
||||||
bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext *nca_fs_ctx);
|
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.
|
/// 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);
|
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.
|
/// Miscellaneous functions.
|
||||||
|
|
||||||
NX_INLINE void pfsFreeContext(PartitionFileSystemContext *ctx)
|
NX_INLINE void pfsFreeContext(PartitionFileSystemContext *ctx)
|
||||||
|
@ -134,4 +153,19 @@ NX_INLINE void pfsFreeEntryPatch(NcaHierarchicalSha256Patch *patch)
|
||||||
ncaFreeHierarchicalSha256Patch(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__ */
|
#endif /* __PFS_H__ */
|
||||||
|
|
|
@ -140,6 +140,10 @@ bool programInfoInitializeContext(ProgramInfoContext *out, NcaContext *nca_ctx)
|
||||||
/* Update output context. */
|
/* Update output context. */
|
||||||
out->nca_ctx = nca_ctx;
|
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;
|
success = true;
|
||||||
|
|
||||||
end:
|
end:
|
||||||
|
|
|
@ -75,14 +75,9 @@ NX_INLINE bool programInfoChangeAcidPublicKeyAndNcaSignature(ProgramInfoContext
|
||||||
return (programInfoIsValidContext(program_info_ctx) && npdmChangeAcidPublicKeyAndNcaSignature(&(program_info_ctx->npdm_ctx)));
|
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)));
|
if (program_info_ctx) npdmWriteNcaPatch(&(program_info_ctx->npdm_ctx), buf, buf_size, buf_offset);
|
||||||
}
|
|
||||||
|
|
||||||
NX_INLINE bool programInfoGenerateNcaPatch(ProgramInfoContext *program_info_ctx)
|
|
||||||
{
|
|
||||||
return (programInfoIsValidContext(program_info_ctx) && npdmGenerateNcaPatch(&(program_info_ctx->npdm_ctx)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* __PROGRAM_INFO_H__ */
|
#endif /* __PROGRAM_INFO_H__ */
|
||||||
|
|
|
@ -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 && \
|
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_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!");
|
LOGFILE("Invalid parameters!");
|
||||||
return false;
|
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);
|
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);
|
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!");
|
LOGFILE("Invalid RomFS directory entries table!");
|
||||||
return false;
|
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);
|
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);
|
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!");
|
LOGFILE("Invalid RomFS file entries table!");
|
||||||
goto end;
|
goto end;
|
||||||
|
@ -156,7 +156,7 @@ end:
|
||||||
|
|
||||||
bool romfsReadFileSystemData(RomFileSystemContext *ctx, void *out, u64 read_size, u64 offset)
|
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!");
|
LOGFILE("Invalid parameters!");
|
||||||
return false;
|
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)
|
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 || \
|
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)
|
||||||
!out || !read_size || offset >= file_entry->size || (offset + read_size) > file_entry->size)
|
|
||||||
{
|
{
|
||||||
LOGFILE("Invalid parameters!");
|
LOGFILE("Invalid parameters!");
|
||||||
return false;
|
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)
|
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 || \
|
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 || \
|
!file_entry->size || (file_entry->offset + file_entry->size) > ctx->size || !data || !data_size || (data_offset + data_size) > file_entry->size || !out)
|
||||||
(data_offset + data_size) > file_entry->size || !out)
|
|
||||||
{
|
{
|
||||||
LOGFILE("Invalid parameters!");
|
LOGFILE("Invalid parameters!");
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -1746,6 +1746,32 @@ save_ctx_t *save_open_savefile(const char *path, u32 action)
|
||||||
|
|
||||||
open_savefile = true;
|
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));
|
save_ctx = calloc(1, sizeof(save_ctx_t));
|
||||||
if (!save_ctx)
|
if (!save_ctx)
|
||||||
{
|
{
|
||||||
|
|
211
source/tik.c
211
source/tik.c
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* tik.c
|
* tik.c
|
||||||
*
|
*
|
||||||
* Copyright (c) 2019, shchmue.
|
* Copyright (c) 2019-2020, shchmue.
|
||||||
* Copyright (c) 2020, DarkMatterCore <pabloacurielz@gmail.com>.
|
* Copyright (c) 2020, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||||
*
|
*
|
||||||
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
|
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
|
||||||
|
@ -27,6 +27,8 @@
|
||||||
#include "keys.h"
|
#include "keys.h"
|
||||||
#include "rsa.h"
|
#include "rsa.h"
|
||||||
#include "gamecard.h"
|
#include "gamecard.h"
|
||||||
|
#include "mem.h"
|
||||||
|
#include "aes.h"
|
||||||
|
|
||||||
#define TIK_COMMON_SAVEFILE_PATH BIS_SYSTEM_PARTITION_MOUNT_NAME "/save/80000000000000e1"
|
#define TIK_COMMON_SAVEFILE_PATH BIS_SYSTEM_PARTITION_MOUNT_NAME "/save/80000000000000e1"
|
||||||
#define TIK_PERSONALIZED_SAVEFILE_PATH BIS_SYSTEM_PARTITION_MOUNT_NAME "/save/80000000000000e2"
|
#define TIK_PERSONALIZED_SAVEFILE_PATH BIS_SYSTEM_PARTITION_MOUNT_NAME "/save/80000000000000e2"
|
||||||
|
@ -34,6 +36,8 @@
|
||||||
|
|
||||||
#define ETICKET_DEVKEY_PUBLIC_EXPONENT 0x10001
|
#define ETICKET_DEVKEY_PUBLIC_EXPONENT 0x10001
|
||||||
|
|
||||||
|
#define ES_CTRKEY_ENTRY_ALIGNMENT 0x8
|
||||||
|
|
||||||
/* Type definitions. */
|
/* Type definitions. */
|
||||||
|
|
||||||
/// Everything after the AES CTR is encrypted.
|
/// Everything after the AES CTR is encrypted.
|
||||||
|
@ -47,6 +51,22 @@ typedef struct {
|
||||||
u8 ghash[0x10];
|
u8 ghash[0x10];
|
||||||
} tikEticketDeviceKeyData;
|
} 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. */
|
/* Global variables. */
|
||||||
|
|
||||||
static SetCalRsa2048DeviceKey g_eTicketDeviceKey = {0};
|
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
|
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. */
|
/* Function prototypes. */
|
||||||
|
|
||||||
static bool tikRetrieveTicketFromGameCardByRightsId(Ticket *dst, const FsRightsId *id);
|
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 tikGetTitleKeyTypeFromRightsId(const FsRightsId *id, u8 *out);
|
||||||
static bool tikRetrieveRightsIdsByTitleKeyType(FsRightsId **out, u32 *out_count, bool personalized);
|
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);
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TikCommonBlock *tik_common_block = NULL;
|
||||||
|
|
||||||
/* Check if this ticket has already been retrieved. */
|
/* 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)
|
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;
|
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;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,6 +217,7 @@ bool tikConvertPersonalizedTicketToCommonTicket(Ticket *tik, u8 **out_raw_cert_c
|
||||||
|
|
||||||
/* Update the rest of the ticket fields. */
|
/* Update the rest of the ticket fields. */
|
||||||
tik_common_block->titlekey_type = TikTitleKeyType_Common;
|
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->ticket_id = 0;
|
||||||
tik_common_block->device_id = 0;
|
tik_common_block->device_id = 0;
|
||||||
tik_common_block->account_id = 0;
|
tik_common_block->account_id = 0;
|
||||||
|
@ -245,7 +285,6 @@ static bool tikRetrieveTicketFromEsSaveDataByRightsId(Ticket *dst, const FsRight
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 i;
|
|
||||||
u8 titlekey_type = 0;
|
u8 titlekey_type = 0;
|
||||||
|
|
||||||
save_ctx_t *save_ctx = NULL;
|
save_ctx_t *save_ctx = NULL;
|
||||||
|
@ -253,10 +292,9 @@ static bool tikRetrieveTicketFromEsSaveDataByRightsId(Ticket *dst, const FsRight
|
||||||
u64 ticket_bin_size = 0;
|
u64 ticket_bin_size = 0;
|
||||||
|
|
||||||
u64 buf_size = (SIGNED_TIK_MAX_SIZE * 0x10);
|
u64 buf_size = (SIGNED_TIK_MAX_SIZE * 0x10);
|
||||||
u64 br = 0, total_br = 0;
|
u8 *buf = NULL, *ticket_entry = NULL;
|
||||||
u8 *ticket_bin_buf = NULL;
|
|
||||||
|
|
||||||
bool found_tik = false, success = false;
|
bool success = false;
|
||||||
|
|
||||||
if (!tikGetTitleKeyTypeFromRightsId(id, &titlekey_type))
|
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);
|
save_ctx = save_open_savefile(titlekey_type == TikTitleKeyType_Common ? TIK_COMMON_SAVEFILE_PATH : TIK_PERSONALIZED_SAVEFILE_PATH, 0);
|
||||||
if (!save_ctx)
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!save_get_fat_storage_from_file_entry_by_path(save_ctx, TIK_SAVEFILE_STORAGE_PATH, &fat_storage, &ticket_bin_size))
|
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;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ticket_bin_size < SIGNED_TIK_MIN_SIZE || (ticket_bin_size % SIGNED_TIK_MAX_SIZE) != 0)
|
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;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
ticket_bin_buf = malloc(buf_size);
|
buf = malloc(buf_size);
|
||||||
if (!ticket_bin_buf)
|
if (!buf)
|
||||||
{
|
{
|
||||||
LOGFILE("Unable to allocate 0x%lX bytes block for temporary read buffer!", buf_size);
|
LOGFILE("Unable to allocate 0x%lX bytes block for temporary read buffer!", buf_size);
|
||||||
goto end;
|
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);
|
LOGFILE("Unable to find a matching %s ticket entry for the provided Rights ID!", g_tikTitleKeyTypeStrings[titlekey_type]);
|
||||||
|
|
||||||
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!");
|
|
||||||
goto end;
|
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!");
|
LOGFILE("Unable to determine ticket type and size!");
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(dst->data, ticket_bin_buf + i, dst->size);
|
memcpy(dst->data, ticket_entry, dst->size);
|
||||||
|
|
||||||
success = true;
|
success = true;
|
||||||
|
|
||||||
end:
|
end:
|
||||||
if (ticket_bin_buf) free(ticket_bin_buf);
|
if (buf) free(buf);
|
||||||
|
|
||||||
if (save_ctx) save_close_savefile(save_ctx);
|
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))
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -473,6 +481,7 @@ static bool tikRetrieveRightsIdsByTitleKeyType(FsRightsId **out, u32 *out_count,
|
||||||
Result rc = 0;
|
Result rc = 0;
|
||||||
u32 count = 0, ids_written = 0;
|
u32 count = 0, ids_written = 0;
|
||||||
FsRightsId *rights_ids = NULL;
|
FsRightsId *rights_ids = NULL;
|
||||||
|
u8 str_idx = (personalized ? TikTitleKeyType_Personalized : TikTitleKeyType_Common);
|
||||||
|
|
||||||
*out = NULL;
|
*out = NULL;
|
||||||
*out_count = 0;
|
*out_count = 0;
|
||||||
|
@ -480,27 +489,27 @@ static bool tikRetrieveRightsIdsByTitleKeyType(FsRightsId **out, u32 *out_count,
|
||||||
rc = (personalized ? esCountPersonalizedTicket((s32*)&count) : esCountCommonTicket((s32*)&count));
|
rc = (personalized ? esCountPersonalizedTicket((s32*)&count) : esCountCommonTicket((s32*)&count));
|
||||||
if (R_FAILED(rc))
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!count)
|
if (!count)
|
||||||
{
|
{
|
||||||
LOGFILE("No %s tickets available!", personalized ? "personalized" : "common");
|
LOGFILE("No %s tickets available!", g_tikTitleKeyTypeStrings[str_idx]);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
rights_ids = calloc(count, sizeof(FsRightsId));
|
rights_ids = calloc(count, sizeof(FsRightsId));
|
||||||
if (!rights_ids)
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
rc = (personalized ? esListPersonalizedTicket((s32*)&ids_written, rights_ids, (s32)count) : esListCommonTicket((s32*)&ids_written, rights_ids, (s32)count));
|
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)
|
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);
|
free(rights_ids);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -511,6 +520,106 @@ static bool tikRetrieveRightsIdsByTitleKeyType(FsRightsId **out, u32 *out_count,
|
||||||
return true;
|
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)
|
static bool tikGetTicketTypeAndSize(void *data, u64 data_size, u8 *out_type, u64 *out_size)
|
||||||
{
|
{
|
||||||
u32 sig_type = 0;
|
u32 sig_type = 0;
|
||||||
|
|
|
@ -55,8 +55,8 @@ typedef enum {
|
||||||
TikPropertyMask_SharedTitle = BIT(1),
|
TikPropertyMask_SharedTitle = BIT(1),
|
||||||
TikPropertyMask_AllContents = BIT(2),
|
TikPropertyMask_AllContents = BIT(2),
|
||||||
TikPropertyMask_DeviceLinkIndepedent = BIT(3),
|
TikPropertyMask_DeviceLinkIndepedent = BIT(3),
|
||||||
TikPropertyMask_Volatile = BIT(4),
|
TikPropertyMask_Volatile = BIT(4), ///< Used to determine if the ticket copy inside ticket.bin should be encrypted or not.
|
||||||
TikPropertyMask_ELicenseRequired = BIT(5)
|
TikPropertyMask_ELicenseRequired = BIT(5) ///< Used to determine if the console should connect to the Internet to perform elicense verification.
|
||||||
} TikPropertyMask;
|
} TikPropertyMask;
|
||||||
|
|
||||||
/// Placed after the ticket signature block.
|
/// Placed after the ticket signature block.
|
||||||
|
@ -167,6 +167,7 @@ typedef struct {
|
||||||
u8 data[SIGNED_TIK_MAX_SIZE]; ///< Raw ticket data.
|
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 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.
|
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;
|
} 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.
|
/// 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.
|
||||||
|
|
|
@ -1377,12 +1377,12 @@ static bool titleRetrieveContentMetaKeysFromDatabase(u8 storage_id)
|
||||||
for(u32 j = 0; j < cur_title_info->content_count; j++)
|
for(u32 j = 0; j < cur_title_info->content_count; j++)
|
||||||
{
|
{
|
||||||
titleConvertNcmContentSizeToU64(cur_title_info->content_infos[j].size, &tmp_size);
|
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. */
|
/* 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. */
|
/* Update title info count. */
|
||||||
|
|
|
@ -47,8 +47,8 @@ typedef struct _TitleInfo {
|
||||||
VersionType1 version; ///< Holds the same value from meta_key.version.
|
VersionType1 version; ///< Holds the same value from meta_key.version.
|
||||||
u32 content_count; ///< Content info count.
|
u32 content_count; ///< Content info count.
|
||||||
NcmContentInfo *content_infos; ///< Content info entries from this title.
|
NcmContentInfo *content_infos; ///< Content info entries from this title.
|
||||||
u64 title_size; ///< Total title size.
|
u64 size; ///< Total title size.
|
||||||
char title_size_str[32]; ///< Total title size string.
|
char size_str[32]; ///< Total title size string.
|
||||||
TitleApplicationMetadata *app_metadata; ///< Only available for system titles and applications.
|
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.
|
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;
|
} TitleInfo;
|
||||||
|
@ -222,4 +222,28 @@ NX_INLINE NcmContentInfo *titleGetContentInfoByTypeAndIdOffset(TitleInfo *info,
|
||||||
return NULL;
|
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__ */
|
#endif /* __TITLE_H__ */
|
||||||
|
|
|
@ -672,7 +672,7 @@ static bool usbInitializeComms(void)
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hosversionAtLeast(5,0,0))
|
if (hosversionAtLeast(5, 0, 0))
|
||||||
{
|
{
|
||||||
u8 manufacturer = 0, product = 0, serial_number = 0;
|
u8 manufacturer = 0, product = 0, serial_number = 0;
|
||||||
static const u16 supported_langs[1] = { 0x0409 };
|
static const u16 supported_langs[1] = { 0x0409 };
|
||||||
|
@ -805,7 +805,7 @@ static bool usbInitializeComms(void)
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hosversionAtLeast(5,0,0))
|
if (hosversionAtLeast(5, 0, 0))
|
||||||
{
|
{
|
||||||
rc = usbDsEnable();
|
rc = usbDsEnable();
|
||||||
if (R_FAILED(rc))
|
if (R_FAILED(rc))
|
||||||
|
@ -856,7 +856,7 @@ static void usbFreeDeviceInterface(void)
|
||||||
|
|
||||||
NX_INLINE bool usbInitializeDeviceInterface(void)
|
NX_INLINE bool usbInitializeDeviceInterface(void)
|
||||||
{
|
{
|
||||||
return (hosversionAtLeast(5,0,0) ? usbInitializeDeviceInterface5x() : usbInitializeDeviceInterface1x());
|
return (hosversionAtLeast(5, 0, 0) ? usbInitializeDeviceInterface5x() : usbInitializeDeviceInterface1x());
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool usbInitializeDeviceInterface5x(void)
|
static bool usbInitializeDeviceInterface5x(void)
|
||||||
|
|
|
@ -680,6 +680,28 @@ void utilsCreateDirectoryTree(const char *path, bool create_last_element)
|
||||||
utilsCommitFileSystemChangesByPath(path);
|
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)
|
bool utilsAppletModeCheck(void)
|
||||||
{
|
{
|
||||||
return (g_programAppletType != AppletType_Application && g_programAppletType != AppletType_SystemApplication);
|
return (g_programAppletType != AppletType_Application && g_programAppletType != AppletType_SystemApplication);
|
||||||
|
|
|
@ -115,6 +115,8 @@ bool utilsCreateConcatenationFile(const char *path);
|
||||||
|
|
||||||
void utilsCreateDirectoryTree(const char *path, bool create_last_element);
|
void utilsCreateDirectoryTree(const char *path, bool create_last_element);
|
||||||
|
|
||||||
|
char *utilsGeneratePath(const char *prefix, const char *filename, const char *extension);
|
||||||
|
|
||||||
bool utilsAppletModeCheck(void);
|
bool utilsAppletModeCheck(void);
|
||||||
|
|
||||||
void utilsChangeHomeButtonBlockStatus(bool block);
|
void utilsChangeHomeButtonBlockStatus(bool block);
|
||||||
|
|
28
todo.txt
28
todo.txt
|
@ -11,24 +11,23 @@ list of top level functions designed to alter nca data in order of (possible) us
|
||||||
* calls pfsGenerateEntryPatch
|
* calls pfsGenerateEntryPatch
|
||||||
* calls ncaGenerateHierarchicalSha256Patch
|
* calls ncaGenerateHierarchicalSha256Patch
|
||||||
* cnmtIsNcaPatchRequired -> not sure if i'll keep this
|
* cnmtIsNcaPatchRequired -> not sure if i'll keep this
|
||||||
* missing wrapper for pfsWriteEntryPatchToMemoryBuffer
|
* missing wrapper for pfsWriteEntryPatchToMemoryBuffer !!!
|
||||||
|
|
||||||
* programInfoChangeAcidPublicKeyAndNcaSignature (Program)
|
* programInfoChangeAcidPublicKeyAndNcaSignature (Program)
|
||||||
* calls npdmChangeAcidPublicKeyAndNcaSignature (Program)
|
* calls npdmChangeAcidPublicKeyAndNcaSignature
|
||||||
* requires programInfoGenerateNcaPatch to be effective
|
* calls pfsGenerateEntryPatch
|
||||||
* calls npdmGenerateNcaPatch (Program)
|
* calls ncaGenerateHierarchicalSha256Patch
|
||||||
* calls pfsGenerateEntryPatch
|
* needs programInfoWriteNcaPatch to write patched data
|
||||||
* calls ncaGenerateHierarchicalSha256Patch
|
* calls npdmWriteNcaPatch
|
||||||
* programInfoIsNcaPatchRequired -> not sure if i'll keep this
|
* calls pfsWriteEntryPatchToMemoryBuffer
|
||||||
* calls npdmIsNcaPatchRequired -> not sure if i'll keep this
|
* calls ncaWriteHierarchicalSha256PatchToMemoryBuffer
|
||||||
* call inside programInfoChangeAcidPublicKeyAndNcaSignature maybe ???
|
|
||||||
* missing wrapper for pfsWriteEntryPatchToMemoryBuffer
|
|
||||||
|
|
||||||
* nacpGenerateNcaPatch (Control)
|
* nacpGenerateNcaPatch (Control)
|
||||||
* calls romfsGenerateFileEntryPatch
|
* calls romfsGenerateFileEntryPatch
|
||||||
* calls ncaGenerateHierarchicalSha256Patch / ncaGenerateHierarchicalIntegrityPatch
|
* calls ncaGenerateHierarchicalSha256Patch / ncaGenerateHierarchicalIntegrityPatch
|
||||||
* nacpIsNcaPatchRequired -> not sure if i'll keep this
|
* nacpIsNcaPatchRequired is used to check if a nacp patch was applied
|
||||||
* missing wrapper for romfsWriteFileEntryPatchToMemoryBuffer
|
* 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)
|
* 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)
|
* 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:
|
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 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: automatically dump tickets to the SD card?
|
||||||
tik: use dumped tickets when the original ones can't be found in the ES savefile?
|
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)
|
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 title lookup? (filters, patches / aoc, etc.)
|
||||||
title: more functions for content lookup? (based on id)
|
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
|
title: parse the update partition from gamecards (if available) to generate ncmcontentinfo data for all update titles
|
||||||
|
|
Loading…
Reference in a new issue