mirror of
https://github.com/zeldaret/ph
synced 2026-05-23 15:01:37 -04:00
Remove obsolete ROM tools
This commit is contained in:
@@ -1,2 +0,0 @@
|
||||
compress
|
||||
compress.exe
|
||||
@@ -1,22 +0,0 @@
|
||||
CC := gcc
|
||||
CFLAGS := -g
|
||||
|
||||
ifneq ($(DEBUG),1)
|
||||
CFLAGS += -O2 -DNDEBUG
|
||||
endif
|
||||
|
||||
ifeq ($(OS),Windows_NT)
|
||||
OUTFILE := compress.exe
|
||||
else
|
||||
OUTFILE := compress
|
||||
endif
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(OUTFILE)
|
||||
|
||||
clean:
|
||||
rm -f $(OUTFILE)
|
||||
|
||||
$(OUTFILE): main.c
|
||||
$(CC) $(CFLAGS) -o $(OUTFILE) main.c
|
||||
@@ -1,287 +0,0 @@
|
||||
// Compresses files using a backwards LZ77 algorithm.
|
||||
// This is used to compress code after linking so that the compressed code matches the base ROM.
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define VERSION "1.0"
|
||||
|
||||
#ifndef min
|
||||
# define min(a,b) ((a) < (b) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
#define WRITE16(buf,val) do { (buf)[0] = (val) & 0xFF; (buf)[1] = ((val) >> 8) & 0xFF; } while (0)
|
||||
#define WRITE24(buf,val) do { (buf)[0] = (val) & 0xFF; (buf)[1] = ((val) >> 8) & 0xFF; (buf)[2] = ((val) >> 16) & 0xFF; } while (0)
|
||||
#define WRITE32(buf,val) do { (buf)[0] = (val) & 0xFF; (buf)[1] = ((val) >> 8) & 0xFF; (buf)[2] = ((val) >> 16) & 0xFF; (buf)[3] = ((val) >> 24) & 0xFF; } while (0)
|
||||
#define READ16(buf) ((buf)[0] | ((buf)[1] << 8))
|
||||
#define READ24(buf) ((buf)[0] | ((buf)[1] << 8) | ((buf)[2] << 16))
|
||||
#define READ32(buf) ((buf)[0] | ((buf)[1] << 8) | ((buf)[2] << 16) | ((buf)[3] << 24))
|
||||
|
||||
#define LEN_BITS 4
|
||||
#define DIST_BITS 12
|
||||
#define MIN_SUBSEQ 3
|
||||
|
||||
#define LEN_MASK ((1 << (LEN_BITS)) - 1)
|
||||
#define DIST_MASK ((1 << (DIST_BITS)) - 1)
|
||||
|
||||
#define MAX_SUBSEQ ((MIN_SUBSEQ) + LEN_MASK)
|
||||
#define LOOKAHEAD (1 << (DIST_BITS))
|
||||
|
||||
typedef struct {
|
||||
uint8_t *pos;
|
||||
uint32_t totalBytesSaved;
|
||||
uint8_t flags;
|
||||
} BlockInfo;
|
||||
|
||||
bool FindSubsequence(const uint8_t *buf, const uint8_t *start, const uint8_t *end, size_t *pLen, size_t *pDist) {
|
||||
size_t bestLen = 0;
|
||||
size_t bestDist = 0;
|
||||
size_t maxLookahead = min(LOOKAHEAD + MAX_SUBSEQ, end - buf - 1);
|
||||
for (size_t i = 0; i < maxLookahead; ++i) {
|
||||
const uint8_t *needle = buf;
|
||||
const uint8_t *haystack = buf + 1 + i;
|
||||
if (*needle != *haystack) continue;
|
||||
size_t len = 0;
|
||||
while (needle - len >= start && needle[-len] == haystack[-len] && haystack - len > buf && len < MAX_SUBSEQ) {
|
||||
++len;
|
||||
}
|
||||
size_t dist = haystack - needle - MIN_SUBSEQ;
|
||||
if (len > bestLen && dist <= DIST_MASK) {
|
||||
bestLen = len;
|
||||
bestDist = dist;
|
||||
}
|
||||
}
|
||||
if (bestLen < MIN_SUBSEQ) return false;
|
||||
*pLen = bestLen;
|
||||
*pDist = bestDist;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Compress(const uint8_t *src, uint8_t *dst, size_t size, uint8_t **pResult, size_t *pLen, size_t *pNumIdentical) {
|
||||
const uint8_t *end = src + size;
|
||||
const uint8_t *read = end;
|
||||
uint8_t *writeEnd = dst + size;
|
||||
uint8_t *write = writeEnd - 1;
|
||||
uint8_t *pFlags = write;
|
||||
uint8_t flags;
|
||||
size_t flagCount = 0;
|
||||
int32_t bytesSaved = 0;
|
||||
BlockInfo *blockInfos = malloc(((size + 7) / 8) * sizeof(BlockInfo));
|
||||
size_t numBlocks = 0;
|
||||
while (read > src) {
|
||||
flags <<= 1;
|
||||
size_t len, dist;
|
||||
if (FindSubsequence(read - 1, src, end, &len, &dist)) {
|
||||
// write length-distance pair
|
||||
size_t pair = (((len - MIN_SUBSEQ) & LEN_MASK) << DIST_BITS) | (dist & DIST_MASK);
|
||||
read -= len;
|
||||
write -= 2;
|
||||
WRITE16(write, pair);
|
||||
flags |= 1;
|
||||
bytesSaved += len - 2;
|
||||
} else {
|
||||
// write literal
|
||||
write -= 1;
|
||||
read -= 1;
|
||||
*write = *read;
|
||||
}
|
||||
flagCount++;
|
||||
if (flagCount == 8) {
|
||||
flagCount = 0;
|
||||
*pFlags = flags;
|
||||
bytesSaved -= 1;
|
||||
write -= 1;
|
||||
pFlags = write;
|
||||
|
||||
blockInfos[numBlocks].pos = write;
|
||||
blockInfos[numBlocks].totalBytesSaved = bytesSaved;
|
||||
blockInfos[numBlocks].flags = flags;
|
||||
numBlocks++;
|
||||
|
||||
flags = 0;
|
||||
}
|
||||
}
|
||||
if (flagCount != 0) {
|
||||
blockInfos[numBlocks].pos = write;
|
||||
blockInfos[numBlocks].totalBytesSaved = bytesSaved;
|
||||
numBlocks++;
|
||||
|
||||
*pFlags = flags << (8 - flagCount);
|
||||
}
|
||||
else {
|
||||
write += 1;
|
||||
}
|
||||
size_t numIdentical = 0;
|
||||
for (int32_t i = 0; i < numBlocks - 1; ++i) {
|
||||
if (blockInfos[i].totalBytesSaved == bytesSaved) {
|
||||
size_t flagBytesSaved = 0;
|
||||
for (; i >= 0; --i, ++flagBytesSaved) {
|
||||
// Save more bytes by ignoring blocks that have no length-distance pairs in them
|
||||
if (blockInfos[i].flags != 0) break;
|
||||
}
|
||||
numIdentical = blockInfos[i].pos - write;
|
||||
write += 1;
|
||||
|
||||
// See if it's possible to remove some chunks based on the number of flag bytes saved
|
||||
flags = blockInfos[i].flags;
|
||||
for (int32_t j = 0; j < 8 && flagBytesSaved > 0; ++j) {
|
||||
if ((flags & 0x80) != 0) {
|
||||
if (flagBytesSaved < 2) break;
|
||||
numIdentical += 2;
|
||||
flagBytesSaved -= 2;
|
||||
} else {
|
||||
numIdentical += 1;
|
||||
flagBytesSaved -= 1;
|
||||
}
|
||||
flags >>= 1;
|
||||
}
|
||||
|
||||
memcpy(write, src, numIdentical);
|
||||
while (write[numIdentical] == read[numIdentical]) {
|
||||
numIdentical += 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(blockInfos);
|
||||
*pResult = write;
|
||||
*pLen = writeEnd - write;
|
||||
*pNumIdentical = numIdentical;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteFooter(FILE *fp, const char *fileName, bool addPadding, uint32_t compressedSize, uint32_t originalSize, uint32_t start, uint32_t numIdentical) {
|
||||
size_t padding = 0;
|
||||
if (addPadding) padding = (0x4 - (compressedSize & 0x3)) & 0x3;
|
||||
for (size_t i = 0; i < padding; ++i) fputc(0xFF, fp);
|
||||
compressedSize += padding + 8;
|
||||
uint32_t readOffset = padding + 8;
|
||||
uint32_t writeOffset = originalSize - compressedSize - start;
|
||||
compressedSize -= numIdentical;
|
||||
if (fwrite(&compressedSize, 3, 1, fp) != 1) {
|
||||
if (fp == stdout) fprintf(stderr, "Failed to write compressed size to stdout\n");
|
||||
else fprintf(stderr, "Failed to write compressed size to '%s'\n", fileName);
|
||||
return false;
|
||||
}
|
||||
if (fwrite(&readOffset, 1, 1, fp) != 1) {
|
||||
if (fp == stdout) fprintf(stderr, "Failed to write read offset to stdout\n");
|
||||
else fprintf(stderr, "Failed to write read offset to '%s'\n", fileName);
|
||||
return false;
|
||||
}
|
||||
if (fwrite(&writeOffset, 4, 1, fp) != 1) {
|
||||
if (fp == stdout) fprintf(stderr, "Failed to write write offset to stdout\n");
|
||||
else fprintf(stderr, "Failed to write write offset to '%s'\n", fileName);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PrintUsage(const char *program) {
|
||||
printf(
|
||||
"compress " VERSION "\n"
|
||||
"\n"
|
||||
"Usage: %s [-o OUTFILE] [-s START] [-p] -i INFILE\n"
|
||||
" -o OUTFILE\tWrites result to file instead of stdout\n"
|
||||
" -s START \tOffset to start compressing from\n"
|
||||
" -p \tPad compressed data to 4-alignment\n"
|
||||
" -i INFILE \tInput file to compress",
|
||||
program
|
||||
);
|
||||
}
|
||||
|
||||
int main(int argc, const char **argv) {
|
||||
const char *program = argv[0];
|
||||
if (argc == 1) {
|
||||
PrintUsage(program);
|
||||
return 0;
|
||||
}
|
||||
const char *outFile = NULL;
|
||||
const char *inFile = NULL;
|
||||
int start = 0;
|
||||
bool padCompressedBlock = false;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (strcmp(argv[i], "-o") == 0) {
|
||||
if (++i >= argc) {
|
||||
fprintf(stderr, "Expected filename after -o\n");
|
||||
return 1;
|
||||
}
|
||||
outFile = argv[i];
|
||||
} else if (strcmp(argv[i], "-s") == 0) {
|
||||
if (++i >= argc) {
|
||||
fprintf(stderr, "Expected start offset after -s\n");
|
||||
return 1;
|
||||
}
|
||||
if (sscanf(argv[i], "%i", &start) != 1) {
|
||||
fprintf(stderr, "Invalid start offset '%s' could not be parsed\n", argv[i]);
|
||||
return 1;
|
||||
}
|
||||
} else if (strcmp(argv[i], "-p") == 0) {
|
||||
padCompressedBlock = true;
|
||||
} else if (strcmp(argv[i], "-i") == 0) {
|
||||
if (++i >= argc) {
|
||||
fprintf(stderr, "Expected filename after -i\n");
|
||||
return 1;
|
||||
}
|
||||
inFile = argv[i];
|
||||
} else {
|
||||
fprintf(stderr, "Unknown option '%s'\n", argv[i]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (inFile == NULL) {
|
||||
PrintUsage(program);
|
||||
fprintf(stderr, "Please provide an input file to compress, see usage above.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
FILE *fpIn = fopen(inFile, "rb");
|
||||
if (fpIn == NULL) {
|
||||
fprintf(stderr, "Failed to open input file '%s'\n", inFile);
|
||||
return 1;
|
||||
}
|
||||
fseek(fpIn, 0, SEEK_END);
|
||||
size_t inSize = ftell(fpIn);
|
||||
uint8_t *buf = malloc(inSize);
|
||||
if (buf == NULL) {
|
||||
fprintf(stderr, "Failed to allocate input buffer for '%s'\n", inFile);
|
||||
return 1;
|
||||
}
|
||||
fseek(fpIn, 0, SEEK_SET);
|
||||
if (fread(buf, inSize, 1, fpIn) != 1) {
|
||||
fprintf(stderr, "Failed to read input file '%s'\n", inFile);
|
||||
return 1;
|
||||
}
|
||||
fclose(fpIn);
|
||||
|
||||
FILE *fpOut = stdout;
|
||||
if (outFile != NULL) {
|
||||
fpOut = fopen(outFile, "wb");
|
||||
if (fpOut == NULL) {
|
||||
fprintf(stderr, "Failed to open output file '%s'\n", outFile);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (start > 0 && fwrite(buf, start, 1, fpOut) != 1) {
|
||||
if (fpOut == stdout) fprintf(stderr, "Failed to write uncompressed header to stdout\n");
|
||||
else fprintf(stderr, "Failed to write uncompressed header to '%s'\n", outFile);
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint8_t *outBuf = malloc(inSize - start);
|
||||
uint8_t *result;
|
||||
size_t resultLen, numIdentical;
|
||||
if (!Compress(buf + start, outBuf, inSize - start, &result, &resultLen, &numIdentical)) return 1;
|
||||
if (fwrite(result, resultLen, 1, fpIn) != 1) {
|
||||
if (fpOut == stdout) fprintf(stderr, "Failed to write compressed output to stdout\n");
|
||||
else fprintf(stderr, "Failed to write compressed output to '%s'\n", outFile);
|
||||
return 1;
|
||||
}
|
||||
if (!WriteFooter(fpOut, outFile, padCompressedBlock, resultLen, inSize, start, numIdentical)) return 1;
|
||||
free(outBuf);
|
||||
|
||||
if (outFile != NULL) fclose(fpOut);
|
||||
free(buf);
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
extractrom
|
||||
extractrom.exe
|
||||
buildrom
|
||||
buildrom.exe
|
||||
@@ -1,29 +0,0 @@
|
||||
CC := gcc
|
||||
CFLAGS := -g -Wall -I../include
|
||||
|
||||
ifneq ($(DEBUG),1)
|
||||
CFLAGS += -O2 -DNDEBUG
|
||||
endif
|
||||
|
||||
ifeq ($(OS),Windows_NT)
|
||||
EXTRACTFILE := extractrom.exe
|
||||
BUILDFILE := buildrom.exe
|
||||
else
|
||||
EXTRACTFILE := extractrom
|
||||
BUILDFILE := buildrom
|
||||
CFLAGS += -fshort-wchar
|
||||
endif
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(EXTRACTFILE) $(BUILDFILE)
|
||||
|
||||
clean:
|
||||
rm -f $(EXTRACTFILE)
|
||||
rm -f $(BUILDFILE)
|
||||
|
||||
$(EXTRACTFILE): extract.c
|
||||
$(CC) $(CFLAGS) -o $(EXTRACTFILE) extract.c
|
||||
|
||||
$(BUILDFILE): build.c
|
||||
$(CC) $(CFLAGS) -o $(BUILDFILE) build.c
|
||||
-1020
File diff suppressed because it is too large
Load Diff
@@ -1,395 +0,0 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
@@ -1,223 +0,0 @@
|
||||
#ifndef __FILES_H
|
||||
#define __FILES_H
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
#include "util.h"
|
||||
#include "rom.h"
|
||||
|
||||
typedef struct FileTree {
|
||||
struct FileTree *children;
|
||||
uint16_t numChildren;
|
||||
uint16_t maxChildren;
|
||||
uint16_t firstFileId;
|
||||
bool addedToFat;
|
||||
FntSubEntry *entry;
|
||||
} FileTree;
|
||||
|
||||
bool MakeFileTree(FileTree *pTree);
|
||||
|
||||
bool IterFiles(bool (*callback)(const char *name, bool isDir, void*), void *userData) {
|
||||
#ifdef __UTIL_WINDOWS
|
||||
WIN32_FIND_DATAA findData;
|
||||
HANDLE hFind = FindFirstFileA("*", &findData);
|
||||
if (hFind == INVALID_HANDLE_VALUE) FATAL("Failed to open directory to iterate files\n");
|
||||
do {
|
||||
const char *name = findData.cFileName;
|
||||
if (strcmp(name, ".") == 0) continue;
|
||||
if (strcmp(name, "..") == 0) continue;
|
||||
bool isDir = (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
|
||||
if (!callback(name, isDir, userData)) return false;
|
||||
} while (FindNextFileA(hFind, &findData));
|
||||
FindClose(hFind);
|
||||
#elif defined(__UTIL_LINUX)
|
||||
DIR *dir = opendir(".");
|
||||
struct dirent *entry;
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
const char *name = entry->d_name;
|
||||
if (strcmp(name, ".") == 0) continue;
|
||||
if (strcmp(name, "..") == 0) continue;
|
||||
bool isDir = entry->d_type == DT_DIR;
|
||||
if (!callback(name, isDir, userData)) return false;
|
||||
}
|
||||
closedir(dir);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _GrowFileTreeChildren(FileTree *pTree, size_t minChildren) {
|
||||
FileTree tree;
|
||||
memcpy(&tree, pTree, sizeof(tree));
|
||||
if (tree.maxChildren >= minChildren) return true;
|
||||
|
||||
size_t newSize = tree.maxChildren;
|
||||
if (newSize == 0) newSize = minChildren;
|
||||
while (newSize < minChildren) newSize *= 2;
|
||||
if (tree.children == NULL) {
|
||||
FileTree *children = malloc(newSize * sizeof(FileTree));
|
||||
if (children == NULL) FATAL("Failed to allocate file tree children\n");
|
||||
tree.children = children;
|
||||
} else {
|
||||
FileTree *children = realloc(tree.children, newSize * sizeof(FileTree));
|
||||
if (children == NULL) FATAL("Failed to reallocate file tree children\n");
|
||||
tree.children = children;
|
||||
}
|
||||
tree.maxChildren = newSize;
|
||||
|
||||
memcpy(pTree, &tree, sizeof(tree));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _FileTreeFileCallback(const char *name, bool isDir, void *userData) {
|
||||
FileTree *pTree = (FileTree*) userData;
|
||||
size_t nameLength = strlen(name);
|
||||
|
||||
if (isDir) {
|
||||
FntSubEntry *entry = malloc(sizeof(FntSubEntry) + nameLength + 2);
|
||||
if (entry == NULL) FATAL("Failed to allocate FNT sub entry for directory '%s'\n", name);
|
||||
entry->isSubdir = true;
|
||||
entry->length = nameLength;
|
||||
memcpy(entry->name, name, nameLength);
|
||||
WRITE_SUBDIR_ID(entry, 0);
|
||||
|
||||
if (!ChangeDir(name)) return false;
|
||||
|
||||
FileTree child;
|
||||
if (!MakeFileTree(&child)) return false;
|
||||
child.entry = entry;
|
||||
if (!_GrowFileTreeChildren(pTree, pTree->numChildren + 1)) return false;
|
||||
memcpy(&pTree->children[pTree->numChildren], &child, sizeof(child));
|
||||
pTree->numChildren += 1;
|
||||
|
||||
if (!ChangeDir("..")) return false;
|
||||
} else {
|
||||
FntSubEntry *entry = malloc(sizeof(FntSubEntry) + nameLength);
|
||||
if (entry == NULL) FATAL("Failed to allocate FNT sub entry for file '%s'\n", name);
|
||||
entry->isSubdir = false;
|
||||
entry->length = nameLength;
|
||||
memcpy(entry->name, name, nameLength);
|
||||
|
||||
FileTree child;
|
||||
child.children = NULL;
|
||||
child.numChildren = 0;
|
||||
child.maxChildren = 0;
|
||||
child.firstFileId = 0;
|
||||
child.addedToFat = false;
|
||||
child.entry = entry;
|
||||
if (!_GrowFileTreeChildren(pTree, pTree->numChildren + 1)) return false;
|
||||
memcpy(&pTree->children[pTree->numChildren], &child, sizeof(child));
|
||||
pTree->numChildren += 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MakeFileTree(FileTree *pTree) {
|
||||
FileTree tree;
|
||||
tree.children = NULL;
|
||||
tree.numChildren = 0;
|
||||
tree.maxChildren = 0;
|
||||
tree.addedToFat = false;
|
||||
if (!_GrowFileTreeChildren(&tree, 64)) return false;
|
||||
tree.entry = NULL;
|
||||
|
||||
if (!IterFiles(_FileTreeFileCallback, &tree)) return false;
|
||||
memcpy(pTree, &tree, sizeof(tree));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FreeFileTree(FileTree *pTree) {
|
||||
for (size_t i = 0; i < pTree->numChildren; ++i) {
|
||||
if (!FreeFileTree(&pTree->children[i])) return false;
|
||||
}
|
||||
if (pTree->children != NULL) {
|
||||
free(pTree->children);
|
||||
pTree->children = NULL;
|
||||
}
|
||||
pTree->numChildren = 0;
|
||||
pTree->maxChildren = 0;
|
||||
pTree->addedToFat = false;
|
||||
if (pTree->entry != NULL) {
|
||||
free(pTree->entry);
|
||||
pTree->entry = NULL;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int CompareFileTreeNormal(const void *a, const void *b) {
|
||||
FileTree *treeA = (FileTree*) a;
|
||||
FileTree *treeB = (FileTree*) b;
|
||||
|
||||
// Files before directories
|
||||
bool dirA = treeA->entry == NULL || treeA->entry->isSubdir;
|
||||
bool dirB = treeB->entry == NULL || treeB->entry->isSubdir;
|
||||
if (dirA && !dirB) return 1;
|
||||
if (!dirA && dirB) return -1;
|
||||
|
||||
size_t lenA = treeA->entry->length;
|
||||
size_t lenB = treeB->entry->length;
|
||||
size_t minSize = (lenA < lenB) ? lenA : lenB;
|
||||
|
||||
// Alphabetic name order
|
||||
const char *nameA = treeA->entry->name;
|
||||
const char *nameB = treeB->entry->name;
|
||||
for (size_t i = 0; i < minSize; ++i) {
|
||||
const char chA = tolower(nameA[i]);
|
||||
const char chB = tolower(nameB[i]);
|
||||
if (chA != chB) return chA - chB;
|
||||
}
|
||||
|
||||
// Shortest name first
|
||||
if (lenA < lenB) return -1;
|
||||
if (lenA > lenB) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CompareFileTreeAscii(const void *a, const void *b) {
|
||||
FileTree *treeA = (FileTree*) a;
|
||||
FileTree *treeB = (FileTree*) b;
|
||||
|
||||
size_t lenA = treeA->entry->length;
|
||||
size_t lenB = treeB->entry->length;
|
||||
size_t minSize = (lenA < lenB) ? lenA : lenB;
|
||||
|
||||
// ASCII order
|
||||
int cmp = strncmp(treeA->entry->name, treeB->entry->name, minSize);
|
||||
if (cmp != 0) return cmp;
|
||||
|
||||
// Shortest name first
|
||||
if (lenA < lenB) return -1;
|
||||
if (lenA > lenB) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool SortFileTree(FileTree *pTree, int (*compare)(const void*, const void*)) {
|
||||
if (pTree->numChildren <= 1) return true;
|
||||
FileTree tree;
|
||||
memcpy(&tree, pTree, sizeof(tree));
|
||||
|
||||
qsort(tree.children, tree.numChildren, sizeof(*tree.children), compare);
|
||||
for (size_t i = 0; i < tree.numChildren; ++i) {
|
||||
if (!SortFileTree(&tree.children[i], compare)) return false;
|
||||
}
|
||||
|
||||
memcpy(pTree, &tree, sizeof(tree));
|
||||
return true;
|
||||
}
|
||||
|
||||
FileTree* FindSubTree(FileTree *tree, const char *path) {
|
||||
if (*path == '/') ++path;
|
||||
const char *nextPath = strchr(path, '/');
|
||||
if (nextPath == NULL) return tree;
|
||||
|
||||
size_t dirNameLen = nextPath - path;
|
||||
for (size_t i = 0; i < tree->numChildren; ++i) {
|
||||
FileTree *child = &tree->children[i];
|
||||
if (child->entry->length != dirNameLen) continue;
|
||||
if (strncmp(child->entry->name, path, dirNameLen) != 0) continue;
|
||||
return FindSubTree(child, nextPath);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,42 +0,0 @@
|
||||
#ifndef __PH_H
|
||||
#define __PH_H
|
||||
|
||||
#define TITLE "ZELDA_DS:PH"
|
||||
#define GAMECODE_PREFIX "AZE"
|
||||
|
||||
#define BANNER_SUBDIR "banner"
|
||||
#define ICON_BITMAP_FILE BANNER_SUBDIR "/icon.bin"
|
||||
#define ICON_PALETTE_FILE BANNER_SUBDIR "/icon.pal"
|
||||
#define TITLE_JAP_FILE BANNER_SUBDIR "/title_jap.txt"
|
||||
#define TITLE_ENG_FILE BANNER_SUBDIR "/title_eng.txt"
|
||||
#define TITLE_FRE_FILE BANNER_SUBDIR "/title_fre.txt"
|
||||
#define TITLE_GER_FILE BANNER_SUBDIR "/title_ger.txt"
|
||||
#define TITLE_ITA_FILE BANNER_SUBDIR "/title_ita.txt"
|
||||
#define TITLE_SPA_FILE BANNER_SUBDIR "/title_spa.txt"
|
||||
|
||||
#define ASSETS_SUBDIR "assets"
|
||||
|
||||
#define ARM9_PROGRAM_FILE "arm9.lz"
|
||||
#define ARM9_FOOTER_FILE "arm9_footer.bin"
|
||||
#define ARM9_METADATA_FILE "arm9_metadata.bin"
|
||||
#define ARM9_OVERLAY_TABLE_FILE "arm9_ovt.bin"
|
||||
#define ARM9_OVERLAY_DATA_FILE "arm9_ovdata.bin"
|
||||
#define OVERLAYS_SUBDIR "overlays"
|
||||
|
||||
#define ARM7_PROGRAM_FILE "arm7.bin"
|
||||
|
||||
typedef enum {
|
||||
REGION_JAPAN = 'J',
|
||||
REGION_USA = 'E',
|
||||
REGION_EUROPE = 'P',
|
||||
} Region;
|
||||
|
||||
typedef struct {
|
||||
Region region;
|
||||
} BuildInfo;
|
||||
|
||||
typedef struct {
|
||||
uint32_t fileId;
|
||||
} OverlayData;
|
||||
|
||||
#endif
|
||||
-124
@@ -1,124 +0,0 @@
|
||||
#ifndef __ROM_H
|
||||
#define __ROM_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define SECURE_AREA_SIZE 0x4000
|
||||
|
||||
typedef struct {
|
||||
/* 00 */ uint32_t offset;
|
||||
/* 04 */ uint32_t entry;
|
||||
/* 08 */ uint32_t baseAddr;
|
||||
/* 0c */ uint32_t size;
|
||||
/* 10 */
|
||||
} ProgramOffset;
|
||||
|
||||
typedef struct {
|
||||
/* 0 */ uint32_t offset;
|
||||
/* 4 */ uint32_t size;
|
||||
/* 8 */
|
||||
} TableOffset;
|
||||
|
||||
typedef struct {
|
||||
/* 0000 */ char title[0xc];
|
||||
/* 000c */ char gamecode[0x4];
|
||||
/* 0010 */ char makercode[0x2];
|
||||
/* 0012 */ uint8_t unitcode;
|
||||
/* 0013 */ uint8_t encSeedSelect;
|
||||
/* 0014 */ uint8_t capacity;
|
||||
/* 0015 */ uint8_t reserved0[0x8];
|
||||
/* 001d */ uint8_t dsRegion;
|
||||
/* 001e */ uint8_t romVersion;
|
||||
/* 001f */ uint8_t autostart;
|
||||
/* 0020 */ ProgramOffset arm9;
|
||||
/* 0030 */ ProgramOffset arm7;
|
||||
/* 0040 */ TableOffset fileNames;
|
||||
/* 0048 */ TableOffset fileAllocs;
|
||||
/* 0050 */ TableOffset arm9Overlays;
|
||||
/* 0058 */ TableOffset arm7Overlays;
|
||||
/* 0060 */ uint32_t normalCmdSetting;
|
||||
/* 0064 */ uint32_t key1CmdSetting;
|
||||
/* 0068 */ uint32_t bannerOffset;
|
||||
/* 006c */ uint16_t secureAreaCrc;
|
||||
/* 006e */ uint16_t secureAreaDelay;
|
||||
/* 0070 */ uint32_t arm9AutoloadCallback;
|
||||
/* 0074 */ uint32_t arm7AutoloadCallback;
|
||||
/* 0078 */ uint64_t secureAreaDisable;
|
||||
/* 0080 */ uint32_t romSize;
|
||||
/* 0084 */ uint32_t headerSize;
|
||||
/* 0088 */ uint32_t autoloadBlockInfosOffset;
|
||||
/* 008c */ uint8_t reserved1[0x8];
|
||||
/* 0094 */ uint16_t romEnd;
|
||||
/* 0096 */ uint16_t rwEnd;
|
||||
/* 0098 */ uint8_t reserved2[0x18];
|
||||
/* 00b0 */ uint8_t reserved3[0x10];
|
||||
/* 00c0 */ uint8_t logo[0x9c];
|
||||
/* 015c */ uint16_t logoCrc;
|
||||
/* 015e */ uint16_t headerCrc;
|
||||
/* 0160 */ uint32_t debugRomOffset;
|
||||
/* 0164 */ uint32_t debugSize;
|
||||
/* 0168 */ uint32_t debugRamAddr;
|
||||
/* 016c */ uint8_t reserved4[0x4];
|
||||
/* 0170 */ uint8_t reserved5[0x90];
|
||||
/* 0200 */ uint8_t reserved6[0xe00];
|
||||
/* 1000 */ uint8_t reserved7[0x3000];
|
||||
/* 4000 */
|
||||
} Header;
|
||||
|
||||
typedef struct {
|
||||
/* 00 */ uint32_t id;
|
||||
/* 04 */ uint32_t baseAddr;
|
||||
/* 08 */ uint32_t textSize;
|
||||
/* 0c */ uint32_t bssSize;
|
||||
/* 10 */ uint32_t sinitStart;
|
||||
/* 14 */ uint32_t sinitEnd;
|
||||
/* 18 */ uint32_t fileId;
|
||||
/* 1c */ uint32_t compressedSize : 24;
|
||||
/* 1f */ uint32_t isCompressed : 8;
|
||||
/* 20 */
|
||||
} OverlayEntry;
|
||||
|
||||
typedef struct {
|
||||
/* 0000 */ uint16_t version;
|
||||
/* 0002 */ uint16_t crc;
|
||||
/* 0004 */ uint8_t reserved0[0x1c];
|
||||
/* 0020 */ uint8_t iconBitmap[0x200];
|
||||
/* 0220 */ uint16_t iconPalette[0x10];
|
||||
/* 0240 */ wchar_t japaneseTitle[0x80];
|
||||
/* 0340 */ wchar_t englishTitle[0x80];
|
||||
/* 0440 */ wchar_t frenchTitle[0x80];
|
||||
/* 0540 */ wchar_t germanTitle[0x80];
|
||||
/* 0640 */ wchar_t italianTitle[0x80];
|
||||
/* 0740 */ wchar_t spanishTitle[0x80];
|
||||
/* 0540 */
|
||||
} Banner;
|
||||
|
||||
typedef struct {
|
||||
/* 0 */ uint32_t subtableOffset;
|
||||
/* 4 */ uint16_t firstFile;
|
||||
/* 6 */ uint16_t parentId;
|
||||
/* 8 */
|
||||
} FntEntry;
|
||||
|
||||
typedef struct {
|
||||
/* 0.0 */ uint8_t length : 7;
|
||||
/* 0.7 */ bool isSubdir : 1;
|
||||
/* 1.0 */ char name[];
|
||||
// If isSubdir
|
||||
/* 1.0 + length */ // uint16_t subdirId;
|
||||
/* 1.0 + length + 2 */
|
||||
// Else
|
||||
/* 1.0 + length */
|
||||
} FntSubEntry;
|
||||
|
||||
#define READ_SUBDIR_ID(entry) READ16(entry + sizeof(*entry) + entry->length);
|
||||
#define WRITE_SUBDIR_ID(entry,id) WRITE16(entry + sizeof(*entry) + entry->length, id)
|
||||
|
||||
typedef struct {
|
||||
/* 0 */ uint32_t startOffset;
|
||||
/* 4 */ uint32_t endOffset;
|
||||
} FatEntry;
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user