diff --git a/code_templates/sd_romfs_dumper.c b/code_templates/sd_romfs_dumper.c index f008a6d..3708055 100644 --- a/code_templates/sd_romfs_dumper.c +++ b/code_templates/sd_romfs_dumper.c @@ -99,14 +99,16 @@ static void read_thread_func(void *arg) goto end; } - u64 file_table_offset = 0; - u64 file_table_size = shared_data->romfs_ctx->file_table_size; RomFileSystemFileEntry *file_entry = NULL; char path[FS_MAX_PATH] = {0}; sprintf(path, "sdmc:/romfs"); - while(file_table_offset < file_table_size) + /* Reset current file table offset. */ + romfsResetFileTableOffset(shared_data->romfs_ctx); + + /* Loop through all file entries. */ + while(romfsCanMoveToNextFileEntry(shared_data->romfs_ctx)) { /* Check if the transfer has been cancelled by the user. */ if (shared_data->transfer_cancelled) @@ -130,7 +132,7 @@ static void read_thread_func(void *arg) } /* Retrieve RomFS file entry information. */ - shared_data->read_error = (!(file_entry = romfsGetFileEntryByOffset(shared_data->romfs_ctx, file_table_offset)) || \ + shared_data->read_error = (!(file_entry = romfsGetCurrentFileEntry(shared_data->romfs_ctx)) || \ !romfsGeneratePathFromFileEntry(shared_data->romfs_ctx, file_entry, path + 11, FS_MAX_PATH - 11, RomFileSystemPathIllegalCharReplaceType_KeepAsciiCharsOnly)); if (shared_data->read_error) { @@ -197,7 +199,13 @@ static void read_thread_func(void *arg) if (shared_data->read_error || shared_data->write_error || shared_data->transfer_cancelled) break; - file_table_offset += ALIGN_UP(sizeof(RomFileSystemFileEntry) + file_entry->name_length, 4); + /* Move to the next file entry. */ + shared_data->read_error = !romfsMoveToNextFileEntry(shared_data->romfs_ctx); + if (shared_data->read_error) + { + condvarWakeAll(&g_writeCondvar); + break; + } } /* Wait until the previous file data chunk has been written. */ diff --git a/code_templates/system_title_dumper.c b/code_templates/system_title_dumper.c index 09527fd..f84784a 100644 --- a/code_templates/system_title_dumper.c +++ b/code_templates/system_title_dumper.c @@ -160,7 +160,6 @@ static void dumpRomFs(TitleInfo *info, NcaFsSectionContext *nca_fs_ctx) { if (!buf || !info || !nca_fs_ctx) return; - u64 romfs_file_table_offset = 0; RomFileSystemContext romfs_ctx = {0}; RomFileSystemFileEntry *romfs_file_entry = NULL; @@ -178,12 +177,12 @@ static void dumpRomFs(TitleInfo *info, NcaFsSectionContext *nca_fs_ctx) utilsCreateDirectoryTree(path, true); path_len = strlen(path); - while(romfs_file_table_offset < romfs_ctx.file_table_size) + while(romfsCanMoveToNextFileEntry(&romfs_ctx)) { - if (!(romfs_file_entry = romfsGetFileEntryByOffset(&romfs_ctx, romfs_file_table_offset)) || \ + if (!(romfs_file_entry = romfsGetCurrentFileEntry(&romfs_ctx)) || \ !romfsGeneratePathFromFileEntry(&romfs_ctx, romfs_file_entry, path + path_len, sizeof(path) - path_len, RomFileSystemPathIllegalCharReplaceType_KeepAsciiCharsOnly)) { - consolePrint("romfs get entry / generate path failed for 0x%lX!\n", romfs_file_table_offset); + consolePrint("romfs get entry / generate path failed for 0x%lX!\n", romfs_ctx.cur_file_offset); goto end; } @@ -215,7 +214,11 @@ static void dumpRomFs(TitleInfo *info, NcaFsSectionContext *nca_fs_ctx) fclose(filefd); filefd = NULL; - romfs_file_table_offset += ALIGN_UP(sizeof(RomFileSystemFileEntry) + romfs_file_entry->name_length, 4); + if (!romfsMoveToNextFileEntry(&romfs_ctx)) + { + consolePrint("failed to move to next file entry!\n"); + goto end; + } } consolePrint("romfs dump complete\n"); diff --git a/code_templates/usb_romfs_dumper.c b/code_templates/usb_romfs_dumper.c index 126f360..8f631c6 100644 --- a/code_templates/usb_romfs_dumper.c +++ b/code_templates/usb_romfs_dumper.c @@ -100,12 +100,14 @@ static void read_thread_func(void *arg) goto end; } - u64 file_table_offset = 0; - u64 file_table_size = shared_data->romfs_ctx->file_table_size; RomFileSystemFileEntry *file_entry = NULL; char path[FS_MAX_PATH] = {0}; - while(file_table_offset < file_table_size) + /* Reset current file table offset. */ + romfsResetFileTableOffset(shared_data->romfs_ctx); + + /* Loop through all file entries. */ + while(romfsCanMoveToNextFileEntry(shared_data->romfs_ctx)) { /* Check if the transfer has been cancelled by the user */ if (shared_data->transfer_cancelled) @@ -115,7 +117,7 @@ static void read_thread_func(void *arg) } /* Retrieve RomFS file entry information */ - shared_data->read_error = (!(file_entry = romfsGetFileEntryByOffset(shared_data->romfs_ctx, file_table_offset)) || \ + shared_data->read_error = (!(file_entry = romfsGetCurrentFileEntry(shared_data->romfs_ctx)) || \ !romfsGeneratePathFromFileEntry(shared_data->romfs_ctx, file_entry, path, FS_MAX_PATH, RomFileSystemPathIllegalCharReplaceType_IllegalFsChars)); if (shared_data->read_error) { @@ -178,7 +180,13 @@ static void read_thread_func(void *arg) if (shared_data->read_error || shared_data->write_error || shared_data->transfer_cancelled) break; - file_table_offset += ALIGN_UP(sizeof(RomFileSystemFileEntry) + file_entry->name_length, 4); + /* Move to the next file entry. */ + shared_data->read_error = !romfsMoveToNextFileEntry(shared_data->romfs_ctx); + if (shared_data->read_error) + { + condvarWakeAll(&g_writeCondvar); + break; + } } free(buf); diff --git a/include/core/nxdt_utils.h b/include/core/nxdt_utils.h index c3c6866..de23ba6 100644 --- a/include/core/nxdt_utils.h +++ b/include/core/nxdt_utils.h @@ -109,7 +109,7 @@ void utilsJoinThread(Thread *thread); __attribute__((format(printf, 3, 4))) bool utilsAppendFormattedStringToBuffer(char **dst, size_t *dst_size, const char *fmt, ...); /// Replaces illegal FAT characters in the provided UTF-8 string with underscores. -/// If 'ascii_only' is set to true, all codepoints outside the (0x20,0x7E] range will also be replaced with underscores. +/// If 'ascii_only' is set to true, all codepoints outside the [0x20,0x7F) range will also be replaced with underscores. /// Replacements are performed on a per-codepoint basis, which means the string length can be reduced by this function. void utilsReplaceIllegalCharacters(char *str, bool ascii_only); diff --git a/include/core/romfs.h b/include/core/romfs.h index e9cd7ac..866af9d 100644 --- a/include/core/romfs.h +++ b/include/core/romfs.h @@ -30,10 +30,12 @@ extern "C" { #endif -#define ROMFS_OLD_HEADER_SIZE 0x28 -#define ROMFS_HEADER_SIZE 0x50 +#define ROMFS_OLD_HEADER_SIZE 0x28 +#define ROMFS_HEADER_SIZE 0x50 -#define ROMFS_VOID_ENTRY 0xFFFFFFFF +#define ROMFS_VOID_ENTRY UINT32_MAX + +#define ROMFS_TABLE_ENTRY_ALIGNMENT 0x4 /// Header used by NCA0 RomFS sections. typedef struct { @@ -80,28 +82,28 @@ typedef struct { NXDT_ASSERT(RomFileSystemHeader, ROMFS_HEADER_SIZE); -/// Directory entry. Always aligned to a 4-byte boundary past the directory name. +/// Directory entry. Always aligned to a ROMFS_TABLE_ENTRY_ALIGNMENT boundary past the directory name. typedef struct { u32 parent_offset; ///< Parent directory offset. - u32 next_offset; ///< Next sibling directory offset. - u32 directory_offset; ///< First child directory offset. - u32 file_offset; ///< First child file offset. + u32 next_offset; ///< Next sibling directory offset. May be set to ROMFS_VOID_ENTRY if there are no other directory entries at this level. + u32 directory_offset; ///< First child directory offset. May be set to ROMFS_VOID_ENTRY if there are no child directories entries. + u32 file_offset; ///< First child file offset. May be set to ROMFS_VOID_ENTRY if there are no child file entries. u32 bucket_offset; ///< Directory bucket offset. u32 name_length; ///< Name length. - char name[]; ///< Name (UTF-8). + char name[]; ///< Name (UTF-8, may not be NULL terminated depending on the whole entry alignment). } RomFileSystemDirectoryEntry; NXDT_ASSERT(RomFileSystemDirectoryEntry, 0x18); -/// Directory entry. Always aligned to a 4-byte boundary past the file name. +/// Directory entry. Always aligned to a ROMFS_TABLE_ENTRY_ALIGNMENT boundary past the file name. typedef struct { u32 parent_offset; ///< Parent directory offset. - u32 next_offset; ///< Next sibling file offset. + u32 next_offset; ///< Next sibling file offset. May be set to ROMFS_VOID_ENTRY if there are no other file entries at this level. u64 offset; ///< File data offset. u64 size; ///< File data size. u32 bucket_offset; ///< File bucket offset. u32 name_length; ///< Name length. - char name[]; ///< Name (UTF-8). + char name[]; ///< Name (UTF-8, may not be NULL terminated depending on the whole entry alignment). } RomFileSystemFileEntry; NXDT_ASSERT(RomFileSystemFileEntry, 0x20); @@ -118,7 +120,8 @@ typedef struct { u64 file_table_size; ///< RomFS file entries table size. RomFileSystemFileEntry *file_table; ///< RomFS file entries table. u64 body_offset; ///< RomFS file data body offset (relative to the start of the RomFS). - u32 cur_dir_offset; ///< Current RomFS directory offset (relative to the start of the directory entries table). Used for RomFS browsing. + u64 cur_dir_offset; ///< Current RomFS directory offset (relative to the start of the directory entries table). Used for RomFS browsing. + u64 cur_file_offset; ///< Current RomFS file offset (relative to the start of the file entries table). Used for RomFS browsing. } RomFileSystemContext; typedef struct { @@ -173,8 +176,7 @@ bool romfsGeneratePathFromFileEntry(RomFileSystemContext *ctx, RomFileSystemFile /// Use the romfsWriteFileEntryPatchToMemoryBuffer() wrapper to write patch data generated by this function. bool romfsGenerateFileEntryPatch(RomFileSystemContext *ctx, RomFileSystemFileEntry *file_entry, const void *data, u64 data_size, u64 data_offset, RomFileSystemFileEntryPatch *out); -/// Miscellaneous functions. - +/// Resets a previously initialized RomFileSystemContext. NX_INLINE void romfsFreeContext(RomFileSystemContext *ctx) { if (!ctx) return; @@ -185,22 +187,93 @@ NX_INLINE void romfsFreeContext(RomFileSystemContext *ctx) memset(ctx, 0, sizeof(RomFileSystemContext)); } -NX_INLINE RomFileSystemDirectoryEntry *romfsGetDirectoryEntryByOffset(RomFileSystemContext *ctx, u32 dir_entry_offset) +/// Functions to reset the current directory/file entry offset. +NX_INLINE void romfsResetDirectoryTableOffset(RomFileSystemContext *ctx) { - if (!ctx || !ctx->dir_table || (dir_entry_offset + sizeof(RomFileSystemDirectoryEntry)) > ctx->dir_table_size) return NULL; - return (RomFileSystemDirectoryEntry*)((u8*)ctx->dir_table + dir_entry_offset); + if (ctx) ctx->cur_dir_offset = 0; } -NX_INLINE RomFileSystemFileEntry *romfsGetFileEntryByOffset(RomFileSystemContext *ctx, u32 file_entry_offset) +NX_INLINE void romfsResetFileTableOffset(RomFileSystemContext *ctx) { - if (!ctx || !ctx->file_table || (file_entry_offset + sizeof(RomFileSystemFileEntry)) > ctx->file_table_size) return NULL; - return (RomFileSystemFileEntry*)((u8*)ctx->file_table + file_entry_offset); + if (ctx) ctx->cur_file_offset = 0; } +/// Checks if the provided RomFileSystemContext is valid. +NX_INLINE bool romfsIsValidContext(RomFileSystemContext *ctx) +{ + return (ctx && ncaStorageIsValidContext(ctx->default_storage_ctx) && ctx->size && ctx->dir_table_size && ctx->dir_table && ctx->file_table_size && ctx->file_table && \ + ctx->body_offset >= ctx->header.old_format.header_size && ctx->body_offset < ctx->size); +} + +/// Functions to retrieve a directory/file entry. +NX_INLINE void *romfsGetEntryByOffset(RomFileSystemContext *ctx, void *entry_table, u64 entry_table_size, u64 entry_size, u64 entry_offset) +{ + if (!romfsIsValidContext(ctx) || !entry_table || !entry_table_size || !entry_size || (entry_offset + entry_size) > entry_table_size) return NULL; + return ((u8*)entry_table + entry_offset); +} + +NX_INLINE RomFileSystemDirectoryEntry *romfsGetDirectoryEntryByOffset(RomFileSystemContext *ctx, u64 dir_entry_offset) +{ + return (ctx ? (RomFileSystemDirectoryEntry*)romfsGetEntryByOffset(ctx, ctx->dir_table, ctx->dir_table_size, sizeof(RomFileSystemDirectoryEntry), dir_entry_offset) : NULL); +} + +NX_INLINE RomFileSystemDirectoryEntry *romfsGetCurrentDirectoryEntry(RomFileSystemContext *ctx) +{ + return (ctx ? romfsGetDirectoryEntryByOffset(ctx, ctx->cur_dir_offset) : NULL); +} + +NX_INLINE RomFileSystemFileEntry *romfsGetFileEntryByOffset(RomFileSystemContext *ctx, u64 file_entry_offset) +{ + return (ctx ? (RomFileSystemFileEntry*)romfsGetEntryByOffset(ctx, ctx->file_table, ctx->file_table_size, sizeof(RomFileSystemFileEntry), file_entry_offset) : NULL); +} + +NX_INLINE RomFileSystemFileEntry *romfsGetCurrentFileEntry(RomFileSystemContext *ctx) +{ + return (ctx ? romfsGetFileEntryByOffset(ctx, ctx->cur_file_offset) : NULL); +} + +/// Functions to check if it's possible to move to the next directory/file entry based on the current directory/file entry offset. +NX_INLINE bool romfsCanMoveToNextEntry(RomFileSystemContext *ctx, void *entry_table, u64 entry_table_size, u64 entry_size, u64 entry_offset) +{ + if (!romfsIsValidContext(ctx) || !entry_table || !entry_table_size || entry_size < 4 || (entry_offset + entry_size) > entry_table_size) return false; + u32 name_length = *((u32*)((u8*)entry_table + entry_offset + entry_size - 4)); + return ((entry_offset + ALIGN_UP(entry_size + name_length, ROMFS_TABLE_ENTRY_ALIGNMENT)) <= entry_table_size); +} + +NX_INLINE bool romfsCanMoveToNextDirectoryEntry(RomFileSystemContext *ctx) +{ + return (ctx ? romfsCanMoveToNextEntry(ctx, ctx->dir_table, ctx->dir_table_size, sizeof(RomFileSystemDirectoryEntry), ctx->cur_dir_offset) : false); +} + +NX_INLINE bool romfsCanMoveToNextFileEntry(RomFileSystemContext *ctx) +{ + return (ctx ? romfsCanMoveToNextEntry(ctx, ctx->file_table, ctx->file_table_size, sizeof(RomFileSystemFileEntry), ctx->cur_file_offset) : false); +} + +/// Functions to update the current directory/file entry offset to make it point to the next directory/file entry. +NX_INLINE bool romfsMoveToNextEntry(RomFileSystemContext *ctx, void *entry_table, u64 entry_table_size, u64 entry_size, u64 *entry_offset) +{ + if (!romfsIsValidContext(ctx) || !entry_table || !entry_table_size || entry_size < 4 || !entry_offset || (*entry_offset + entry_size) > entry_table_size) return false; + u32 name_length = *((u32*)((u8*)entry_table + *entry_offset + entry_size - 4)); + *entry_offset += ALIGN_UP(entry_size + name_length, ROMFS_TABLE_ENTRY_ALIGNMENT); + return true; +} + +NX_INLINE bool romfsMoveToNextDirectoryEntry(RomFileSystemContext *ctx) +{ + return (ctx ? romfsMoveToNextEntry(ctx, ctx->dir_table, ctx->dir_table_size, sizeof(RomFileSystemDirectoryEntry), &(ctx->cur_dir_offset)) : false); +} + +NX_INLINE bool romfsMoveToNextFileEntry(RomFileSystemContext *ctx) +{ + return (ctx ? romfsMoveToNextEntry(ctx, ctx->file_table, ctx->file_table_size, sizeof(RomFileSystemFileEntry), &(ctx->cur_file_offset)) : false); +} + +/// NCA patch management functions. NX_INLINE void romfsWriteFileEntryPatchToMemoryBuffer(RomFileSystemContext *ctx, RomFileSystemFileEntryPatch *patch, void *buf, u64 buf_size, u64 buf_offset) { - if (!ctx || ctx->is_patch || !ncaStorageIsValidContext(ctx->default_storage_ctx) || ctx->default_storage_ctx->base_storage_type != NcaStorageBaseStorageType_Regular || !patch || \ - (!patch->use_old_format_patch && ctx->default_storage_ctx->nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs) || \ + if (!romfsIsValidContext(ctx) || ctx->is_patch || ctx->default_storage_ctx->base_storage_type != NcaStorageBaseStorageType_Regular || !patch || \ + (!patch->use_old_format_patch && ctx->default_storage_ctx->nca_fs_ctx->section_type != NcaFsSectionType_RomFs) || \ (patch->use_old_format_patch && ctx->default_storage_ctx->nca_fs_ctx->section_type != NcaFsSectionType_Nca0RomFs)) return; NcaContext *nca_ctx = (NcaContext*)ctx->default_storage_ctx->nca_fs_ctx->nca_ctx; diff --git a/source/core/romfs.c b/source/core/romfs.c index fadc3fa..32eab01 100644 --- a/source/core/romfs.c +++ b/source/core/romfs.c @@ -182,7 +182,7 @@ end: bool romfsReadFileSystemData(RomFileSystemContext *ctx, void *out, u64 read_size, u64 offset) { - if (!ctx || !ncaStorageIsValidContext(ctx->default_storage_ctx) || !ctx->size || !out || !read_size || (offset + read_size) > ctx->size) + if (!romfsIsValidContext(ctx) || !out || !read_size || (offset + read_size) > ctx->size) { LOG_MSG("Invalid parameters!"); return false; @@ -200,7 +200,8 @@ bool romfsReadFileSystemData(RomFileSystemContext *ctx, void *out, u64 read_size bool romfsReadFileEntryData(RomFileSystemContext *ctx, RomFileSystemFileEntry *file_entry, void *out, u64 read_size, u64 offset) { - if (!ctx || !ctx->body_offset || !file_entry || !file_entry->size || (file_entry->offset + file_entry->size) > ctx->size || !out || !read_size || (offset + read_size) > file_entry->size) + if (!romfsIsValidContext(ctx) || !file_entry || !file_entry->size || (file_entry->offset + file_entry->size) > ctx->size || !out || !read_size || \ + (offset + read_size) > file_entry->size) { LOG_MSG("Invalid parameters!"); return false; @@ -218,75 +219,120 @@ bool romfsReadFileEntryData(RomFileSystemContext *ctx, RomFileSystemFileEntry *f bool romfsGetTotalDataSize(RomFileSystemContext *ctx, u64 *out_size) { - if (!ctx || !ctx->file_table_size || !ctx->file_table || !out_size) + if (!romfsIsValidContext(ctx) || !out_size) { LOG_MSG("Invalid parameters!"); return false; } - u64 offset = 0, total_size = 0; RomFileSystemFileEntry *file_entry = NULL; + u64 total_size = 0; + bool success = false; - while(offset < ctx->file_table_size) + /* Reset current file table offset. */ + romfsResetFileTableOffset(ctx); + + /* Loop through all file entries. */ + while(romfsCanMoveToNextFileEntry(ctx)) { - if (!(file_entry = romfsGetFileEntryByOffset(ctx, offset))) + /* Get current file entry. */ + if (!(file_entry = romfsGetCurrentFileEntry(ctx))) { - LOG_MSG("Failed to retrieve file entry!"); - return false; + LOG_MSG("Failed to retrieve current file entry! (0x%lX, 0x%lX).", ctx->cur_file_offset, ctx->file_table_size); + goto end; } + /* Update total data size. */ total_size += file_entry->size; - offset += ALIGN_UP(sizeof(RomFileSystemFileEntry) + file_entry->name_length, 4); + + /* Move to the next file entry. */ + if (!romfsMoveToNextFileEntry(ctx)) + { + LOG_MSG("Failed to move to the next file entry! (0x%lX, 0x%lX).", ctx->cur_file_offset, ctx->file_table_size); + goto end; + } } + /* Update output values. */ *out_size = total_size; + success = true; - return true; +end: + /* Reset current file table offset. */ + romfsResetFileTableOffset(ctx); + + return success; } bool romfsGetDirectoryDataSize(RomFileSystemContext *ctx, RomFileSystemDirectoryEntry *dir_entry, u64 *out_size) { - if (!ctx || !ctx->dir_table_size || !ctx->dir_table || !ctx->file_table_size || !ctx->file_table || !dir_entry || (dir_entry->file_offset == ROMFS_VOID_ENTRY && \ - dir_entry->directory_offset == ROMFS_VOID_ENTRY) || !out_size) + if (!romfsIsValidContext(ctx) || !dir_entry || !out_size) { LOG_MSG("Invalid parameters!"); return false; } - u64 total_size = 0, child_dir_size = 0; - u32 cur_file_offset = 0, cur_dir_offset = 0; + /* Short-circuit: check if we're dealing with an empty directory. */ + if (dir_entry->file_offset == ROMFS_VOID_ENTRY && dir_entry->directory_offset == ROMFS_VOID_ENTRY) + { + *out_size = 0; + return true; + } + RomFileSystemFileEntry *cur_file_entry = NULL; RomFileSystemDirectoryEntry *cur_dir_entry = NULL; + u64 total_size = 0, cur_entry_offset = 0, child_dir_size = 0; + bool success = false; - cur_file_offset = dir_entry->file_offset; - while(cur_file_offset != ROMFS_VOID_ENTRY) + /* Loop through the child file entries' linked list. */ + cur_entry_offset = dir_entry->file_offset; + while(cur_entry_offset != ROMFS_VOID_ENTRY) { - if (!(cur_file_entry = romfsGetFileEntryByOffset(ctx, cur_file_offset))) + /* Get current file entry. */ + if (!(cur_file_entry = romfsGetFileEntryByOffset(ctx, cur_entry_offset))) { - LOG_MSG("Failed to retrieve file entry!"); - return false; + LOG_MSG("Failed to retrieve file entry! (0x%lX, 0x%lX).", cur_entry_offset, ctx->file_table_size); + goto end; } + /* Update total data size. */ total_size += cur_file_entry->size; - cur_file_offset = cur_file_entry->next_offset; + + /* Update current file entry offset. */ + cur_entry_offset = cur_file_entry->next_offset; } - cur_dir_offset = dir_entry->directory_offset; - while(cur_dir_offset != ROMFS_VOID_ENTRY) + /* Loop through the child directory entries' linked list. */ + cur_entry_offset = dir_entry->directory_offset; + while(cur_entry_offset != ROMFS_VOID_ENTRY) { - if (!(cur_dir_entry = romfsGetDirectoryEntryByOffset(ctx, cur_dir_offset)) || !romfsGetDirectoryDataSize(ctx, cur_dir_entry, &child_dir_size)) + /* Get current directory entry. */ + if (!(cur_dir_entry = romfsGetDirectoryEntryByOffset(ctx, cur_entry_offset))) { - LOG_MSG("Failed to retrieve directory entry/size!"); - return false; + LOG_MSG("Failed to retrieve directory entry! (0x%lX, 0x%lX).", cur_entry_offset, ctx->dir_table_size); + goto end; } + /* Calculate directory size. */ + if (!romfsGetDirectoryDataSize(ctx, cur_dir_entry, &child_dir_size)) + { + LOG_MSG("Failed to get size for directory entry! (0x%lX, 0x%lX).", cur_entry_offset, ctx->dir_table_size); + goto end; + } + + /* Update total data size. */ total_size += child_dir_size; - cur_dir_offset = cur_dir_entry->next_offset; + + /* Update current directory entry offset. */ + cur_entry_offset = cur_dir_entry->next_offset; } + /* Update output values. */ *out_size = total_size; + success = true; - return true; +end: + return success; } RomFileSystemDirectoryEntry *romfsGetDirectoryEntryByPath(RomFileSystemContext *ctx, const char *path) @@ -295,22 +341,27 @@ RomFileSystemDirectoryEntry *romfsGetDirectoryEntryByPath(RomFileSystemContext * char *path_dup = NULL, *pch = NULL, *state = NULL; RomFileSystemDirectoryEntry *dir_entry = NULL; - if (!ctx || !ctx->dir_table || !ctx->dir_table_size || !path || *path != '/' || !(path_len = strlen(path)) || !(dir_entry = romfsGetDirectoryEntryByOffset(ctx, 0))) + if (!romfsIsValidContext(ctx) || !path || *path != '/' || !(dir_entry = romfsGetDirectoryEntryByOffset(ctx, 0))) { LOG_MSG("Invalid parameters!"); return NULL; } - /* Check if the root directory was requested. */ + /* Retrieve path length. */ + path_len = strlen(path); + + /* Short-circuit: check if the root directory was requested. */ if (path_len == 1) return dir_entry; /* Duplicate path to avoid problems with strtok_r(). */ if (!(path_dup = strdup(path))) { LOG_MSG("Unable to duplicate input path! (\"%s\").", path); - return NULL; + dir_entry = NULL; + goto end; } + /* Tokenize duplicated path using path separators. */ pch = strtok_r(path_dup, "/", &state); if (!pch) { @@ -319,14 +370,17 @@ RomFileSystemDirectoryEntry *romfsGetDirectoryEntryByPath(RomFileSystemContext * goto end; } + /* Loop through all path elements. */ while(pch) { + /* Get child directory entry using the current token. */ if (!(dir_entry = romfsGetChildDirectoryEntryByName(ctx, dir_entry, pch))) { LOG_MSG("Failed to retrieve directory entry by name for \"%s\"! (\"%s\").", pch, path); break; } + /* Move onto the next token. */ pch = strtok_r(NULL, "/", &state); } @@ -345,20 +399,23 @@ RomFileSystemFileEntry *romfsGetFileEntryByPath(RomFileSystemContext *ctx, const RomFileSystemDirectoryEntry *dir_entry = NULL; NcaContext *nca_ctx = NULL; - if (!ctx || !ctx->file_table || !ctx->file_table_size || !ncaStorageIsValidContext(ctx->default_storage_ctx) || \ - !(nca_ctx = (NcaContext*)ctx->default_storage_ctx->nca_fs_ctx->nca_ctx) || !path || *path != '/' || (path_len = strlen(path)) <= 1) + if (!romfsIsValidContext(ctx) || !(nca_ctx = (NcaContext*)ctx->default_storage_ctx->nca_fs_ctx->nca_ctx) || !path || *path != '/') { LOG_MSG("Invalid parameters!"); return NULL; } + /* Retrieve path length. */ + path_len = strlen(path); + + /* Retrieve NCA content type. */ content_type = nca_ctx->content_type; /* Duplicate path. */ if (!(path_dup = strdup(path))) { LOG_MSG("Unable to duplicate input path! (\"%s\").", path); - return NULL; + goto end; } /* Remove any trailing slashes. */ @@ -403,42 +460,45 @@ end: bool romfsGeneratePathFromDirectoryEntry(RomFileSystemContext *ctx, RomFileSystemDirectoryEntry *dir_entry, char *out_path, size_t out_path_size, u8 illegal_char_replace_type) { size_t path_len = 0; - u32 dir_offset = ROMFS_VOID_ENTRY, dir_entries_count = 0; + u64 dir_offset = ROMFS_VOID_ENTRY; + u32 dir_entries_count = 0; RomFileSystemDirectoryEntry **dir_entries = NULL, **tmp_dir_entries = NULL; bool success = false; - if (!ctx || !ctx->dir_table || !ctx->dir_table_size || !dir_entry || (!dir_entry->name_length && dir_entry->parent_offset) || !out_path || out_path_size < 2 || \ + if (!romfsIsValidContext(ctx) || !dir_entry || (!dir_entry->name_length && dir_entry->parent_offset) || !out_path || out_path_size < 2 || \ illegal_char_replace_type > RomFileSystemPathIllegalCharReplaceType_KeepAsciiCharsOnly) { LOG_MSG("Invalid parameters!"); return false; } - /* Check if we're dealing with the root directory entry. */ + /* Short-circuit: check if we're dealing with the root directory entry. */ if (!dir_entry->name_length) { sprintf(out_path, "/"); return true; } - /* Allocate memory for our directory entries. */ + /* Allocate memory for our directory entries pointer array. */ dir_entries = calloc(1, sizeof(RomFileSystemDirectoryEntry*)); if (!dir_entries) { LOG_MSG("Unable to allocate memory for directory entries!"); - return false; + goto end; } + /* Update stats. */ path_len = (1 + dir_entry->name_length); *dir_entries = dir_entry; dir_entries_count++; while(true) { + /* Get parent directory offset. Break out of the loop if we reached the root directory. */ dir_offset = dir_entries[dir_entries_count - 1]->parent_offset; if (!dir_offset) break; - /* Reallocate directory entries. */ + /* Reallocate directory entries pointer array. */ if (!(tmp_dir_entries = realloc(dir_entries, (dir_entries_count + 1) * sizeof(RomFileSystemDirectoryEntry*)))) { LOG_MSG("Unable to reallocate directory entries buffer!"); @@ -448,6 +508,7 @@ bool romfsGeneratePathFromDirectoryEntry(RomFileSystemContext *ctx, RomFileSyste dir_entries = tmp_dir_entries; tmp_dir_entries = NULL; + /* Retrieve parent directory entry using the offset we got earlier. */ RomFileSystemDirectoryEntry **cur_dir_entry = &(dir_entries[dir_entries_count]); if (!(*cur_dir_entry = romfsGetDirectoryEntryByOffset(ctx, dir_offset)) || !(*cur_dir_entry)->name_length) { @@ -455,37 +516,44 @@ bool romfsGeneratePathFromDirectoryEntry(RomFileSystemContext *ctx, RomFileSyste goto end; } + /* Update stats. */ path_len += (1 + (*cur_dir_entry)->name_length); dir_entries_count++; } + /* Make sure the output buffer is big enough to hold the full path + NULL terminator. */ if (path_len >= out_path_size) { - LOG_MSG("Output path length exceeds output buffer size!"); + LOG_MSG("Output path length exceeds output buffer size! (%lu >= %lu).", path_len, out_path_size); goto end; } - /* Generate output path. */ + /* Generate output path, looping through our directory entries pointer array in reverse order. */ *out_path = '\0'; path_len = 0; for(u32 i = dir_entries_count; i > 0; i--) { - RomFileSystemDirectoryEntry **cur_dir_entry = &(dir_entries[i - 1]); + /* Get current directory entry. */ + RomFileSystemDirectoryEntry *cur_dir_entry = dir_entries[i - 1]; + /* Concatenate path separator and current directory name to the output buffer. */ strcat(out_path, "/"); - strncat(out_path, (*cur_dir_entry)->name, (*cur_dir_entry)->name_length); + strncat(out_path, cur_dir_entry->name, cur_dir_entry->name_length); path_len++; if (illegal_char_replace_type) { + /* Replace illegal characters within this directory name, then update the full path length. */ utilsReplaceIllegalCharacters(out_path + path_len, illegal_char_replace_type == RomFileSystemPathIllegalCharReplaceType_KeepAsciiCharsOnly); path_len += strlen(out_path + path_len); } else { - path_len += (*cur_dir_entry)->name_length; + /* Update full path length. */ + path_len += cur_dir_entry->name_length; } } + /* Update return value. */ success = true; end: @@ -498,8 +566,9 @@ bool romfsGeneratePathFromFileEntry(RomFileSystemContext *ctx, RomFileSystemFile { size_t path_len = 0; RomFileSystemDirectoryEntry *dir_entry = NULL; + bool success = false; - if (!ctx || !ctx->file_table || !ctx->file_table_size || !file_entry || !file_entry->name_length || !out_path || out_path_size < 2 || \ + if (!romfsIsValidContext(ctx) || !file_entry || !file_entry->name_length || !out_path || out_path_size < 2 || \ !(dir_entry = romfsGetDirectoryEntryByOffset(ctx, file_entry->parent_offset)) || illegal_char_replace_type > RomFileSystemPathIllegalCharReplaceType_KeepAsciiCharsOnly) { LOG_MSG("Invalid parameters!"); @@ -510,35 +579,41 @@ bool romfsGeneratePathFromFileEntry(RomFileSystemContext *ctx, RomFileSystemFile if (!romfsGeneratePathFromDirectoryEntry(ctx, dir_entry, out_path, out_path_size, illegal_char_replace_type)) { LOG_MSG("Failed to retrieve RomFS directory path!"); - return false; + goto end; } - /* Check path length. */ + /* Make sure the output buffer is big enough to hold the full path + NULL terminator. */ path_len = strlen(out_path); - if ((1 + file_entry->name_length) >= (out_path_size - path_len)) + if ((path_len + 1 + file_entry->name_length) >= out_path_size) { - LOG_MSG("Output path length exceeds output buffer size!"); - return false; + LOG_MSG("Output path length exceeds output buffer size! (%lu >= %lu).", path_len + 1 + file_entry->name_length, out_path_size); + goto end; } - /* Concatenate file entry name. */ + /* Concatenate path separator if our parent directory isn't the root directory. */ if (file_entry->parent_offset) { strcat(out_path, "/"); path_len++; } + /* Concatenate file entry name. */ strncat(out_path, file_entry->name, file_entry->name_length); + /* Replace illegal characters within the file name, if needed. */ if (illegal_char_replace_type) utilsReplaceIllegalCharacters(out_path + path_len, illegal_char_replace_type == RomFileSystemPathIllegalCharReplaceType_KeepAsciiCharsOnly); - return true; + /* Update return value. */ + success = true; + +end: + return success; } bool romfsGenerateFileEntryPatch(RomFileSystemContext *ctx, RomFileSystemFileEntry *file_entry, const void *data, u64 data_size, u64 data_offset, RomFileSystemFileEntryPatch *out) { - if (!ctx || ctx->is_patch || !ncaStorageIsValidContext(ctx->default_storage_ctx) || ctx->default_storage_ctx->base_storage_type != NcaStorageBaseStorageType_Regular || \ - !ctx->body_offset || (ctx->default_storage_ctx->nca_fs_ctx->section_type != NcaFsSectionType_Nca0RomFs && ctx->default_storage_ctx->nca_fs_ctx->section_type != NcaFsSectionType_RomFs) || \ + if (!romfsIsValidContext(ctx) || ctx->is_patch || ctx->default_storage_ctx->base_storage_type != NcaStorageBaseStorageType_Regular || \ + (ctx->default_storage_ctx->nca_fs_ctx->section_type != NcaFsSectionType_Nca0RomFs && ctx->default_storage_ctx->nca_fs_ctx->section_type != NcaFsSectionType_RomFs) || \ !file_entry || !file_entry->size || (file_entry->offset + file_entry->size) > ctx->size || !data || !data_size || (data_offset + data_size) > file_entry->size || !out) { LOG_MSG("Invalid parameters!"); @@ -572,24 +647,28 @@ static RomFileSystemDirectoryEntry *romfsGetChildDirectoryEntryByName(RomFileSys size_t name_len = 0; RomFileSystemDirectoryEntry *child_dir_entry = NULL; - if (!ctx || !ctx->dir_table || !ctx->dir_table_size || !dir_entry || (dir_offset = dir_entry->directory_offset) == ROMFS_VOID_ENTRY || !name || !(name_len = strlen(name))) + if (!dir_entry || (dir_offset = dir_entry->directory_offset) == ROMFS_VOID_ENTRY || !name || !(name_len = strlen(name))) { LOG_MSG("Invalid parameters!"); return NULL; } + /* Loop through the child directory entries' linked list. */ while(dir_offset != ROMFS_VOID_ENTRY) { + /* Get current directory entry. */ if (!(child_dir_entry = romfsGetDirectoryEntryByOffset(ctx, dir_offset))) { - LOG_MSG("Failed to retrieve directory entry at offset 0x%lX!", dir_offset); + LOG_MSG("Failed to retrieve directory entry! (0x%lX, 0x%lX).", dir_offset, ctx->dir_table_size); break; } + /* Check if we found the right child directory entry. */ /* strncmp() is used here instead of strcmp() because names stored in RomFS sections are not always NULL terminated. */ /* If the name ends at a 4-byte boundary, the next entry starts immediately. */ if (child_dir_entry->name_length == name_len && !strncmp(child_dir_entry->name, name, name_len)) return child_dir_entry; + /* Update current directory entry offset. */ dir_offset = child_dir_entry->next_offset; } @@ -602,21 +681,23 @@ static RomFileSystemFileEntry *romfsGetChildFileEntryByName(RomFileSystemContext size_t name_len = 0; RomFileSystemFileEntry *child_file_entry = NULL; - if (!ctx || !ctx->dir_table || !ctx->dir_table_size || !ctx->file_table || !ctx->file_table_size || !dir_entry || (file_offset = dir_entry->file_offset) == ROMFS_VOID_ENTRY || \ - !name || !(name_len = strlen(name))) + if (!dir_entry || (file_offset = dir_entry->file_offset) == ROMFS_VOID_ENTRY || !name || !(name_len = strlen(name))) { LOG_MSG("Invalid parameters!"); return NULL; } + /* Loop through the child file entries' linked list. */ while(file_offset != ROMFS_VOID_ENTRY) { + /* Get current file entry. */ if (!(child_file_entry = romfsGetFileEntryByOffset(ctx, file_offset))) { - LOG_MSG("Failed to retrieve file entry at offset 0x%lX!", file_offset); + LOG_MSG("Failed to retrieve file entry! (0x%lX, 0x%lX).", file_offset, ctx->file_table_size); break; } + /* Check if we found the right child file entry. */ /* strncmp() is used here instead of strcmp() because names stored in RomFS sections are not always NULL terminated. */ /* If the name ends at a 4-byte boundary, the next entry starts immediately. */ if (child_file_entry->name_length == name_len && !strncmp(child_file_entry->name, name, name_len)) return child_file_entry;