#include #include #include #include #include #include "rom.h" #include "ph.h" #include "util.h" #define VERSION "1.0.2" #define INDENT 4 // Command line flags for debugging purposes bool printFileHierarchy = false; bool printFileAllocOrder = false; void Indent(size_t depth) { char spaces[INDENT + 1]; memset(spaces, ' ', INDENT); spaces[INDENT] = '\0'; for (size_t i = 0; i < depth; ++i) { puts(spaces); } } bool CheckRegion(const Header *pHeader, BuildInfo *pInfo) { Region region = pHeader->gamecode[3]; if ( memcmp(pHeader->gamecode, GAMECODE_PREFIX, 3) != 0 || ( region != REGION_JAPAN && region != REGION_USA && region != REGION_EUROPE ) ) { FATAL("Invalid gamecode prefix '%.4s'\n", pHeader->gamecode); } pInfo->region = region; return true; } bool ExtractArm7(const uint8_t *rom, ProgramOffset *pArm7) { File file; if (!FileOpenWrite(ARM7_PROGRAM_FILE, &file)) FATAL("Failed to create ARM7 program '" ARM7_PROGRAM_FILE "'\n"); if (!FileWrite(&file, rom + pArm7->offset, pArm7->size, 1)) FATAL("Failed to write ARM7 program '" ARM7_PROGRAM_FILE "'\n"); FileClose(&file); return true; } bool ExtractTitle(const char *language, const char *fileName, const wchar_t *title, size_t titleSize) { char buf[1024]; File file; if (!FileOpenWrite(fileName, &file)) FATAL("Failed to create %s banner title '%s'\n", language, fileName); size_t resultSize = 0; if (!WcharToUtf8((wchar_t*) title, titleSize, buf, sizeof(buf), &resultSize)) return false; size_t len = strlen(buf); if (!FileWrite(&file, buf, len, 1)) FATAL("Failed to write %s banner title '%s'\n", language, fileName); FileClose(&file); return true; } bool ExtractBanner(const Banner *pBanner, const BuildInfo *pInfo) { if (!MakeDir(BANNER_SUBDIR)) return 1; File file; if (!FileOpenWrite(ICON_BITMAP_FILE, &file)) FATAL("Failed to create banner icon bitmap '" ICON_BITMAP_FILE "'\n"); if (!FileWrite(&file, pBanner->iconBitmap, sizeof(pBanner->iconBitmap), 1)) { FATAL("Failed to write banner icon bitmap '" ICON_BITMAP_FILE "'\n"); } FileClose(&file); if (!FileOpenWrite(ICON_PALETTE_FILE, &file)) FATAL("Failed to create banner icon palette '" ICON_PALETTE_FILE "'\n"); if (!FileWrite(&file, pBanner->iconPalette, sizeof(pBanner->iconPalette), 1)) { FATAL("Failed to write banner icon palette '" ICON_PALETTE_FILE "'\n"); } FileClose(&file); if (!ExtractTitle("Japanese", TITLE_JAP_FILE, pBanner->japaneseTitle, sizeof(pBanner->japaneseTitle))) return false; if (!ExtractTitle("English", TITLE_ENG_FILE, pBanner->englishTitle, sizeof(pBanner->englishTitle))) return false; if (!ExtractTitle("French", TITLE_FRE_FILE, pBanner->frenchTitle, sizeof(pBanner->frenchTitle))) return false; if (!ExtractTitle("German", TITLE_GER_FILE, pBanner->germanTitle, sizeof(pBanner->germanTitle))) return false; if (!ExtractTitle("Italian", TITLE_ITA_FILE, pBanner->italianTitle, sizeof(pBanner->italianTitle))) return false; if (!ExtractTitle("Spanish", TITLE_SPA_FILE, pBanner->spanishTitle, sizeof(pBanner->spanishTitle))) return false; return true; } typedef struct { char basePath[256]; size_t basePathLen; char **filePathList; size_t numFilePaths; size_t maxFilePaths; size_t depth; } ExtractContext; bool GrowFilePathList(ExtractContext *pContext, size_t minEntries) { ExtractContext ctx; memcpy(&ctx, pContext, sizeof(ctx)); if (minEntries <= ctx.maxFilePaths) return true; while (minEntries > ctx.maxFilePaths) { ctx.maxFilePaths *= 2; } char **newFilePathList = realloc(ctx.filePathList, ctx.maxFilePaths * sizeof(*ctx.filePathList)); if (newFilePathList == NULL) FATAL("Failed to reallocate file path list to %ld entries\n", ctx.maxFilePaths); ctx.filePathList = newFilePathList; // Fill new entries with 0 memset(&ctx.filePathList[pContext->maxFilePaths], 0, (ctx.maxFilePaths - pContext->maxFilePaths) * sizeof(*ctx.filePathList)); memcpy(pContext, &ctx, sizeof(ctx)); return true; } bool ExtractSubtable( const uint8_t *rom, const uint8_t *fatStart, const uint8_t *fntStart, const FntEntry *pFntEntry, ExtractContext *pContext ) { ExtractContext ctx; memcpy(&ctx, pContext, sizeof(ctx)); const uint8_t *subEntryAddr = fntStart + pFntEntry->subtableOffset; const FntSubEntry *pSubEntry = (const FntSubEntry*) subEntryAddr; uint16_t fileId = pFntEntry->firstFile; while(pSubEntry->length > 0) { char name[128]; memcpy(name, pSubEntry->name, pSubEntry->length); name[pSubEntry->length] = '\0'; if (!pSubEntry->isSubdir) { if (printFileHierarchy) { Indent(ctx.depth); printf("%d: %s\n", fileId, name); } const FatEntry *pFatEntry = (const FatEntry*) fatStart + fileId; size_t fileSize = pFatEntry->endOffset - pFatEntry->startOffset; const uint8_t *pFileBytes = rom + pFatEntry->startOffset; File file; if (!FileOpenWrite(name, &file)) FATAL("Failed to open assets file '%s'\n", name); if (!FileWrite(&file, pFileBytes, fileSize, 1)) FATAL("Failed to write to assets file '%s'\n", name); FileClose(&file); if (printFileAllocOrder) { if (!GrowFilePathList(&ctx, fileId + 1)) return false; char *filePath = malloc(ctx.basePathLen + pSubEntry->length + 1); if (filePath == NULL) FATAL("Failed to allocate string for file path\n"); sprintf(filePath, "%s%s", ctx.basePath, name); ctx.filePathList[fileId] = filePath; } subEntryAddr += sizeof(FntSubEntry) + pSubEntry->length; pSubEntry = (const FntSubEntry*) subEntryAddr; ++fileId; continue; } if (printFileHierarchy) { Indent(ctx.depth); printf("%s {\n", name); } if (!MakeDir(name)) return false; if (!ChangeDir(name)) return false; uint16_t subdirId = READ_SUBDIR_ID(pSubEntry); uint16_t subdirIndex = subdirId & 0xfff; ctx.depth += 1; if (printFileAllocOrder) { size_t basePathSpace = sizeof(ctx.basePath) - ctx.basePathLen; size_t basePathAdded = snprintf(&ctx.basePath[ctx.basePathLen], basePathSpace, "%s/", name); if (basePathAdded >= basePathSpace) FATAL("Maximum base path size was exceeded\n"); ctx.basePathLen += basePathAdded; if (!ExtractSubtable(rom, fatStart, fntStart, (FntEntry*) fntStart + subdirIndex, &ctx)) return false; ctx.basePathLen -= basePathAdded; memset(&ctx.basePath[ctx.basePathLen], 0, basePathAdded); } else { if (!ExtractSubtable(rom, fatStart, fntStart, (FntEntry*) fntStart + subdirIndex, &ctx)) return false; } ctx.depth -= 1; if (printFileHierarchy) { Indent(ctx.depth); printf("}\n"); } if (!ChangeDir("..")) return false; subEntryAddr += sizeof(FntSubEntry) + pSubEntry->length + sizeof(subdirId); pSubEntry = (const FntSubEntry*) subEntryAddr; } memcpy(pContext, &ctx, sizeof(ctx)); return true; } typedef struct { uint32_t fileId; uint32_t startOffset; uint32_t endOffset; } FatInfo; int32_t CompareFatInfo(const void *a, const void *b) { const FatInfo *infoA = (FatInfo*) a; const FatInfo *infoB = (FatInfo*) b; return infoA->startOffset - infoB->startOffset; } bool PrintFileAllocOrder(ExtractContext *ctx, const uint8_t *fatAddr) { const FatEntry *fatStart = (FatEntry*) fatAddr; const FatEntry *fat; for (fat = fatStart; fat->startOffset != 0xFFFFFFFF; ++fat); size_t numFiles = fat - fatStart; FatInfo *fatInfo = malloc(numFiles * sizeof(FatInfo)); if (fatInfo == NULL) FATAL("Failed to allocate array for printing file allocation order\n"); for (size_t i = 0; i < numFiles; ++i) { fatInfo[i].fileId = i; fatInfo[i].startOffset = fatStart[i].startOffset; fatInfo[i].endOffset = fatStart[i].endOffset; } qsort(fatInfo, numFiles, sizeof(*fatInfo), CompareFatInfo); for (size_t i = 0; i < numFiles; ++i) { uint32_t fileId = fatInfo[i].fileId; if (ctx->filePathList[fileId] == NULL) continue; printf("%s\n", ctx->filePathList[fileId]); } free(fatInfo); return true; } bool ExtractAssets(const uint8_t *rom, const uint8_t *fatStart, const uint8_t *fntStart) { ExtractContext ctx; memset(ctx.basePath, 0, sizeof(ctx.basePath)); ctx.basePathLen = 0; ctx.numFilePaths = 0; if (printFileAllocOrder) { ctx.maxFilePaths = 4096; ctx.filePathList = calloc(ctx.maxFilePaths, sizeof(*ctx.filePathList)); } else { ctx.maxFilePaths = 0; ctx.filePathList = NULL; } ctx.depth = 0; if (!ExtractSubtable(rom, fatStart, fntStart, (FntEntry*) fntStart, &ctx)) return false; if (printFileAllocOrder) { if (!PrintFileAllocOrder(&ctx, fatStart)) return false; for (size_t i = 0; i < ctx.numFilePaths; ++i) { if (ctx.filePathList[i] != NULL) free(ctx.filePathList[i]); } free(ctx.filePathList); } return true; } bool ExtractOverlayData(const uint8_t *rom, const Header *header) { const OverlayEntry *entry = (OverlayEntry*) (rom + header->arm9Overlays.offset); const OverlayEntry *end = entry + header->arm9Overlays.size / sizeof(OverlayEntry); File file; if (!FileOpenWrite(ARM9_OVERLAY_DATA_FILE, &file)) { FATAL("Failed to open overlay data file '" ARM9_OVERLAY_DATA_FILE "'\n"); } for(; entry < end; ++entry) { OverlayData data; data.fileId = entry->fileId; if (!FileWrite(&file, &data, sizeof(data), 1)) FATAL("Failed to write overlay data to '" ARM9_OVERLAY_DATA_FILE "'\n"); } FileClose(&file); return true; } void PrintUsage(const char *program) { printf( "extractrom " VERSION "\n" "\n" "Usage: %s -i ROMFILE -o OUTDIR [-n] [-a] [-l]\n" " -o OUTDIR \tDirectory to extract files to\n" " -i ROMFILE\tROM to extract from\n" " -n \tPrints file name hierarchy to stdout\n" " -a \tPrints file allocation order to stdout\n", program ); } int main(int argc, const char **argv) { // --------------------- Parse command line arguments --------------------- const char *program = argv[0]; if (argc == 1) { PrintUsage(program); return 0; } const char *romFile = NULL; const char *outDir = NULL; for (int i = 1; i < argc; ++i) { if (strcmp(argv[i], "-o") == 0) { if (++i >= argc) { fprintf(stderr, "Expected dirname after -o\n"); return 1; } outDir = argv[i]; } else if (strcmp(argv[i], "-i") == 0) { if (++i >= argc) { fprintf(stderr, "Expected filename after -i\n"); return 1; } romFile = argv[i]; } else if (strcmp(argv[i], "-n") == 0) { printFileHierarchy = true; } else if (strcmp(argv[i], "-a") == 0) { printFileAllocOrder = true; } else { fprintf(stderr, "Unknown option '%s'\n", argv[i]); return 1; } } if (romFile == NULL) { PrintUsage(program); fprintf(stderr, "Please provide a ROM file, see usage above.\n"); return 1; } if (outDir == NULL) { PrintUsage(program); fprintf(stderr, "Please provide an output directory, see usage above.\n"); return 1; } // --------------------- Load ROM --------------------- File fileRom; if (!FileOpenRead(romFile, &fileRom)) { fprintf(stderr, "Failed to open input ROM '%s'\n", romFile); return 1; } size_t romSize = FileSize(&fileRom); uint8_t *rom = malloc(romSize); if (rom == NULL) { fprintf(stderr, "Failed to allocate buffer for '%s'\n", romFile); return 1; } if (FileRead(&fileRom, rom, romSize, 1) != 1) { fprintf(stderr, "Failed to read from '%s'\n", romFile); return 1; } FileClose(&fileRom); // --------------------- Set up --------------------- Header *pHeader = (Header*) rom; BuildInfo info; if (!CheckRegion(pHeader, &info)) return 1; if (!MakeDir(outDir)) return 1; if (!ChangeDir(outDir)) return 1; // --------------------- Extract ARM7 program --------------------- if (!ExtractArm7(rom, &pHeader->arm7)) return 1; // --------------------- Extract banner --------------------- Banner *pBanner = (Banner*) (rom + pHeader->bannerOffset); if (!ExtractBanner(pBanner, &info)) return 1; // --------------------- Extract assets --------------------- if (!MakeDir(ASSETS_SUBDIR)) return 1; if (!ChangeDir(ASSETS_SUBDIR)) return 1; const uint8_t *fntStart = rom + pHeader->fileNames.offset; const uint8_t *fatStart = rom + pHeader->fileAllocs.offset; if (!ExtractAssets(rom, fatStart, fntStart)) return 1; if (!ChangeDir("..")) return 1; // --------------------- Extract overlay data --------------------- if (!ExtractOverlayData(rom, pHeader)) return 1; if (!ChangeDir("..")) return 1; free(rom); }