mirror of
https://github.com/zeldaret/ph
synced 2026-06-04 10:48:35 -04:00
Add extractrom and buildrom tools
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
extractrom
|
||||
extractrom.exe
|
||||
buildrom
|
||||
buildrom.exe
|
||||
@@ -0,0 +1,28 @@
|
||||
CC := gcc
|
||||
CFLAGS := -g
|
||||
|
||||
ifneq ($(DEBUG),1)
|
||||
CFLAGS += -O2 -DNDEBUG
|
||||
endif
|
||||
|
||||
ifeq ($(OS),Windows_NT)
|
||||
EXTRACTFILE := extractrom.exe
|
||||
BUILDFILE := buildrom.exe
|
||||
else
|
||||
EXTRACTFILE := extractrom
|
||||
BUILDFILE := buildrom
|
||||
endif
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(EXTRACTFILE) $(BUILDFILE)
|
||||
|
||||
clean:
|
||||
rm $(EXTRACTFILE)
|
||||
rm $(BUILDFILE)
|
||||
|
||||
$(EXTRACTFILE): extract.c
|
||||
$(CC) $(CFLAGS) -o $(EXTRACTFILE) extract.c
|
||||
|
||||
$(BUILDFILE): build.c
|
||||
$(CC) $(CFLAGS) -o $(BUILDFILE) build.c
|
||||
@@ -0,0 +1,454 @@
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "rom.h"
|
||||
#include "ph.h"
|
||||
#include "util.h"
|
||||
#include "files.h"
|
||||
|
||||
#define VERSION "1.0"
|
||||
|
||||
#define BUFFER_SIZE 1024 * 1024
|
||||
uint8_t *readBuffer = NULL;
|
||||
|
||||
#define MAX_DIR_SIZE 256
|
||||
#define INITIAL_SUBTABLE_SIZE 1024 * 1024
|
||||
|
||||
const uint8_t logo[] = {
|
||||
0x24, 0xff, 0xae, 0x51, 0x69, 0x9a, 0xa2, 0x21, 0x3d, 0x84, 0x82, 0x0a, 0x84, 0xe4, 0x09, 0xad,
|
||||
0x11, 0x24, 0x8b, 0x98, 0xc0, 0x81, 0x7f, 0x21, 0xa3, 0x52, 0xbe, 0x19, 0x93, 0x09, 0xce, 0x20,
|
||||
0x10, 0x46, 0x4a, 0x4a, 0xf8, 0x27, 0x31, 0xec, 0x58, 0xc7, 0xe8, 0x33, 0x82, 0xe3, 0xce, 0xbf,
|
||||
0x85, 0xf4, 0xdf, 0x94, 0xce, 0x4b, 0x09, 0xc1, 0x94, 0x56, 0x8a, 0xc0, 0x13, 0x72, 0xa7, 0xfc,
|
||||
0x9f, 0x84, 0x4d, 0x73, 0xa3, 0xca, 0x9a, 0x61, 0x58, 0x97, 0xa3, 0x27, 0xfc, 0x03, 0x98, 0x76,
|
||||
0x23, 0x1d, 0xc7, 0x61, 0x03, 0x04, 0xae, 0x56, 0xbf, 0x38, 0x84, 0x00, 0x40, 0xa7, 0x0e, 0xfd,
|
||||
0xff, 0x52, 0xfe, 0x03, 0x6f, 0x95, 0x30, 0xf1, 0x97, 0xfb, 0xc0, 0x85, 0x60, 0xd6, 0x80, 0x25,
|
||||
0xa9, 0x63, 0xbe, 0x03, 0x01, 0x4e, 0x38, 0xe2, 0xf9, 0xa2, 0x34, 0xff, 0xbb, 0x3e, 0x03, 0x44,
|
||||
0x78, 0x00, 0x90, 0xcb, 0x88, 0x11, 0x3a, 0x94, 0x65, 0xc0, 0x7c, 0x63, 0x87, 0xf0, 0x3c, 0xaf,
|
||||
0xd6, 0x25, 0xe4, 0x8b, 0x38, 0x0a, 0xac, 0x72, 0x21, 0xd4, 0xf8, 0x07,
|
||||
};
|
||||
|
||||
uint16_t crcTable[0x100];
|
||||
void GenerateCrcTable() {
|
||||
uint16_t polynomial = 0x2f15;
|
||||
for (size_t i = 0; i < 0x100; ++i) {
|
||||
uint16_t value = i;
|
||||
for (size_t j = 0; j < 8; ++j) {
|
||||
if (value & 1) value = polynomial ^ (value >> 1);
|
||||
else value >>= 1;
|
||||
}
|
||||
crcTable[i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t Crc(const void *buf, size_t size) {
|
||||
uint16_t crc = 0xffff;
|
||||
const uint8_t *data = buf;
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
crc = crcTable[(crc & 0xff) ^ data[i]] ^ (crc >> 8);
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
void InitHeader(Header *pHeader, const BuildInfo *info) {
|
||||
memcpy(&pHeader->title, TITLE, sizeof(pHeader->title));
|
||||
memcpy(&pHeader->gamecode, GAMECODE_PREFIX, 3);
|
||||
pHeader->gamecode[3] = info->region;
|
||||
memcpy(&pHeader->makercode, "01", sizeof(pHeader->makercode));
|
||||
|
||||
pHeader->unitcode = 0;
|
||||
pHeader->encSeedSelect = 0;
|
||||
pHeader->capacity = 0; // will be set
|
||||
memset(&pHeader->reserved0, 0, sizeof(pHeader->reserved0));
|
||||
pHeader->dsRegion = 0;
|
||||
pHeader->romVersion = 0;
|
||||
pHeader->autostart = 0;
|
||||
|
||||
pHeader->arm9.offset = 0; // will be set after header
|
||||
pHeader->arm9.entry = 0x2000800; // TODO: Get from linker
|
||||
pHeader->arm9.baseAddr = 0x2000000; // TODO: Get from linker
|
||||
pHeader->arm9.size = 0; // TODO: Get from arm9.lz
|
||||
|
||||
pHeader->arm7.offset = 0; // will be 256-aligned after ARM9 overlay files
|
||||
pHeader->arm7.entry = 0x2380000;
|
||||
pHeader->arm7.baseAddr = 0x2380000;
|
||||
pHeader->arm7.size = 0; // TODO: Get from arm7.bin
|
||||
|
||||
pHeader->fileNames.offset = 0; // will be 256-aligned after ARM7 program
|
||||
pHeader->fileNames.size = 0; // will be set
|
||||
pHeader->fileAllocs.offset = 0; // will be 256-aligned after file name table
|
||||
pHeader->fileAllocs.size = 0; // will be set
|
||||
pHeader->arm9Overlays.offset = 0; // will be 256-aligned after ARM9 program
|
||||
pHeader->arm9Overlays.size = 0; // will be set
|
||||
pHeader->arm7Overlays.offset = 0;
|
||||
pHeader->arm7Overlays.size = 0;
|
||||
|
||||
pHeader->normalCmdSetting = 0x00416657;
|
||||
pHeader->key1CmdSetting = 0x081808f8;
|
||||
pHeader->bannerOffset = 0; // will be 256-aligned after file alloc table
|
||||
pHeader->secureAreaCrc = 0; // TODO: Calculate
|
||||
pHeader->secureAreaDelay = 0x0d7e;
|
||||
pHeader->arm9AutoloadList = 0; // TODO: Get from linker (always 2000a74)
|
||||
pHeader->arm7AutoloadList = 0x2380158;
|
||||
pHeader->secureAreaDisable = 0;
|
||||
pHeader->romSize = 0; // Will be set
|
||||
pHeader->headerSize = sizeof(Header);
|
||||
pHeader->autoloadParamsOffset = 0; // TODO: Get from linker (always 4b64, see 2000b64)
|
||||
memset(&pHeader->reserved1, 0, sizeof(pHeader->reserved1));
|
||||
pHeader->romEnd = 0;
|
||||
pHeader->rwEnd = 0;
|
||||
memset(&pHeader->reserved2, 0, sizeof(pHeader->reserved2));
|
||||
memset(&pHeader->reserved3, 0, sizeof(pHeader->reserved3));
|
||||
memcpy(&pHeader->logo, logo, sizeof(pHeader->logo));
|
||||
pHeader->logoCrc = 0xcf56;
|
||||
pHeader->headerCrc = 0; // Will be set
|
||||
pHeader->debugRomOffset = 0;
|
||||
pHeader->debugSize = 0;
|
||||
pHeader->debugRamAddr = 0;
|
||||
memset(&pHeader->reserved4, 0, sizeof(pHeader->reserved4));
|
||||
memset(&pHeader->reserved5, 0, sizeof(pHeader->reserved5));
|
||||
memset(&pHeader->reserved6, 0, sizeof(pHeader->reserved6));
|
||||
memset(&pHeader->reserved7, 0, sizeof(pHeader->reserved7));
|
||||
}
|
||||
|
||||
bool AppendFile(FILE *fpRom, const char *filePath, size_t *pAddress, uint32_t *pFileSize) {
|
||||
assert(readBuffer != NULL);
|
||||
|
||||
FILE *fp = fopen(filePath, "rb");
|
||||
if (fp == NULL) FATAL("Failed to open file '%s'\n", filePath);
|
||||
fseek(fp, 0, SEEK_END);
|
||||
size_t size = ftell(fp);
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
|
||||
size_t bytesWritten = 0;
|
||||
while (bytesWritten < size) {
|
||||
size_t bytesRead = fread(readBuffer, 1, BUFFER_SIZE, fp);
|
||||
if (bytesRead == 0) FATAL("Failed to read from file '%s'\n", filePath);
|
||||
if (fwrite(readBuffer, bytesRead, 1, fpRom) != 1) FATAL("Failed to write file '%s' to output ROM\n", filePath);
|
||||
bytesWritten += bytesRead;
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
*pAddress += size;
|
||||
if (pFileSize != NULL) *pFileSize = size;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Align(size_t alignment, FILE *fpRom, size_t *pAddress) {
|
||||
assert((alignment & (alignment - 1)) == 0);
|
||||
|
||||
size_t mask = alignment - 1;
|
||||
size_t address = ftell(fpRom);
|
||||
size_t nextAddr = (address + mask) & ~mask;
|
||||
while (address < nextAddr) {
|
||||
if (fputc(0xff, fpRom) == -1) FATAL("Failed to pad output ROM at address 0x%x\n", address);
|
||||
address += 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteArm9Overlays(FILE *fpRom, size_t *pAddress, size_t *pNumOverlays) {
|
||||
size_t address = *pAddress;
|
||||
uint32_t ovNum = 0;
|
||||
char fileName[32];
|
||||
|
||||
if (chdir(OVERLAYS_SUBDIR) != 0) FATAL("Failed to enter overlays directory '" OVERLAYS_SUBDIR "'\n");
|
||||
|
||||
while (true) {
|
||||
sprintf(fileName, "ov%02d.lz", ovNum);
|
||||
if (!Align(256, fpRom, &address)) return false;
|
||||
// TODO (aetias): Store start and end address in FAT
|
||||
if (!AppendFile(fpRom, fileName, &address, NULL)) return false;
|
||||
}
|
||||
|
||||
if (chdir("..") != 0) FATAL("Failed to leave overlays directory '" OVERLAYS_SUBDIR "'\n");
|
||||
|
||||
*pAddress = address;
|
||||
*pNumOverlays = ovNum;
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uint16_t tableSize;
|
||||
uint16_t nextFileId;
|
||||
uint16_t parentId;
|
||||
|
||||
uint8_t *subtable;
|
||||
size_t subtableSize;
|
||||
size_t subtableMax;
|
||||
} FntContext;
|
||||
|
||||
bool GrowFntSubtable(FntContext *pContext, size_t growSize) {
|
||||
FntContext ctx;
|
||||
memcpy(&ctx, pContext, sizeof(ctx));
|
||||
|
||||
if (ctx.subtableSize + growSize < ctx.subtableMax) return true;
|
||||
while (ctx.subtableSize + growSize >= ctx.subtableMax) {
|
||||
ctx.subtableMax *= 2;
|
||||
}
|
||||
|
||||
uint8_t *newTable = realloc(ctx.subtable, ctx.subtableMax);
|
||||
if (newTable == NULL) FATAL("Failed to reallocate FNT subtable to %d bytes\n", ctx.subtableMax);
|
||||
ctx.subtable = newTable;
|
||||
|
||||
memcpy(pContext, &ctx, sizeof(ctx));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteFntSubtable(FILE *fpRom, size_t *pAddress, FntContext *pContext) {
|
||||
size_t address = *pAddress;
|
||||
FntSubEntry *entries[MAX_DIR_SIZE];
|
||||
size_t numEntries;
|
||||
|
||||
FntContext ctx;
|
||||
memcpy(&ctx, pContext, sizeof(ctx));
|
||||
size_t subtableStart = ctx.subtableSize;
|
||||
|
||||
if (!GetFiles(entries, MAX_DIR_SIZE, &numEntries)) return false;
|
||||
qsort(entries, numEntries, sizeof(FntSubEntry*), CompareFnt);
|
||||
|
||||
// Create initial subtable entries
|
||||
size_t numFiles = 0;
|
||||
for (size_t i = 0; i < numEntries; ++i) {
|
||||
FntSubEntry *entry = entries[i];
|
||||
if (!entry->isSubdir) numFiles += 1;
|
||||
|
||||
size_t entrySize = sizeof(*entry) + entry->length + (entry->isSubdir ? 2 : 0);
|
||||
if (!GrowFntSubtable(&ctx, entrySize)) return false;
|
||||
|
||||
FntSubEntry *dest = ctx.subtable + ctx.subtableSize;
|
||||
memcpy(dest, entry, entrySize);
|
||||
ctx.subtableSize += entrySize;
|
||||
}
|
||||
|
||||
if (!GrowFntSubtable(&ctx, 1)) return false;
|
||||
ctx.subtable[ctx.subtableSize] = 0; // End of subtable
|
||||
ctx.subtableSize += 1;
|
||||
|
||||
// Recurse child directories
|
||||
for (size_t i = 0; i < numEntries; ++i) {
|
||||
FntSubEntry *entry = entries[i];
|
||||
if (!entry->isSubdir) continue;
|
||||
uint16_t subdirId = 0xf000 | ctx.tableSize;
|
||||
WRITE_SUBDIR_ID(entry, subdirId);
|
||||
FntEntry mainEntry;
|
||||
mainEntry.subtableOffset = ctx.subtableSize; // will add main table length later
|
||||
mainEntry.firstFile = ctx.nextFileId;
|
||||
mainEntry.parentId = ctx.parentId;
|
||||
if (fwrite(&mainEntry, sizeof(mainEntry), 1, fpRom) != 1) {
|
||||
FATAL("Failed to write FNT entry for directory '%.*s'\n", entry->length, entry->name);
|
||||
}
|
||||
address += sizeof(mainEntry);
|
||||
|
||||
ctx.nextFileId += numFiles;
|
||||
uint16_t oldParentId = ctx.parentId;
|
||||
ctx.parentId = subdirId;
|
||||
|
||||
char name[128];
|
||||
strncpy(name, entry->name, entry->length);
|
||||
if (chdir(name) != 0) FATAL("Failed to enter assets subdirectory '%s'\n", name);
|
||||
if (!WriteFntSubtable(fpRom, &address, &ctx)) return false;
|
||||
if (chdir("..") != 0) FATAL("Failed to leave assets subdirectory '%s'\n", name);
|
||||
|
||||
ctx.parentId = oldParentId;
|
||||
}
|
||||
|
||||
// Update subdir IDs
|
||||
size_t subtableOffset = 0;
|
||||
for (size_t i = 0; i < numEntries; ++i) {
|
||||
FntSubEntry *entry = entries[i];
|
||||
size_t entrySize = sizeof(*entry) + entry->length + (entry->isSubdir ? 2 : 0);
|
||||
memcpy(ctx.subtable + subtableStart + subtableOffset, entry, entrySize);
|
||||
subtableOffset += entrySize;
|
||||
}
|
||||
|
||||
FreeFiles(entries, numEntries);
|
||||
|
||||
memcpy(pContext, &ctx, sizeof(ctx));
|
||||
*pAddress = address;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteFnt(FILE *fpRom, size_t *pAddress, size_t firstFileId) {
|
||||
size_t address = *pAddress;
|
||||
|
||||
FntContext ctx;
|
||||
ctx.tableSize = 0;
|
||||
ctx.nextFileId = firstFileId;
|
||||
ctx.parentId = 0xf000; // root directory
|
||||
ctx.subtable = malloc(INITIAL_SUBTABLE_SIZE);
|
||||
if (ctx.subtable == NULL) FATAL(stderr, "Failed to allocate FNT subtable\n");
|
||||
ctx.subtableSize = 0;
|
||||
ctx.subtableMax = INITIAL_SUBTABLE_SIZE;
|
||||
|
||||
if (!WriteFntSubtable(fpRom, &address, &ctx)) return false;
|
||||
free(ctx.subtable);
|
||||
|
||||
*pAddress = address;
|
||||
return true;
|
||||
}
|
||||
|
||||
void PrintUsage(const char *program) {
|
||||
printf(
|
||||
"buildrom " VERSION "\n"
|
||||
"\n"
|
||||
"Usage: %s -a ASSETSDIR -b BUILDDIR -r REGION -o OUTFILE\n"
|
||||
" -a ASSETSDIR\tAssets directory generated by extractrom\n"
|
||||
" -b BUILDDIR \tBuild directory generated by Makefile\n"
|
||||
" -r REGION \tJ = Japan, E = USA, P = Europe\n"
|
||||
" -o OUTFILE \tOutput ROM file\n",
|
||||
program
|
||||
);
|
||||
}
|
||||
|
||||
int main(int argc, const char **argv) {
|
||||
const char *program = argv[0];
|
||||
if (argc == 1) {
|
||||
PrintUsage(program);
|
||||
return 0;
|
||||
}
|
||||
const char *assetsDir = NULL;
|
||||
const char *buildDir = NULL;
|
||||
const char *romFile = NULL;
|
||||
Region region = 0;
|
||||
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;
|
||||
}
|
||||
romFile = argv[i];
|
||||
} else if (strcmp(argv[i], "-a") == 0) {
|
||||
if (++i >= argc) {
|
||||
fprintf(stderr, "Expected pathname after -a\n");
|
||||
return 1;
|
||||
}
|
||||
assetsDir = argv[i];
|
||||
} else if (strcmp(argv[i], "-b") == 0) {
|
||||
if (++i >= argc) {
|
||||
fprintf(stderr, "Expected pathname after -b\n");
|
||||
return 1;
|
||||
}
|
||||
buildDir = argv[i];
|
||||
} else if (strcmp(argv[i], "-r") == 0) {
|
||||
if (++i >= argc) {
|
||||
fprintf(stderr, "Expected region after -r\n");
|
||||
return 1;
|
||||
}
|
||||
if (strlen(argv[i]) != 1) {
|
||||
fprintf(stderr, "Region must be a single character\n");
|
||||
return 1;
|
||||
}
|
||||
region = argv[i][0];
|
||||
} else {
|
||||
fprintf(stderr, "Unknown option '%s'\n", argv[i]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (assetsDir == NULL) {
|
||||
PrintUsage(program);
|
||||
fprintf(stderr, "Please provide an assets directory, see usage above\n");
|
||||
return 1;
|
||||
}
|
||||
if (buildDir == NULL) {
|
||||
PrintUsage(program);
|
||||
fprintf(stderr, "Please provide a build directory, see usage above\n");
|
||||
return 1;
|
||||
}
|
||||
if (region != REGION_JAPAN && region != REGION_USA && region != REGION_EUROPE) {
|
||||
PrintUsage(program);
|
||||
fprintf(stderr, "Invalid region '%c', see usage above\n", region);
|
||||
return 1;
|
||||
}
|
||||
if (romFile == NULL) {
|
||||
PrintUsage(program);
|
||||
fprintf(stderr, "Please provide an output ROM file, see usage above\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
char rootDir[256];
|
||||
if (getcwd(rootDir, sizeof(rootDir)) == NULL) {
|
||||
fprintf(stderr, "Failed to get root directory\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
FILE *fpRom = fopen(romFile, "wb");
|
||||
if (fpRom == NULL) {
|
||||
fprintf(stderr, "Failed to open output ROM file '%s'\n", romFile);
|
||||
return 1;
|
||||
}
|
||||
|
||||
readBuffer = malloc(BUFFER_SIZE);
|
||||
if (readBuffer == NULL) {
|
||||
fprintf(stderr, "Failed to allocate read buffer to %d bytes\n", BUFFER_SIZE);
|
||||
return 1;
|
||||
}
|
||||
|
||||
GenerateCrcTable();
|
||||
|
||||
BuildInfo info;
|
||||
info.region = region;
|
||||
size_t address = 0;
|
||||
|
||||
Header header;
|
||||
InitHeader(&header, ®ion);
|
||||
|
||||
if (fwrite(&header, sizeof(header), 1, fpRom) != 1) {
|
||||
fprintf(stderr, "Failed to write NDS header\n");
|
||||
return 1;
|
||||
}
|
||||
address += sizeof(header);
|
||||
|
||||
if (chdir(buildDir) != 0) {
|
||||
fprintf(stderr, "Failed to enter build directory '%s'\n", buildDir);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!Align(256, fpRom, &address)) return 1;
|
||||
header.arm9.offset = address;
|
||||
if (!AppendFile(fpRom, ARM9_PROGRAM_FILE, &address, &header.arm9.size)) return 1;
|
||||
if (!AppendFile(fpRom, ARM9_FOOTER_FILE, &address, NULL)) return 1;
|
||||
|
||||
if (!Align(256, fpRom, &address)) return 1;
|
||||
header.arm9Overlays.offset = address;
|
||||
if (!AppendFile(fpRom, ARM9_OVERLAY_TABLE_FILE, &address, &header.arm9Overlays.size)) return 1;
|
||||
|
||||
size_t numOverlays = 0;
|
||||
if (!WriteArm9Overlays(fpRom, &address, &numOverlays)) return 1;
|
||||
|
||||
if (chdir(rootDir) != 0) {
|
||||
fprintf(stderr, "Failed to leave build directory '%s'\n", buildDir);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (chdir(assetsDir) != 0) {
|
||||
fprintf(stderr, "Failed to enter assets directory '%s'\n", assetsDir);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!Align(256, fpRom, &address)) return 1;
|
||||
header.arm7.offset = address;
|
||||
if (!AppendFile(fpRom, ARM7_PROGRAM_FILE, &address, &header.arm7.size)) return 1;
|
||||
|
||||
if (!Align(256, fpRom, &address)) return 1;
|
||||
header.fileNames.offset = address;
|
||||
if (!WriteFnt(fpRom, &address, numOverlays)) return 1;
|
||||
|
||||
if (!Align(256, fpRom, &address)) return 1;
|
||||
header.fileAllocs.offset = address;
|
||||
// TODO (aetias): Write initial FAT
|
||||
|
||||
// TODO (aetias): Write files
|
||||
|
||||
if (chdir(rootDir) != 0) {
|
||||
fprintf(stderr, "Failed to leave assets directory '%s'\n", assetsDir);
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t romEnd = 1 << (32 - __builtin_clz(address));
|
||||
if (!Align(romEnd, fpRom, &address)) return 1;
|
||||
|
||||
free(readBuffer);
|
||||
flose(fpRom);
|
||||
free(rootDir);
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
#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"
|
||||
|
||||
bool MakeDir(const char *dir) {
|
||||
struct stat dirStat;
|
||||
if (stat(dir, &dirStat) != 0) {
|
||||
if (mkdir(dir, 0777) != 0) FATAL("Failed to make directory '%s'\n", dir);
|
||||
return true;
|
||||
}
|
||||
if (!S_ISDIR(dirStat.st_mode)) FATAL("Could not make directory '%s' due to a file with the same name\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
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 ExtractTitle(const char *language, const char *file, const wchar_t *title, size_t titleSize) {
|
||||
size_t bufSize = 1024;
|
||||
char *buf = malloc(1024);
|
||||
if (buf == NULL) FATAL("Failed to allocate UTF-8 buffer for %s banner title\n", language);
|
||||
|
||||
FILE *fp = fopen(file, "wb");
|
||||
if (fp == NULL) FATAL("Failed to create %s banner title '%s'\n", language, file);
|
||||
size_t resultSize = 0;
|
||||
if (!WcharToUtf8((wchar_t*) title, titleSize, buf, bufSize, &resultSize)) return false;
|
||||
if (fputs(buf, fp) == -1) FATAL("Failed to write %s banner title '%s'\n", language, file);
|
||||
fclose(fp);
|
||||
|
||||
free(buf);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ExtractBanner(const Banner *pBanner, const BuildInfo *pInfo) {
|
||||
if (!MakeDir(BANNER_SUBDIR)) return 1;
|
||||
|
||||
FILE *fp;
|
||||
|
||||
fp = fopen(ICON_BITMAP_FILE, "wb");
|
||||
if (fp == NULL) FATAL("Failed to create banner icon bitmap '" ICON_BITMAP_FILE "'\n");
|
||||
if (fwrite(pBanner->iconBitmap, sizeof(pBanner->iconBitmap), 1, fp) != 1) {
|
||||
FATAL("Failed to write banner icon bitmap '" ICON_BITMAP_FILE "'\n");
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
fp = fopen(ICON_PALETTE_FILE, "wb");
|
||||
if (fp == NULL) FATAL("Failed to create banner icon palette '" ICON_PALETTE_FILE "'\n");
|
||||
if (fwrite(pBanner->iconPalette, sizeof(pBanner->iconPalette), 1, fp) != 1) {
|
||||
FATAL("Failed to write banner icon palette '" ICON_PALETTE_FILE "'\n");
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool ExtractAssets(const uint8_t *rom, const uint8_t *fatStart, const uint8_t *fntStart, const FntEntry *pFntEntry) {
|
||||
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) {
|
||||
printf("File '%s'\n", name);
|
||||
|
||||
const FatEntry *pFatEntry = (const FatEntry*) fatStart + fileId;
|
||||
size_t fileSize = pFatEntry->endOffset - pFatEntry->startOffset;
|
||||
const uint8_t *pFileBytes = rom + pFatEntry->startOffset;
|
||||
|
||||
FILE *fp = fopen(name, "wb");
|
||||
if (fp == NULL) FATAL("Failed to open assets file '%s'\n", name);
|
||||
if (fwrite(pFileBytes, fileSize, 1, fp) != 1) FATAL("Failed to write to assets file '%s'\n", name);
|
||||
fclose(fp);
|
||||
|
||||
subEntryAddr += sizeof(FntSubEntry) + pSubEntry->length;
|
||||
pSubEntry = (const FntSubEntry*) subEntryAddr;
|
||||
++fileId;
|
||||
continue;
|
||||
}
|
||||
|
||||
printf("Dir '%s'\n", name);
|
||||
if (!MakeDir(name)) return false;
|
||||
if (chdir(name) != 0) FATAL("Failed to enter assets subdirectory '%s'\n", name);
|
||||
|
||||
uint16_t subdirId = READ16(subEntryAddr + sizeof(FntSubEntry) + pSubEntry->length);
|
||||
uint16_t subdirIndex = subdirId & 0xfff;
|
||||
if (!ExtractAssets(rom, fatStart, fntStart, (FntEntry*) fntStart + subdirIndex)) return false;
|
||||
|
||||
printf("Leave '%s'\n", name);
|
||||
|
||||
if (chdir("..") != 0) FATAL("Failed to leave assets subdirectory '%s'\n", name);
|
||||
subEntryAddr += sizeof(FntSubEntry) + pSubEntry->length + sizeof(subdirId);
|
||||
pSubEntry = (const FntSubEntry*) subEntryAddr;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PrintUsage(const char *program) {
|
||||
printf(
|
||||
"extractrom " VERSION "\n"
|
||||
"\n"
|
||||
"Usage: %s -i ROMFILE -o OUTDIR\n"
|
||||
" -o OUTDIR \tDirectory to extract files to\n"
|
||||
" -i ROMFILE\tROM to extract from",
|
||||
program
|
||||
);
|
||||
}
|
||||
|
||||
int main(int argc, const char **argv) {
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
|
||||
FILE *fpRom = fopen(romFile, "rb");
|
||||
if (fpRom == NULL) {
|
||||
fprintf(stderr, "Failed to open input ROM '%s'\n", romFile);
|
||||
return 1;
|
||||
}
|
||||
fseek(fpRom, 0, SEEK_END);
|
||||
size_t romSize = ftell(fpRom);
|
||||
fseek(fpRom, 0, SEEK_SET);
|
||||
uint8_t *rom = malloc(romSize);
|
||||
if (rom == NULL) {
|
||||
fprintf(stderr, "Failed to allocate buffer for '%s'\n", romFile);
|
||||
return 1;
|
||||
}
|
||||
if (fread(rom, romSize, 1, fpRom) != 1) {
|
||||
fprintf(stderr, "Failed to read from '%s'\n", romFile);
|
||||
return 1;
|
||||
}
|
||||
fclose(fpRom);
|
||||
|
||||
Header *pHeader = (Header*) rom;
|
||||
BuildInfo info;
|
||||
if (!CheckRegion(pHeader, &info)) return 1;
|
||||
if (!MakeDir(outDir)) return 1;
|
||||
if (chdir(outDir) != 0) {
|
||||
fprintf(stderr, "Failed to enter output directory '%s'\n", outDir);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Banner *pBanner = (Banner*) (rom + pHeader->bannerOffset);
|
||||
if (!ExtractBanner(pBanner, &info)) return 1;
|
||||
|
||||
if (!MakeDir(ASSETS_SUBDIR)) return 1;
|
||||
if (chdir(ASSETS_SUBDIR) != 0) {
|
||||
fprintf(stderr, "Failed to enter assets directory '" ASSETS_SUBDIR "'\n");
|
||||
return 1;
|
||||
}
|
||||
const uint8_t *fntStart = rom + pHeader->fileNames.offset;
|
||||
const uint8_t *fatStart = rom + pHeader->fileAllocs.offset;
|
||||
if (!ExtractAssets(rom, fatStart, fntStart, (FntEntry*) fntStart)) return 1;
|
||||
if (chdir("..") != 0) {
|
||||
fprintf(stderr, "Failed to leave assets directory '" ASSETS_SUBDIR "'\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (chdir("..") != 0) {
|
||||
fprintf(stderr, "Failed to leave output directory '%s'\n", outDir);
|
||||
return 1;
|
||||
}
|
||||
|
||||
free(rom);
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
#ifndef __FILES_H
|
||||
#define __FILES_H
|
||||
|
||||
#include "util.h"
|
||||
#include "rom.h"
|
||||
|
||||
bool GetFiles(FntSubEntry **entries, size_t maxLength, size_t *pLength) {
|
||||
#ifdef _WIN32
|
||||
size_t length = 0;
|
||||
WIN32_FIND_DATA findData;
|
||||
HANDLE hFind = FindFirstFileA("*", &findData);
|
||||
if (hFind == INVALID_HANDLE_VALUE) FATAL("Failed to open directory to get files\n");
|
||||
do {
|
||||
if (length >= maxLength) FATAL("Max file entries surpassed\n");
|
||||
size_t nameLength = strlen(findData.cFileName);
|
||||
if (nameLength > 127) FATAL("File name '%s' longer than 127 characters\n", findData.cFileName);
|
||||
|
||||
bool isSubdir = (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
|
||||
size_t extraSize = isSubdir ? 2 : 0;
|
||||
FntSubEntry *entry = (FntSubEntry*) malloc(sizeof(FntSubEntry) + nameLength + extraSize);
|
||||
entry->isSubdir = isSubdir;
|
||||
entry->length = nameLength;
|
||||
memcpy(entry->name, findData.cFileName, nameLength);
|
||||
WRITE_SUBDIR_ID(entry, 0);
|
||||
|
||||
entries[length] = entry;
|
||||
length += 1;
|
||||
} while (FindNextFileA(hFind, &findData));
|
||||
FindClose(hFind);
|
||||
*pLength = length;
|
||||
return true;
|
||||
#elif __linux__
|
||||
size_t length = 0;
|
||||
DIR *dir = opendir(".");
|
||||
if (dir == NULL) FATAL("Failed to open directory to get files\n");
|
||||
struct dirent *dirent;
|
||||
while ((dirent = readdir(dir)) != NULL) {
|
||||
if (length >= maxLength) FATAL("Max file entries surpassed\n");
|
||||
size_t nameLength = strlen(dirent->d_name);
|
||||
if (nameLength > 127) FATAL("File name '%s' longer than 127 characters\n", dirent->d_name);
|
||||
|
||||
bool isSubdir = dirent->d_type == DT_DIR;
|
||||
size_t extraSize = isSubdir ? 2 : 0;
|
||||
FntSubEntry *entry = (FntSubEntry*) malloc(sizeof(FntSubEntry) + nameLength + extraSize);
|
||||
entry->isSubdir = isSubdir;
|
||||
entry->length = nameLength;
|
||||
memcpy(entry->name, findData.cFileName, nameLength);
|
||||
|
||||
entries[length] = entry;
|
||||
length += 1;
|
||||
}
|
||||
closedir(dir);
|
||||
*pLength = length;
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool FreeFiles(FntSubEntry **pEntries, size_t length) {
|
||||
for (size_t i = 0; i < length; ++i) free(pEntries[i]);
|
||||
}
|
||||
|
||||
int CompareFnt(const FntSubEntry *a, const FntSubEntry *b) {
|
||||
size_t minSize = ((a->length < b->length) ? a : b)->length;
|
||||
int cmp = strncmp(a->name, b->name, minSize);
|
||||
if (cmp != 0) return cmp;
|
||||
if (a->length < b->length) return -1;
|
||||
if (a->length > b->length) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,36 @@
|
||||
#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 "footer.bin"
|
||||
#define ARM9_OVERLAY_TABLE_FILE "arm9_ovt.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;
|
||||
|
||||
#endif
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
#ifndef __ROM_H
|
||||
#define __ROM_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
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 arm9AutoloadList;
|
||||
/* 0074 */ uint32_t arm7AutoloadList;
|
||||
/* 0078 */ uint64_t secureAreaDisable;
|
||||
/* 0080 */ uint32_t romSize;
|
||||
/* 0084 */ uint32_t headerSize;
|
||||
/* 0088 */ uint32_t autoloadParamsOffset;
|
||||
/* 008c */ uint8_t reserved1[0x8];
|
||||
/* 0094 */ uint32_t romEnd;
|
||||
/* 0096 */ uint32_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 {
|
||||
/* 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];
|
||||
/* 0440 */ wchar_t germanTitle[0x80];
|
||||
/* 0440 */ wchar_t italianTitle[0x80];
|
||||
/* 0440 */ 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
|
||||
@@ -0,0 +1,94 @@
|
||||
#ifndef __FS_H
|
||||
#define __FS_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define FATAL(...) do { fprintf(stderr, __VA_ARGS__); return false; } while (0);
|
||||
|
||||
#define WRITE16(buf,val) do { ((char*) buf)[0] = (val) & 0xFF; ((char*) buf)[1] = ((val) >> 8) & 0xFF; } while (0)
|
||||
#define WRITE24(buf,val) do { ((char*) buf)[0] = (val) & 0xFF; ((char*) buf)[1] = ((val) >> 8) & 0xFF; ((char*) buf)[2] = ((val) >> 16) & 0xFF; } while (0)
|
||||
#define WRITE32(buf,val) do { ((char*) buf)[0] = (val) & 0xFF; ((char*) buf)[1] = ((val) >> 8) & 0xFF; ((char*) buf)[2] = ((val) >> 16) & 0xFF; ((char*) buf)[3] = ((val) >> 24) & 0xFF; } while (0)
|
||||
#define READ16(buf) (((char*) buf)[0] | (((char*) buf)[1] << 8))
|
||||
#define READ24(buf) (((char*) buf)[0] | (((char*) buf)[1] << 8) | (((char*) buf)[2] << 16))
|
||||
#define READ32(buf) (((char*) buf)[0] | (((char*) buf)[1] << 8) | (((char*) buf)[2] << 16) | (((char*) buf)[3] << 24))
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <Windows.h>
|
||||
# include <sys/stat.h>
|
||||
# define mkdir(path, mode) mkdir(path)
|
||||
#elif __linux__
|
||||
# include <sys/stat.h>
|
||||
# include <iconv.h>
|
||||
# include <dirent.h>
|
||||
#else
|
||||
# error "Target platform not supported"
|
||||
#endif
|
||||
|
||||
bool WcharToUtf8(wchar_t *in, size_t inSize, char *out, size_t outSize, size_t *pResultSize) {
|
||||
#ifdef _WIN32
|
||||
size_t resultSize = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, in, inSize / sizeof(wchar_t), out, outSize, NULL, NULL);
|
||||
if (resultSize == 0) FATAL("Failed to convert to UTF-8\n");
|
||||
*pResultSize = resultSize;
|
||||
return true;
|
||||
#elif __linux__
|
||||
iconv_t convDesc = iconv_open("UTF-16", "UTF-8");
|
||||
if (convDesc == -1) FATAL("Failed to get conversion description to UTF-8\n");
|
||||
size_t remainingBytes = outSize;
|
||||
if (iconv(convDesc, &in, &inSize, &out, &remainingBytes) == -1) FATAL("Failed to convert to UTF-8\n");
|
||||
if (inSize > 0) FATAL("Some characters were not converted to UTF-8\n");
|
||||
*pResultSize = outSize - remainingBytes;
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
// typedef struct {
|
||||
// #ifdef _WIN32
|
||||
// WIN32_FIND_DATA findData;
|
||||
// HANDLE hFind;
|
||||
// bool done;
|
||||
// #elif __linux__
|
||||
// DIR *dir;
|
||||
// struct dirent *entry;
|
||||
// #endif
|
||||
// } DirContext;
|
||||
|
||||
// bool BeginDir(DirContext *ctx) {
|
||||
// #ifdef _WIN32
|
||||
// ctx->hFind = FindFirstFileA("*", &ctx->findData);
|
||||
// if (ctx->hFind == INVALID_HANDLE_VALUE) FATAL("Failed to begin walking directory\n");
|
||||
// return true;
|
||||
// #elif __linux__
|
||||
// ctx->dir = opendir(".");
|
||||
// if (ctx->dir == NULL) FATAL("Failed to begin walking directory\n");
|
||||
// ctx->entry = readdir(ctx->dir);
|
||||
// return true;
|
||||
// #endif
|
||||
// }
|
||||
|
||||
// bool NextFile(DirContext *ctx, char *pName, size_t nameSize, bool *pIsDir) {
|
||||
// #ifdef _WIN32
|
||||
// if (ctx->done) return false;
|
||||
// strncpy(pName, ctx->findData.cFileName, nameSize);
|
||||
// *pIsDir = (ctx->findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
|
||||
// if (!FindNextFileA(ctx->hFind, &ctx->findData)) ctx->done = true;
|
||||
// return true;
|
||||
// #elif __linux__
|
||||
// if (ctx->entry == NULL) return false;
|
||||
// strncpy(pName, ctx->entry.d_name, nameSize);
|
||||
// *pIsDir = ctx->entry.d_type == DT_DIR;
|
||||
// ctx->entry = readdir(ctx->dir);
|
||||
// return true;
|
||||
// #endif
|
||||
// }
|
||||
|
||||
// void EndDir(DirContext *ctx) {
|
||||
// #ifdef _WIN32
|
||||
// FindClose(ctx->hFind);
|
||||
// #elif __linux__
|
||||
// closedir(ctx->dir);
|
||||
// #endif
|
||||
// }
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user