#include "lib.h" #include "dir.h" #include "ncch_build.h" #include "romfs.h" const int ROMFS_BLOCK_SIZE = 0x1000; const unsigned int ROMFS_UNUSED_ENTRY = 0xffffffff; const fs_romfs_char ROMFS_EMPTY_PATH[2] = {0x0000, 0x0000}; // Build bool IsFileWanted(fs_file *file, void *filter_criteria); bool IsDirWanted(fs_dir *dir, void *filter_criteria); void CalcDirSize(romfs_buildctx *ctx, fs_dir *fs); void CalcRomfsSize(romfs_buildctx *ctx); int FilterRomFS(fs_dir *fs_raw, fs_dir *fs_filtered, void *filter_criteria); void AddFileToRomfs(romfs_buildctx *ctx, fs_file *file, u32 parent, u32 sibling); void AddDirToRomfs(romfs_buildctx *ctx, fs_dir *fs, u32 parent, u32 sibling); void AddDirChildrenToRomfs(romfs_buildctx *ctx, fs_dir *fs, u32 parent, u32 dir); void PopulateHashTable(romfs_buildctx *ctx); void PopulateRomfs(romfs_buildctx *ctx); void BuildRomfsHeader(romfs_buildctx *ctx); void BuildIvfcHeader(romfs_buildctx *ctx); void GenIvfcHashTree(romfs_buildctx *ctx); u32 CalcPathHash(u32 parent, fs_romfs_char* path, u32 start, u32 length); int PrepareBuildRomFsBinary(ncch_settings *ncchset, romfs_buildctx *ctx) { /* Input Path */ const int CWD_MAX_LEN = 1024; char *cwd = calloc(CWD_MAX_LEN,sizeof(char)); getcwd(cwd,CWD_MAX_LEN); char *dir = ncchset->rsfSet->RomFs.RootPath; fs_char *fs_path; fs_romfs_char *path; u32 path_len; #ifdef _WIN32 str_u8_to_u16(&path,&path_len,(u8*)dir,strlen(dir)); fs_path = path; #else str_utf8_to_u16(&path,&path_len,(u8*)dir,strlen(dir)); fs_path = dir; #endif /* FS Structures */ void *filter_criteria = NULL; fs_dir *fs_raw = calloc(1,sizeof(fs_dir)); ctx->fs = calloc(1,sizeof(fs_dir)); /* Import FS and process */ fs_OpenDir(fs_path,path,path_len,fs_raw); FilterRomFS(fs_raw,ctx->fs,filter_criteria); /* free unfiltered FS */ fs_FreeFiles(fs_raw); // All important FPs have been moved with FilterRomFS, so only un-wanted FPs are closed here fs_FreeDir(fs_raw); free(fs_raw); /* Abort romfs making, if no wanted files/directories were found */ if(ctx->fs->u_file == 0 && ctx->fs->u_dir == 0){ ctx->romfsSize = 0; goto finish; } /* Print Filtered FS */ if(ncchset->options.verbose){ printf("[ROMFS] File System:\n"); fs_PrintDir(ctx->fs,0); } CalcRomfsSize(ctx); finish: chdir(cwd); return 0; } int BuildRomFsBinary(romfs_buildctx *ctx) { /* Decide IVFC Level Actual Offsets */ ctx->level[0].offset = 0; ctx->level[3].offset = ctx->level[0].offset + align(ctx->level[0].size, ROMFS_BLOCK_SIZE); ctx->level[1].offset = ctx->level[3].offset + align(ctx->level[3].size, ROMFS_BLOCK_SIZE); ctx->level[2].offset = ctx->level[1].offset + align(ctx->level[1].size, ROMFS_BLOCK_SIZE); /* Decide IVFC Level Logical Offsets */ for(int i = 1; i < 4; i++){ if(i == 1) ctx->level[i].logicalOffset = 0; else ctx->level[i].logicalOffset = align(ctx->level[i-1].logicalOffset + ctx->level[i-1].size,ROMFS_BLOCK_SIZE); } /* Setup IVFC Level Ptrs */ for(int i = 0; i < 4; i++){ ctx->level[i].pos = (ctx->output + ctx->level[i].offset); if(i == 0) ctx->level[i].pos += align(sizeof(ivfc_hdr),0x10); } /* Build Romfs */ ctx->romfsHdr = (romfs_infoheader*)(ctx->level[3].pos); BuildRomfsHeader(ctx); PopulateRomfs(ctx); /* Finalise by building IVFC hash tree */ ctx->ivfcHdr = (ivfc_hdr*)(ctx->output + ctx->level[0].offset); BuildIvfcHeader(ctx); GenIvfcHashTree(ctx); return 0; } bool IsFileWanted(fs_file *file, void *filter_criteria) { return true; } bool IsDirWanted(fs_dir *dir, void *filter_criteria) { bool ret = false; for(u32 i = 0; i < dir->u_file; i++) { if(IsFileWanted(&dir->file[i],filter_criteria)) { ret = true; break; } } fs_dir *tmp = (fs_dir*)dir->dir; for(u32 i = 0; i < dir->u_dir; i++) { if(IsDirWanted(&tmp[i],filter_criteria)) { ret = true; break; } } return ret; } void CalcDirSize(romfs_buildctx *ctx, fs_dir *fs) { if(ctx->m_dirTableLen == 0) ctx->m_dirTableLen = sizeof(romfs_direntry); else ctx->m_dirTableLen += sizeof(romfs_direntry) + align(fs->name_len,4); for(u32 i = 0; i < fs->u_file; i++) { ctx->m_fileTableLen += sizeof(romfs_fileentry) + align(fs->file[i].name_len,4); if(fs->file[i].size) ctx->m_dataLen = align(ctx->m_dataLen,0x10) + fs->file[i].size; } fs_dir *dir = (fs_dir*)fs->dir; for(u32 i = 0; i < fs->u_dir; i++) { CalcDirSize(ctx,&dir[i]); } ctx->fileNum += fs->u_file; ctx->dirNum += fs->u_dir; } u32 GetHashTableCount(u32 num) { u32 count = num; if (num < 3) count = 3; else if (count < 19) count |= 1; else { while (count % 2 == 0 || count % 3 == 0 || count % 5 == 0 || count % 7 == 0 || count % 11 == 0 || count % 13 == 0 || count % 17 == 0) count++; } return count; } void CalcRomfsSize(romfs_buildctx *ctx) { ctx->dirNum = 1; // root dir CalcDirSize(ctx,ctx->fs); ctx->m_dirHashTable = GetHashTableCount(ctx->dirNum); ctx->m_fileHashTable = GetHashTableCount(ctx->fileNum); u32 romfsHdrSize = align(sizeof(romfs_infoheader) + ctx->m_dirHashTable*sizeof(u32) + ctx->m_dirTableLen + ctx->m_fileHashTable*sizeof(u32) + ctx->m_fileTableLen,0x10); ctx->level[3].size = romfsHdrSize + ctx->m_dataLen; // data ctx->level[2].size = align(ctx->level[3].size,ROMFS_BLOCK_SIZE) / ROMFS_BLOCK_SIZE * SHA_256_LEN ; ctx->level[1].size = align(ctx->level[2].size,ROMFS_BLOCK_SIZE) / ROMFS_BLOCK_SIZE * SHA_256_LEN ; ctx->level[0].size = align(ctx->level[1].size,ROMFS_BLOCK_SIZE) / ROMFS_BLOCK_SIZE * SHA_256_LEN + align(sizeof(ivfc_hdr),0x10); // hdr ctx->romfsHeaderSize = ctx->level[0].size; ctx->romfsSize = 0; for(int i = 0; i < 4; i++) ctx->romfsSize += align(ctx->level[i].size,ROMFS_BLOCK_SIZE); } int FilterRomFS(fs_dir *fs_raw, fs_dir *fs_filtered, void *filter_criteria) { memset(fs_filtered,0,sizeof(fs_dir)); if(!IsDirWanted(fs_raw,filter_criteria)) return 0; fs_filtered->name_len = fs_raw->name_len; fs_filtered->name = calloc(fs_filtered->name_len+2,1); memcpy(fs_filtered->name,fs_raw->name,fs_filtered->name_len); fs_filtered->u_dir = 0; fs_filtered->m_dir = fs_raw->u_dir; fs_filtered->dir = calloc(fs_filtered->m_dir,sizeof(fs_dir)); fs_filtered->u_file = 0; fs_filtered->m_file = fs_raw->u_file; fs_filtered->file = calloc(fs_filtered->m_file,sizeof(fs_file)); fs_dir *dir_raw = (fs_dir*)fs_raw->dir; fs_dir *dir_filtered = (fs_dir*)fs_filtered->dir; for(u32 i = 0; i < fs_raw->u_dir; i++) { if(IsDirWanted(&dir_raw[i],filter_criteria)) { FilterRomFS(&dir_raw[i],&dir_filtered[fs_filtered->u_dir],filter_criteria); fs_filtered->u_dir++; } } for(u32 i = 0; i < fs_raw->u_file; i++) { if(IsFileWanted(&fs_raw->file[i],filter_criteria)) { fs_filtered->file[fs_filtered->u_file].name_len = fs_raw->file[i].name_len; fs_filtered->file[fs_filtered->u_file].name = malloc(fs_filtered->file[fs_filtered->u_file].name_len+2); memset(fs_filtered->file[fs_filtered->u_file].name,0,fs_filtered->file[fs_filtered->u_file].name_len+2); memcpy(fs_filtered->file[fs_filtered->u_file].name,fs_raw->file[i].name,fs_filtered->file[fs_filtered->u_file].name_len); fs_filtered->file[fs_filtered->u_file].size = fs_raw->file[i].size; fs_filtered->file[fs_filtered->u_file].fp = fs_raw->file[i].fp; fs_raw->file[i].fp = NULL; fs_filtered->u_file++; } } return 0; } void BuildRomfsHeader(romfs_buildctx *ctx) { u32 level3_pos = 0; u32_to_u8(ctx->romfsHdr->headersize,sizeof(romfs_infoheader),LE); level3_pos += sizeof(romfs_infoheader); for(int i = 0; i < 4; i++){ if(i == 0){ ctx->dirHashTable = ctx->level[3].pos + level3_pos; u32_to_u8(ctx->romfsHdr->section[i].offset,level3_pos,LE); u32_to_u8(ctx->romfsHdr->section[i].size,ctx->m_dirHashTable*sizeof(u32),LE); level3_pos += ctx->m_dirHashTable*sizeof(u32); } else if(i == 1 && ctx->m_dirTableLen){ ctx->dirTable = ctx->level[3].pos + level3_pos; u32_to_u8(ctx->romfsHdr->section[i].offset,level3_pos,LE); u32_to_u8(ctx->romfsHdr->section[i].size,ctx->m_dirTableLen,LE); level3_pos += ctx->m_dirTableLen; } else if(i == 2){ ctx->fileHashTable = ctx->level[3].pos + level3_pos; u32_to_u8(ctx->romfsHdr->section[i].offset,level3_pos,LE); u32_to_u8(ctx->romfsHdr->section[i].size,ctx->m_fileHashTable*sizeof(u32),LE); level3_pos += ctx->m_fileHashTable*sizeof(u32); } else if(i == 3 && ctx->m_fileTableLen){ ctx->fileTable = ctx->level[3].pos + level3_pos; u32_to_u8(ctx->romfsHdr->section[i].offset,level3_pos,LE); u32_to_u8(ctx->romfsHdr->section[i].size,ctx->m_fileTableLen,LE); level3_pos += ctx->m_fileTableLen; } else{ u32_to_u8(ctx->romfsHdr->section[i].offset,0,LE); u32_to_u8(ctx->romfsHdr->section[i].size,0,LE); } } ctx->data = ctx->level[3].pos + align(level3_pos,0x10); u32_to_u8(ctx->romfsHdr->dataoffset,align(level3_pos,0x10),LE); for (u32 i = 0; i < ctx->m_dirHashTable; i++) { u32_to_u8(ctx->dirHashTable+i*4, ROMFS_UNUSED_ENTRY, LE); } for (u32 i = 0; i < ctx->m_fileHashTable; i++) { u32_to_u8(ctx->fileHashTable+i*4, ROMFS_UNUSED_ENTRY, LE); } } u32 GetFileHashTableIndex(romfs_buildctx *ctx, u32 parent, fs_romfs_char *path) { u32 hash = CalcPathHash(parent, path, 0, fs_u16StrLen(path)); return hash % ctx->m_fileHashTable; } u32 GetDirHashTableIndex(romfs_buildctx *ctx, u32 parent, fs_romfs_char* path) { u32 hash = CalcPathHash(parent, path, 0, fs_u16StrLen(path)); return hash % ctx->m_dirHashTable; } void AddFileToRomfs(romfs_buildctx *ctx, fs_file *file, u32 parent, u32 sibling) { romfs_fileentry *entry = (romfs_fileentry*)(ctx->fileTable + ctx->u_fileTableLen); u32_to_u8(entry->parentdiroffset,parent,LE); u32_to_u8(entry->siblingoffset,sibling,LE); /* Import name */ u32_to_u8(entry->namesize,file->name_len,LE); u8 *name_pos = (u8*)(ctx->fileTable + ctx->u_fileTableLen + sizeof(romfs_fileentry)); memset(name_pos,0,align(file->name_len,4)); memcpy(name_pos,(u8*)file->name,file->name_len); /* Set hash data */ u32 hashindex = GetFileHashTableIndex(ctx, parent, file->name); u32_to_u8(entry->hashoffset, u8_to_u32(ctx->fileHashTable + hashindex*4, LE), LE); u32_to_u8(ctx->fileHashTable + hashindex*4, ctx->u_fileTableLen, LE); /* Import data */ if(file->size) { ctx->u_dataLen = align(ctx->u_dataLen,0x10); // Padding u64_to_u8(entry->dataoffset,ctx->u_dataLen,LE); u64_to_u8(entry->datasize,file->size,LE); u8 *data_pos = (ctx->data + ctx->u_dataLen); ReadFile64(data_pos,file->size,0,file->fp); ctx->u_dataLen += file->size; // adding file size } else u64_to_u8(entry->dataoffset,0x00,LE); /* Increment used file table length */ ctx->u_fileTableLen += sizeof(romfs_fileentry) + align(file->name_len,4); } void AddDirToRomfs(romfs_buildctx *ctx, fs_dir *fs, u32 parent, u32 sibling) { u32 offset = ctx->u_dirTableLen; u32 hashindex; romfs_direntry *entry = (romfs_direntry*)(ctx->dirTable + offset); /* Set entry data */ u32_to_u8(entry->parentoffset,parent,LE); u32_to_u8(entry->siblingoffset,sibling,LE); u32_to_u8(entry->childoffset, ROMFS_UNUSED_ENTRY, LE); u32_to_u8(entry->fileoffset, ROMFS_UNUSED_ENTRY, LE); /* If root dir ... */ if(offset == 0) { /* Import name (root dir has no name) */ u32_to_u8(entry->namesize,0,LE); /* Get hash table index */ hashindex = GetDirHashTableIndex(ctx, parent, (fs_romfs_char*)ROMFS_EMPTY_PATH); /* Increment used dir table length */ ctx->u_dirTableLen += sizeof(romfs_direntry); } else { /* Import name */ u32_to_u8(entry->namesize,fs->name_len,LE); u8 *name_pos = (u8*)(ctx->dirTable + ctx->u_dirTableLen + sizeof(romfs_direntry)); memset(name_pos,0,(u32)align(fs->name_len,4)); memcpy(name_pos,(u8*)fs->name,fs->name_len); /* Get hash table index */ hashindex = GetDirHashTableIndex(ctx, parent, fs->name); /* Increment used dir table length */ ctx->u_dirTableLen += sizeof(romfs_direntry) + (u32)align(fs->name_len,4); } /* Set hash data */ u32_to_u8(entry->hashoffset, u8_to_u32(ctx->dirHashTable + hashindex*4, LE), LE); u32_to_u8(ctx->dirHashTable + hashindex*4, offset, LE); } void AddDirChildrenToRomfs(romfs_buildctx *ctx, fs_dir *fs, u32 parent, u32 dir) { romfs_direntry *entry = (romfs_direntry*)(ctx->dirTable + dir); if (fs->u_file) { u32_to_u8(entry->fileoffset, ctx->u_fileTableLen, LE); /* Create file entries*/ for (u32 i = 0; i < fs->u_file; i++) { /* If is the last file, no more siblings */ u32 file_sibling = 0; if (i >= fs->u_file - 1) file_sibling = ROMFS_UNUSED_ENTRY; else file_sibling = ctx->u_fileTableLen + sizeof(romfs_fileentry) + (u32)align(fs->file[i].name_len, 4); /* Create file entry */ AddFileToRomfs(ctx, &fs->file[i], dir, file_sibling); } } if (fs->u_dir) { /* Prepare to store child addresses */ u32 *childs = calloc(fs->u_dir, sizeof(u32)); /* Create child directory entries*/ u32_to_u8(entry->childoffset, ctx->u_dirTableLen, LE); fs_dir *subdir = (fs_dir*)fs->dir; for (u32 i = 0; i < fs->u_dir; i++) { /* Store address fo child */ childs[i] = ctx->u_dirTableLen; u32 dir_sibling = 0; if (i >= fs->u_dir - 1) dir_sibling = ROMFS_UNUSED_ENTRY; else dir_sibling = ctx->u_dirTableLen + sizeof(romfs_direntry) + (u32)align(subdir[i].name_len, 4); /* Create child directory entry */ AddDirToRomfs(ctx, &subdir[i], dir, dir_sibling); } /* Populate child's childs */ for (u32 i = 0; i < fs->u_dir; i++) { AddDirChildrenToRomfs(ctx, &subdir[i], dir, childs[i]); } free(childs); } } void PopulateRomfs(romfs_buildctx *ctx) { AddDirToRomfs(ctx, ctx->fs, 0x0, ROMFS_UNUSED_ENTRY); AddDirChildrenToRomfs(ctx, ctx->fs, 0x0, 0); } void BuildIvfcHeader(romfs_buildctx *ctx) { memcpy(ctx->ivfcHdr->magic,"IVFC",4); u32_to_u8(ctx->ivfcHdr->id,0x10000,LE); u32 masterHashSize = ( align(ctx->level[1].size,ROMFS_BLOCK_SIZE) / ROMFS_BLOCK_SIZE ) * SHA_256_LEN ; u32_to_u8(ctx->ivfcHdr->masterHashSize,masterHashSize,LE); for(int i = 1; i < 4; i++){ u64_to_u8(ctx->ivfcHdr->level[i-1].logicalOffset,ctx->level[i].logicalOffset,LE); u64_to_u8(ctx->ivfcHdr->level[i-1].hashDataSize,ctx->level[i].size,LE); u32_to_u8(ctx->ivfcHdr->level[i-1].blockSize,log2l(ROMFS_BLOCK_SIZE),LE); } u32_to_u8(ctx->ivfcHdr->optionalSize,sizeof(ivfc_hdr),LE); return; } void GenIvfcHashTree(romfs_buildctx *ctx) { for(int i = 2; i >= 0; i--){ u32 numHashes = align(ctx->level[i+1].size,ROMFS_BLOCK_SIZE) / ROMFS_BLOCK_SIZE; for(u32 j = 0; j < numHashes; j++){ u8 *datapos = (u8*)(ctx->level[i+1].pos + ROMFS_BLOCK_SIZE * j); u8 *hashpos = (u8*)(ctx->level[i].pos + SHA_256_LEN * j); ShaCalc(datapos, ROMFS_BLOCK_SIZE, hashpos, CTR_SHA_256); } } return; } u32 CalcPathHash(u32 parent, fs_romfs_char* path, u32 start, u32 length) { u32 hash = parent ^ 123456789; for( u32 index = 0; index < length; index++ ) { hash = (u32)((hash >> 5) | (hash << 27));//ror hash ^= (u16)path[start + index]; } return hash; }