Files
goldeneye-pc-port/port/src/romdata.c
T

1258 lines
47 KiB
C

/*
* ROM loading: reads the .z64 and maps it at the N64 cart address 0x10000000.
*
* Why the fixed address: every ROM asset symbol in the PC build is an ABSOLUTE
* cart address (port/src/romassets_<region>.s, generated by
* scripts/gen_romassets.py from ge007.ld + scripts/filelist.<r>.csv). Game
* code takes `&symbol` and DMAs via romCopy()/osPiStartDma() exactly as on
* hardware. If the ROM image is mapped at 0x10000000, those addresses are
* live host pointers: PI reads become plain memcpys (see libultra.c) and
* direct-read assets (fonts, image display lists) work unmodified.
*
* The N64 ROM header is GE-specific (src/rom_header.s): magic 0x80371240 at
* 0x00, ROM name "GOLDENEYE" at 0x20, cartridge ID "GE" at 0x3C, country byte
* at 0x3E ('E' US / 'P' EU / 'J' JP).
*
* STATUS: Phase 1 — load + validate + map. No segment patching needed: the
* generated symbols already carry the layout.
*/
#include <stdlib.h>
#include <string.h>
/* D38: <stdio.h>/<stdlib.h> resolve to the decomp's N64 stubs via the include
* path; declare the host functions this file uses. */
extern void *malloc(unsigned long long size);
extern int snprintf(char *str, size_t maxsize, const char *format, ...);
#include "platform.h"
#if defined(PLATFORM_WINDOWS)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#else
/* POSIX: mmap the ROM at the fixed cart address (mirrors the VirtualAlloc
* path). <sys/mman.h> is a host header the decomp include path does not
* shadow. */
#include <errno.h>
#include <sys/mman.h>
#endif
#include "system.h"
#include "fs.h"
#include "romdata.h"
#include "pcmodels.h"
#include "pccg.h"
#include "romconvert.h"
/* D50: struct font/fontchar layouts for the font-segment re-layout. Order
* matters (see pcmodels.c): ultra64.h must finish before bondtypes.h. */
#include <ultra64.h>
#include "bondtypes.h"
#define CART_BASE 0x10000000u
static u8 *rom = NULL; /* heap copy (fallback path) */
static u32 romSize = 0;
static int mappedAtCartBase = 0;
#if !defined(PLATFORM_WINDOWS)
static unsigned long long mappedLen = 0; /* munmap() needs the length */
#endif
/* D34 (docs/internals.md): PE image base, low 32 bits zero. On N64 the
* animation_data segment links at VMA 0, so game code computes record
* addresses as `(s32)&ANIM_DATA_x + ptr_animation_table` where &ANIM_DATA_x
* is a small segment offset. A PE image cannot be placed at a low address,
* so assets/animationtable_data.h (PC branch) defines ANIM_DATA_x as an
* lvalue at g_pc_animdata_base + offset, making (s32)&ANIM_DATA_x equal the
* offset exactly. Set once in romdataInit(); see also the D34 note in that
* header. */
unsigned char *g_pc_animdata_base = NULL;
/* Candidate ROM file names, in priority order. The decomp repo ships the
* base ROM at the repo root (baserom.<r>.z64); a user-supplied final ROM in
* data/ takes precedence. */
static const char *romFileCandidates(void)
{
#if GE007_IS_PAL
return "ge007.pal-final.z64|baserom.e.z64";
#else
return "ge007.ntsc-final.z64|baserom.u.z64";
#endif
}
static int romHeaderValid(const u8 *h, char *err, size_t errsz)
{
static const u8 magic[4] = { 0x80, 0x37, 0x12, 0x40 };
#if GE007_IS_PAL
const char country = 'P';
#else
const char country = 'E';
#endif
if (memcmp(h, magic, 4) != 0) {
snprintf(err, errsz, "bad N64 magic at 0x00 (got %02x%02x%02x%02x)",
h[0], h[1], h[2], h[3]);
return 0;
}
if (memcmp(h + 0x20, "GOLDENEYE", 9) != 0) {
snprintf(err, errsz, "ROM name at 0x20 is not GOLDENEYE");
return 0;
}
if (h[0x3C] != 'G' || h[0x3D] != 'E') {
snprintf(err, errsz, "cartridge ID at 0x3C is not GE");
return 0;
}
if (h[0x3E] != country) {
snprintf(err, errsz, "country byte at 0x3E is %c, expected %c",
h[0x3E], country);
return 0;
}
return 1;
}
static u32 romdataBswap32(u32 v); /* defined below; used by the D55 fixup */
/* D230: RAW16 wavetable samples are stored big-endian s16 in the ROM (the
* N64 CPU reads them that way), but the PC mixer's resampler consumes
* host-native (little-endian) s16 — so every RAW16 instrument plays as a
* byte-swapped buzz. ADPCM nibble data is byte-order agnostic, which is why
* only RAW16 ranges are affected. The wavetable sample segments stay in the
* cart map (alBnkfNew() points straight at them; only the bank structs are
* re-laid out, D37), so bswap16 those ranges in place once the map is live.
* The walk mirrors the ALBankFile layout romdataFixupAudioBank() uses. */
#define RAW16_MAX_RANGES 32
struct raw16Range { u32 start, end; };
static void romdataRaw16Swap(u8 *tbl, u32 tblSize, u32 base, s32 len,
struct raw16Range *done, int *ndone)
{
int i;
if (len <= 0 || base >= tblSize || (u32)len > tblSize - base) {
sysLogPrintf(LOG_ERROR, "romdataInit: D230 RAW16 wavetable out of "
"range (base 0x%X len %d, segment 0x%X bytes)",
base, len, tblSize);
return;
}
for (i = 0; i < *ndone; i++) {
/* Shared sample data (two instruments can point at the same range):
* swapping twice would undo the first swap. */
if (base >= done[i].start && base + (u32)len <= done[i].end)
return;
}
for (i = 0; i + 1 < len; i += 2) {
u8 t = tbl[base + i];
tbl[base + i] = tbl[base + i + 1];
tbl[base + i + 1] = t;
}
if (*ndone < RAW16_MAX_RANGES) {
done[*ndone].start = base;
done[*ndone].end = base + (u32)len;
(*ndone)++;
}
}
static void romdataRaw16Inst(const u8 *ctl, u32 ctlSize, u8 *tbl, u32 tblSize,
struct raw16Range *done, int *ndone, u32 ioff)
{
s16 scount;
u32 k;
if (!ioff || ioff + 16 > ctlSize)
return;
scount = (s16)((ctl[ioff + 14] << 8) | ctl[ioff + 15]); /* soundCount */
if (scount < 0)
return;
for (k = 0; k < (u32)scount; k++) {
u32 soff, wt;
if (ioff + 16 + 4 * k + 4 > ctlSize)
break;
soff = ((u32)ctl[ioff + 16 + 4 * k] << 24) |
((u32)ctl[ioff + 17 + 4 * k] << 16) |
((u32)ctl[ioff + 18 + 4 * k] << 8) |
(u32)ctl[ioff + 19 + 4 * k];
if (!soff || soff + 15 > ctlSize)
continue;
wt = ((u32)ctl[soff + 8] << 24) | ((u32)ctl[soff + 9] << 16) |
((u32)ctl[soff + 10] << 8) | (u32)ctl[soff + 11];
if (!wt || wt + 20 > ctlSize)
continue;
if (ctl[wt + 8] == 1 /* AL_RAW16_WAVE */) {
u32 base = ((u32)ctl[wt] << 24) | ((u32)ctl[wt + 1] << 16) |
((u32)ctl[wt + 2] << 8) | (u32)ctl[wt + 3];
s32 len = (s32)(((u32)ctl[wt + 4] << 24) |
((u32)ctl[wt + 5] << 16) |
((u32)ctl[wt + 6] << 8) | (u32)ctl[wt + 7]);
romdataRaw16Swap(tbl, tblSize, base, len, done, ndone);
}
}
}
static void romdataRaw16Walk(const u8 *ctl, u32 ctlSize, u8 *tbl, u32 tblSize,
struct raw16Range *done, int *ndone)
{
s16 i;
if (ctlSize < 4 || ((ctl[0] << 8) | ctl[1]) != 0x4231 /* 'B1' */)
return;
for (i = 0; i < (s16)((ctl[2] << 8) | ctl[3]); i++) {
u32 boff, j;
s16 icount;
if (4 + 4 * (u32)i + 4 > ctlSize)
break;
boff = ((u32)ctl[4 + 4 * i] << 24) | ((u32)ctl[5 + 4 * i] << 16) |
((u32)ctl[6 + 4 * i] << 8) | (u32)ctl[7 + 4 * i];
if (!boff || boff + 12 > ctlSize)
continue;
icount = (s16)((ctl[boff] << 8) | ctl[boff + 1]); /* instCount */
if (icount < 0)
continue;
romdataRaw16Inst(ctl, ctlSize, tbl, tblSize, done, ndone,
((u32)ctl[boff + 8] << 24) |
((u32)ctl[boff + 9] << 16) |
((u32)ctl[boff + 10] << 8) | (u32)ctl[boff + 11]);
for (j = 0; j < icount; j++) {
if (boff + 12 + 4 * (u32)j + 4 > ctlSize)
break;
romdataRaw16Inst(ctl, ctlSize, tbl, tblSize, done, ndone,
((u32)ctl[boff + 12 + 4 * j] << 24) |
((u32)ctl[boff + 13 + 4 * j] << 16) |
((u32)ctl[boff + 14 + 4 * j] << 8) |
(u32)ctl[boff + 15 + 4 * j]);
}
}
}
/* The caller has placed a writable region of (romSize + sidecars) bytes at
* CART_BASE. Populate it, load the sidecars, apply the D55 RLE-header fixup,
* and mark the ROM live. Shared by the Windows (VirtualAlloc) and POSIX (mmap)
* fixed-address paths so the post-map logic can never drift between them. */
static int romdataFinishCartMap(const char *tok, u8 *img,
u32 sideTotal, u32 cgTotal)
{
memcpy((void *)(uintptr_t)CART_BASE, img, romSize);
free(img);
mappedAtCartBase = 1;
sysLogPrintf(LOG_INFO, "romdataInit: %s (%u bytes) mapped at 0x%08X "
"(cart base)%s%s", tok, romSize, CART_BASE,
sideTotal ? ", + model sidecars" : "",
cgTotal ? ", + bg/stan sidecars" : "");
pcmodelsLoadSidecars(CART_BASE, romSize);
pccgLoadSidecars(CART_BASE + romSize + pcmodelsTotalSize());
/* D55: the RLE folder-menu background at `unknown2` has a big-endian w/h
* header that rle_expand_8bit() reads little-endian; match the N64 .data
* copy by swapping it in place (no-op if it already reads as LE). */
{
extern void *unknown2; /* absolute cart address */
u32 *hdr = (u32 *)&unknown2;
if ((u16)*hdr > 512 || (u16)(*hdr >> 16) > 512)
*hdr = romdataBswap32(*hdr);
}
/* D230: byte-swap the RAW16 wavetable sample ranges in both bank trees
* (BE s16 in the ROM -> host LE for the mixer's resampler). */
{
extern u32 _sfxctlSegmentRomStart;
extern u32 _sfxtblSegmentRomStart;
extern u32 _instrumentsctlSegmentRomStart;
extern u32 _instrumentstblSegmentRomStart;
struct raw16Range done[RAW16_MAX_RANGES];
int ndone = 0;
/* Each tbl segment runs to the end of the ROM; bound the swap range
* by that (the real segment is smaller, but base/len are validated
* against this anyway). */
romdataRaw16Walk((const u8 *)&_sfxctlSegmentRomStart,
(u32)&_sfxtblSegmentRomStart -
(u32)&_sfxctlSegmentRomStart,
(u8 *)&_sfxtblSegmentRomStart,
CART_BASE + romSize -
(u32)&_sfxtblSegmentRomStart,
done, &ndone);
romdataRaw16Walk((const u8 *)&_instrumentsctlSegmentRomStart,
(u32)&_instrumentstblSegmentRomStart -
(u32)&_instrumentsctlSegmentRomStart,
(u8 *)&_instrumentstblSegmentRomStart,
CART_BASE + romSize -
(u32)&_instrumentstblSegmentRomStart,
done, &ndone);
if (ndone)
sysLogPrintf(LOG_INFO, "romdataInit: D230 byte-swapped %d RAW16 "
"wavetable range(s) in place", ndone);
}
return 0;
}
int romdataInit(void)
{
char err[128] = "";
const char *list = romFileCandidates();
char listBuf[512];
strcpy(listBuf, list);
/* D34: derive the PE image base (any symbol's address with its low 32
* bits cleared) for the ANIM_DATA_* lvalues. Requires a 4GiB-aligned
* image base — always true for the default x64 PE base 0x140000000. */
{
static char probe;
uintptr_t p = (uintptr_t)&probe;
uintptr_t hi = p & ~(uintptr_t)0xFFFFFFFFu;
if (hi == 0) {
/* Low image base (Linux no-PIE loads at 0x400000). The ANIM_DATA_*
* lvalues are only ever address-taken — `(s32)&ANIM_DATA_x` yields
* the offset, never a dereference — so g_pc_animdata_base just has
* to be some 4GiB-aligned address whose low 32 bits are zero. */
hi = (uintptr_t)0x100000000ull;
}
g_pc_animdata_base = (u8 *)hi;
}
/* Try each candidate in the data dir, next to the exe, and in the CWD
* (the decomp repo ships baserom.<r>.z64 at the repo root). */
for (char *tok = strtok(listBuf, "|"); tok;
tok = strtok(NULL, "|")) {
static const char *locs[3] = { "$S/", "$E/", "./" };
int li;
FSFile *f = NULL;
char romRelPath[1024] = ""; /* locs[li] + tok of the ROM we opened */
for (li = 0; li < 3 && !f; li++) {
snprintf(romRelPath, sizeof(romRelPath), "%s%s", locs[li], tok);
f = fsOpen(sysResolvePath(romRelPath), "rb");
}
if (!f)
continue;
romSize = (u32)fsSize(f);
u8 *img = (u8 *)malloc(romSize ? romSize : 1);
int ok = img && fsRead(f, img, romSize) == (int32_t)romSize;
fsClose(f);
if (!ok) {
free(img);
continue;
}
if (romSize < 0x40 || !romHeaderValid(img, err, sizeof(err))) {
sysLogPrintf(LOG_WARNING, "romdataInit: %s rejected: %s",
tok, err);
free(img);
continue;
}
/* Drop-in ROM support: if the ROM-derived sidecars are missing, run
* the bundled frozen converter (ge007-convert) once to generate them.
* Without them the game crashes later in
* modelPromoteNodeOffsetsToPointers (D179), so abort boot cleanly. */
if (!romConvertEnsureSidecars(img, romRelPath)) {
free(img);
return -1;
}
/* D50: reserve room for the PC model sidecar image right after the
* ROM ([CART_BASE+romSize, ...)); it is loaded below and patched into
* file_resource_table after obInit. */
u32 sideTotal = pcmodelsReserveSize(img);
/* D69: the bg/stan sidecar image is appended right after the model
* sidecar extension. */
u32 cgTotal = pccgReserveSize(img);
/* Map at the cart address so absolute asset symbols are live. On
* failure both paths fall through to the heap copy below (degraded:
* anything that dereferences a cart address directly, rather than via
* romdataGetRom()/the PI shims, will read wrong memory). */
{
u32 maplen = romSize + sideTotal + cgTotal;
#if defined(PLATFORM_WINDOWS)
void *at = VirtualAlloc((LPVOID)CART_BASE, maplen,
MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
if (at == (void *)(uintptr_t)CART_BASE)
return romdataFinishCartMap(tok, img, sideTotal, cgTotal);
if (at)
VirtualFree(at, 0, MEM_RELEASE);
sysLogPrintf(LOG_WARNING, "romdataInit: could not reserve 0x%08X; "
"using heap copy — direct ROM reads will fail",
CART_BASE);
#else
/* POSIX: anonymous fixed-address mmap. MAP_FIXED_NOREPLACE (Linux
* 4.17+) fails instead of clobbering an existing mapping; where it
* is unavailable the plain hint is advisory and the == check below
* catches a relocated result. */
int flags = MAP_PRIVATE | MAP_ANONYMOUS;
#ifdef MAP_FIXED_NOREPLACE
flags |= MAP_FIXED_NOREPLACE;
#elif defined(PLATFORM_MACOS)
/* __PAGEZERO ends at CART_BASE in the macOS build, so this range
* is deliberately vacant. Darwin has no MAP_FIXED_NOREPLACE and
* otherwise treats CART_BASE only as a hint, relocating it. */
flags |= MAP_FIXED;
#endif
void *at = mmap((void *)(uintptr_t)CART_BASE, maplen,
PROT_READ | PROT_WRITE, flags, -1, 0);
int mapErrno = errno;
if (at != MAP_FAILED && at == (void *)(uintptr_t)CART_BASE) {
mappedLen = maplen;
return romdataFinishCartMap(tok, img, sideTotal, cgTotal);
}
if (at != MAP_FAILED)
munmap(at, maplen);
sysLogPrintf(LOG_WARNING, "romdataInit: could not map 0x%08X "
"(mmap result=%p errno=%d); using heap copy — direct "
"ROM reads will fail", CART_BASE, at, mapErrno);
#endif
}
rom = img;
return 0;
}
sysLogPrintf(LOG_ERROR, "romdataInit: no ROM found (tried %s in data/ "
"and exe dir)", list);
return -1;
}
void romdataDestroy(void)
{
if (mappedAtCartBase) {
#if defined(PLATFORM_WINDOWS)
VirtualFree((LPVOID)CART_BASE, 0, MEM_RELEASE);
#else
munmap((void *)(uintptr_t)CART_BASE, (size_t)mappedLen);
mappedLen = 0;
#endif
mappedAtCartBase = 0;
}
free(rom);
rom = NULL;
romSize = 0;
}
const u8 *romdataGetRom(void)
{
if (mappedAtCartBase)
return (const u8 *)(uintptr_t)CART_BASE;
return rom;
}
u32 romdataGetRomSize(void) { return romSize; }
/*
* Is this cart address inside the mapped ROM? Used by the PI shims to bounds-
* check DMA before touching host memory.
*/
int romdataCartAddrValid(u32 addr, u32 size)
{
if (!mappedAtCartBase || !romSize)
return 0;
if (addr < CART_BASE)
return 0;
u32 off = addr - CART_BASE;
/* D50/D69: the model + bg/stan sidecar images extend the valid cart
* region (concatenated: ROM, then pcmodels, then pccg). */
return off + size <= romSize + pcmodelsTotalSize() + pccgTotalSize();
}
const void *romdataMapVa(u32 va)
{
/* N64 RDRAM VAs (0x80000000+/0xA0000000+) have no PC meaning; only cart
* addresses are mapped. Kept for API parity with the PD port. */
(void)va;
return NULL;
}
/*
* D33 (docs/internals.md): the .z64 file stores structured multi-byte
* fields in big-endian byte order; bit-packed data streams are raw. No single
* uniform word transform exists (mixed field widths), so the animation_data
* segment is converted per field at load time, after romCopy() and before
* expand_ani_table_entries() reads/writes the fields as LE:
*
* 20-byte record at blob+off:
* +0x00 u32 entries-segment offset -> bswap32
* +0x04 u16 frame count -> bswap16
* +0x06 u8 angle bit width -> identity
* +0x07 u8 loop flag -> identity
* +0x08 u32 bitDescriptors blob offset -> bswap32
* +0x0C u16 bitsPerFrame root motion -> bswap16
* +0x0E u16 frame size in bits -> bswap16
* +0x10 u32 bitStream blob offset -> bswap32
* descriptor array at [bd, bs), 6-byte ModelAnimBitField records:
* +0 u16 bitOffset -> bswap16
* +2 u8 bitCount -> identity
* +3 u8 pad -> identity
* +4 u16 valueOffset -> bswap16
*
* The entries segment (per-frame joint angles) is left identity — it is
* identity-encoded bit-packed data, not consumed at boot.
*/
static u32 romdataBswap32(u32 v)
{
return ((v & 0x000000FFu) << 24) | ((v & 0x0000FF00u) << 8) |
((v & 0x00FF0000u) >> 8) | ((v & 0xFF000000u) >> 24);
}
/* The port/shim <stddef.h> is the N64 stub for C TUs (no offsetof); this is
* the equivalent null-cast idiom. */
#define ROMDATA_OFFSEXP(T, M) ((u32)&(((T *)0)->M))
static u16 romdataBswap16(u16 v)
{
return (u16)((v << 8) | (v >> 8));
}
static void romdataFixupAnimTable(const s32 *table, u8 *blob, u32 blobSize)
{
while (*table != 0) {
s32 off = *table++;
if (off == 1)
continue; /* null sentinel */
if (off < 0 || (u32)off + 20 > blobSize) {
sysLogPrintf(LOG_ERROR, "romdataFixupAnimationData: bad record offset 0x%X",
off);
continue;
}
u8 *rec = blob + off;
*(u32 *)(rec + 0x00) = romdataBswap32(*(const u32 *)(rec + 0x00));
*(u16 *)(rec + 0x04) = romdataBswap16(*(const u16 *)(rec + 0x04));
/* +0x06/+0x07 are u8: identity */
*(u32 *)(rec + 0x08) = romdataBswap32(*(const u32 *)(rec + 0x08));
*(u16 *)(rec + 0x0C) = romdataBswap16(*(const u16 *)(rec + 0x0C));
*(u16 *)(rec + 0x0E) = romdataBswap16(*(const u16 *)(rec + 0x0E));
*(u32 *)(rec + 0x10) = romdataBswap32(*(const u32 *)(rec + 0x10));
u32 bd = *(const u32 *)(rec + 0x08); /* descriptor array start */
u32 bs = *(const u32 *)(rec + 0x10); /* bitStream start */
if (bd == 0 && bs == 0)
continue; /* no descriptors/stream */
if (!(bd < bs && bs <= blobSize && ((bs - bd) % 6u) == 0u)) {
sysLogPrintf(LOG_ERROR, "romdataFixupAnimationData: bad descriptor range [0x%X, 0x%X)",
bd, bs);
continue;
}
for (u32 p = bd; p < bs; p += 6) {
*(u16 *)(blob + p) = romdataBswap16(*(const u16 *)(blob + p));
/* +2 bitCount, +3 pad are u8: identity */
*(u16 *)(blob + p + 4) = romdataBswap16(*(const u16 *)(blob + p + 4));
}
}
}
void romdataFixupAnimationData(u8 *blob, u32 blobSize,
const s32 *tableA, const s32 *tableB)
{
romdataFixupAnimTable(tableA, blob, blobSize);
if (tableB != tableA)
romdataFixupAnimTable(tableB, blob, blobSize);
}
/*
* D35 (docs/internals.md): the music sequence table segment stores its
* fields big-endian:
*
* header at blob+0 (RareALSeqBankFile):
* +0x0 u16 seqCount -> bswap16
* +0x2 u16 unk -> identity (unused)
* entries at blob+4, 8-byte RareALSeqData records:
* +0 u32 address offset -> bswap32
* +4 u16 uncompressed_len -> bswap16
* +6 u16 len -> bswap16
*
* Entries are decoded only as far as blobSize allows: the 0x10-byte header
* copy decodes the header plus entry 0, and the caller re-copies fresh BE
* bytes before the full-size call.
*/
void romdataFixupMusicSeqTable(u8 *blob, u32 blobSize)
{
if (blobSize < 4) {
sysLogPrintf(LOG_ERROR, "romdataFixupMusicSeqTable: blob too small (%u)",
blobSize);
return;
}
*(u16 *)(blob + 0) = romdataBswap16(*(const u16 *)(blob + 0)); /* seqCount */
u32 count = *(const u16 *)(blob + 0);
u32 maxEntries = (blobSize - 4) / 8;
if (count > maxEntries) {
/* Expected, not an error: music.c first decodes a 0x10-byte header
* probe (capacity 1) before re-copying the full table and calling
* again. Log at NOTE so it isn't mistaken for broken music data. */
sysLogPrintf(LOG_NOTE,
"romdataFixupMusicSeqTable: partial decode, seqCount %u > capacity %u (full table follows)",
count, maxEntries);
count = maxEntries;
}
for (u32 i = 0; i < count; i++) {
u8 *e = blob + 4 + 8 * i;
*(u32 *)(e + 0) = romdataBswap32(*(const u32 *)(e + 0));
*(u16 *)(e + 4) = romdataBswap16(*(const u16 *)(e + 4));
*(u16 *)(e + 6) = romdataBswap16(*(const u16 *)(e + 6));
}
}
/*
* D54 (docs/internals.md): a decompressed compact-sequence (cseq) file
* starts with struct ALCMidiHdr — 16 big-endian u32 trackOffset values plus a
* big-endian u32 division. N64 reads them natively; on a little-endian host
* alCSeqNew() byte-swaps them (track 0 offset 0x44 becomes 0x44000000),
* builds "valid" curLoc pointers ~1 GB past the buffer, and __getTrackByte()
* faults. The rest of the stream is byte-oriented (varlens, MIDI bytes, BE
* loop offsets assembled byte-by-byte), so only these 17 words need fixing.
*/
void romdataFixupCseq(u8 *blob)
{
u32 *hdr = (u32 *)blob;
for (u32 i = 0; i < 17; i++) {
hdr[i] = romdataBswap32(hdr[i]);
}
}
/*
* D50 (docs/internals.md): a language bank is [u32 offsets x N][text].
* Offsets are big-endian and point into the text region; langGet() reads them
* little-endian on PC and would build garbage pointers. Scan from the front
* and bswap word i while bswap32(word_i) is a plausible offset (strictly past
* its own position, before the end of the blob). Zero entries stay zero
* ("string not loaded" — langGet returns NULL). The scan stops at the first
* text word: printable ASCII byte-swaps to >= 0x20000000, always >= blobSize
* (banks are < 64 KB). All-zero placeholder banks (e.g. LameE) bswap nothing.
*/
void romdataFixupLangBank(u8 *blob, u32 blobSize)
{
u32 i = 0;
while (i * 4 + 4 <= blobSize) {
u32 v = romdataBswap32(*(const u32 *)(blob + i * 4));
if (v != 0 && (v <= i * 4 || v >= blobSize))
break;
*(u32 *)(blob + i * 4) = v;
i++;
}
}
/*
* D178 (docs/dev/findings.md): a briefing segment (Ubrief*Z, assets/obseg/
* brief/) serializes `struct BriefStruct` = { u16 brief[4];
* { u16 textid; u16 enabled_difficulty; } objective[10] } — 24 big-endian
* u16 in ROM, loaded raw by load_briefing_text_for_stage() with no
* converter. On a little-endian host every field is byte-swapped:
* textid = getStringID(LDAM, 4) = 0x2C04 reads as 0x042C -> langGet()
* resolves bank 1 slot 44 (never loaded) -> NULL -> blank objective text,
* and enabled_difficulty 0x0001 (Secret Agent) reads as 0x0100 = 256 so
* `selected_difficulty >= enabled_difficulty` filters the line out entirely.
* Same for brief[0..3] (the Overview / M / Q / Moneypenny pages, D143).
* Fixed size (48 bytes), so a straight u16 swap of the whole struct.
*/
void romdataFixupBriefing(u8 *blob)
{
u32 i;
for (i = 0; i < ROMDATA_BRIEFING_U16S; i++) {
u16 v = *(const u16 *)(blob + 2 * i);
*(u16 *)(blob + 2 * i) = (u16)((v >> 8) | (v << 8));
}
}
/*
* D50 (docs/internals.md): font segments (Zurich Bold, Bank Gothic)
* serialize `struct font` in the N64 layout — big-endian scalars, 4-byte
* pointers:
* [s32 kerning x 169][fontchar x 94 @ stride 24][glyph pixel data]
* where fontchar = {s32 index, baseline, height, width, kerningindex;
* u32 pixeldata-offset-from-font-base}. The PC C layout is
* {5 s32; u64 pixeldata} @ stride sizeof(fontchar) with chars at
* ROMDATA_OFFSEXP(struct font, chars) — and that region overlaps the start of the
* pixel data in ROM (chars end 0xB74, pixels start 0xB80, PC chars end
* 0xE78), so the re-layout must also shift the pixel data down.
*
* Transform: bswap kerning in place; memmove the pixel block from
* [pixStart, n64Size) to [pcCharsEnd, ...); convert each char entry
* (backward — PC stride 32 > N64 stride 24, dst always past src) with
* pixeldata stored as a zero-extended RELATIVE offset into the new image,
* so load_font_tables()'s existing `pixeldata += base` loop promotes it
* (N64 pattern, D37-style). The caller must allocate romdataFontPcSize()
* bytes and romCopy only n64Size of them.
*/
/* src is the N64-layout image (the ROM segment); n64Size its size. */
u32 romdataFontPcSize(const u8 *src, u32 n64Size)
{
enum { N64_COFF = 169 * 4, N64_CSTRIDE = 24, NCHARS = 94 };
u32 i, n, pixStart = 0;
if (n64Size <= N64_COFF)
return n64Size;
n = (n64Size - N64_COFF) / N64_CSTRIDE;
if (n > NCHARS)
n = NCHARS;
for (i = 0; i < n; i++) {
u32 o = romdataBswap32(*(const u32 *)(src + N64_COFF + N64_CSTRIDE * i + 20));
if (o != 0 && (pixStart == 0 || o < pixStart))
pixStart = o;
}
return (u32)(ROMDATA_OFFSEXP(struct font, chars) + NCHARS * sizeof(struct fontchar))
+ n64Size - pixStart;
}
void romdataFixupFont(u8 *blob, u32 n64Size)
{
enum { N64_KERN = 169, N64_COFF = 169 * 4, N64_CSTRIDE = 24, NCHARS = 94 };
u32 i, n, pixStart = 0, pcPixOff;
for (i = 0; i < N64_KERN; i++)
*(u32 *)(blob + 4 * i) = romdataBswap32(*(const u32 *)(blob + 4 * i));
n = (n64Size - N64_COFF) / N64_CSTRIDE;
if (n > NCHARS)
n = NCHARS;
for (i = 0; i < n; i++) {
u32 o = romdataBswap32(*(const u32 *)(blob + N64_COFF + N64_CSTRIDE * i + 20));
if (o != 0 && (pixStart == 0 || o < pixStart))
pixStart = o;
}
pcPixOff = (u32)(ROMDATA_OFFSEXP(struct font, chars) + NCHARS * sizeof(struct fontchar));
if (pixStart != 0 && pixStart < n64Size)
memmove(blob + pcPixOff, blob + pixStart, n64Size - pixStart);
for (i = n; i-- > 0;) {
const u8 *s = blob + N64_COFF + N64_CSTRIDE * i;
u8 *d = blob + ROMDATA_OFFSEXP(struct font, chars) + sizeof(struct fontchar) * i;
u32 f[6], k, o;
/* D130: PC stride (32) - N64 stride (24) = 8, so for the first few
* glyphs `d` overlaps `s` by less than the 24-byte read span and a
* field write clobbers a later field's source mid-loop (glyph 1's
* width came out as bswap(index), glyph 2's pixeldata as
* pcPixOff+(index-pixStart) -> wild pointer -> fast3d AV rendering
* '#'/'"'). Stage all six N64 fields before writing any. */
for (k = 0; k < 6; k++)
f[k] = romdataBswap32(*(const u32 *)(s + 4 * k));
for (k = 0; k < 5; k++)
*(u32 *)(d + 4 * k) = f[k];
o = f[5];
if (o != 0 && pixStart != 0 && o >= pixStart)
o = pcPixOff + (o - pixStart);
/* D51: on PC pixeldata is a u64 at d+24 (fontchar needs 8-align,
* sizeof=32) — the N64 u32-at-d+20 layout does not hold. Write the
* offset into the pointer and zero-extend it. */
*(u32 *)(d + 24) = o;
*(u32 *)(d + 28) = 0;
}
}
/*
* D37 (docs/internals.md): convert a ROM-serialized libaudio bank tree
* (ALBankFile -> ALBank -> ALInstrument -> ALSound -> ALWaveTable /
* ALEnvelope / ALKeyMap / ALADPCMloop / ALRawLoop / ALADPCMBook) to
* PC-native form. See the header for the contract.
*
* Why a re-layout rather than an in-place transform: on N64 (4-byte
* pointers, BE MIPS) the ROM layout and the C struct layout coincide. On
* x86-64 every pointer field doubles to 8 bytes, so each struct's PC image
* is larger than its ROM form — and structs are packed back-to-back in ROM,
* so a struct's expanded tail overlaps whatever follows it (e.g. the
* ALWaveTable book slot at +24 lands inside the ALADPCMBook that sits 12
* bytes after the wavetable in ROM). The tree is therefore re-laid out into
* a fresh compact image: every sub-struct is placed once, 8-byte aligned,
* and each pointer slot stores the sub-struct's NEW offset from the image
* start (zero-extended to 64 bits). alBnkfNew()/_bnkfPatch*() then rebase
* those offsets to absolute pointers unmodified (ptr + (s32)file). The
* caller must allocate romdataAudioBankPcSize() bytes for the image.
*
* Wavetable `base` fields are offsets into the separate wavetable data
* segment, not into this blob: they are byte-swapped and zero-extended but
* left as offsets — _bnkfPatchWaveTable() rebases them with its `table`
* argument (the cart address of that segment on PC).
*
* ROM layout (offsets within each struct; multi-byte fields big-endian):
* ALBankFile +0 s16 revision, +2 s16 bankCount, +4 N x u32 bank offsets
* ALBank +0 s16 instCount, +2 u8 flags, +3 u8 pad,
* +4 s32 sampleRate, +8 u32 percussion offset,
* +12 M x u32 instrument offsets
* ALInstrument +0..+11 12 x u8, +12 s16 bendRange, +14 s16 soundCount,
* +16 K x u32 sound offsets
* ALSound +0 u32 envelope, +4 u32 keyMap, +8 u32 wavetable,
* +12 u8 pan, +13 u8 volume, +14 u8 flags
* ALWaveTable +0 u32 base (offset into the wavetable data segment),
* +4 s32 len, +8 u8 type, +9 u8 flags,
* adpcm: +12 u32 loop, +16 u32 book; raw: +12 u32 loop
* ALEnvelope +0/+4/+8 s32 times, +12/+13 u8 volumes
* ALKeyMap 6 x u8 (identity)
* ALADPCMloop +0/+4/+8 u32 start/end/count, +12 s16[16] state
* ALRawLoop +0/+4/+8 u32 start/end/count
* ALADPCMBook +0 s32 order, +4 s32 npredictors,
* +8 2*order*npredictors*4 x s16 coefficients
*/
#define AF_UNSET 0xFFFFFFFFu
struct afCtx {
const u8 *src; /* pristine ROM-layout bytes; first srcSize valid */
u32 srcSize;
u8 *dst; /* target image buffer (allocSize bytes) */
u32 allocSize;
int write; /* 0 = measure pass, 1 = write pass */
u32 pos; /* write cursor (kept 8-aligned by afReserve) */
u8 *visited; /* [srcSize] per-struct visited marker */
u32 *newOff; /* [srcSize] ROM offset -> image offset (write pass) */
s16 revision;
s16 bankCount;
int errors;
};
/* ROM-layout reads are big-endian. */
static u16 afRd16(const struct afCtx *c, u32 off)
{
return (u16)((c->src[off] << 8) | c->src[off + 1]);
}
static u32 afRd32(const struct afCtx *c, u32 off)
{
return ((u32)c->src[off] << 24) | ((u32)c->src[off + 1] << 16) |
((u32)c->src[off + 2] << 8) | (u32)c->src[off + 3];
}
static void afWr16(struct afCtx *c, u32 off, u16 v)
{
c->dst[off] = (u8)v;
c->dst[off + 1] = (u8)(v >> 8);
}
static void afWr32(struct afCtx *c, u32 off, u32 v)
{
/* Host byte order (LE on x86-64): libaudio reads these with plain loads. */
c->dst[off] = (u8)v;
c->dst[off + 1] = (u8)(v >> 8);
c->dst[off + 2] = (u8)(v >> 16);
c->dst[off + 3] = (u8)(v >> 24);
}
/* Zero-extended image-relative offset into an 8-byte pointer slot. */
static void afWrPtr(struct afCtx *c, u32 off, u32 value)
{
afWr32(c, off, value);
c->dst[off + 4] = c->dst[off + 5] = c->dst[off + 6] = c->dst[off + 7] = 0;
}
/* -1: invalid offset, 0: first visit, 1: already placed. */
static int afEnter(struct afCtx *c, u32 off)
{
if (off == 0 || off >= c->srcSize)
return -1;
if (c->visited[off])
return 1;
c->visited[off] = 1;
return 0;
}
/* Align the cursor, advance it by size, and return the reserved start
* (write pass) or 0 (measure pass). */
static u32 afReserve(struct afCtx *c, u32 size)
{
u32 n = ((c->pos + 7u) & ~7u);
c->pos = n + size;
return c->write ? n : 0;
}
/* New image offset of a referenced sub-struct, or 0 if absent/invalid. */
static u32 afPtr(struct afCtx *c, u32 off)
{
if (off == 0 || off >= c->srcSize)
return 0;
return (c->newOff[off] != AF_UNSET) ? c->newOff[off] : 0;
}
static void afFixupBook(struct afCtx *c, u32 o)
{
int r = afEnter(c, o);
if (r == 1)
return;
if (r < 0 || o + 8 > c->srcSize) { c->errors++; return; }
s32 order = (s32)afRd32(c, o);
s32 npred = (s32)afRd32(c, o + 4);
u32 size = 8;
if (order > 0 && npred > 0) {
/* Coefficient array is 2*order*npredictors*ADPCMVSIZE(8) bytes on
* disk (see load.c bookSize): that many s16 values. */
size += 2u * (u32)order * (u32)npred * 8u;
} else {
sysLogPrintf(LOG_ERROR,
"romdataFixupAudioBank: bad ADPCM book @0x%X (order %d, npredictors %d)",
o, order, npred);
}
u32 n = afReserve(c, size);
if (!c->write)
return;
c->newOff[o] = n;
afWr32(c, n, (u32)order);
afWr32(c, n + 4, (u32)npred);
if (order > 0 && npred > 0 && o + size <= c->srcSize) {
u32 ncoeff = 2u * (u32)order * (u32)npred * 4u;
for (u32 i = 0; i < ncoeff; i++)
afWr16(c, n + 8 + 2 * i, afRd16(c, o + 8 + 2 * i));
}
}
static void afFixupAdpcmLoop(struct afCtx *c, u32 o)
{
int r = afEnter(c, o);
if (r == 1)
return;
if (r < 0 || o + 44 > c->srcSize) { c->errors++; return; }
u32 n = afReserve(c, 48);
if (!c->write)
return;
c->newOff[o] = n;
for (u32 i = 0; i < 3; i++) /* start/end/count */
afWr32(c, n + 4 * i, afRd32(c, o + 4 * i));
for (u32 i = 0; i < 16; i++) /* ADPCM_STATE = s16[16] */
afWr16(c, n + 12 + 2 * i, afRd16(c, o + 12 + 2 * i));
}
static void afFixupRawLoop(struct afCtx *c, u32 o)
{
int r = afEnter(c, o);
if (r == 1)
return;
if (r < 0 || o + 12 > c->srcSize) { c->errors++; return; }
u32 n = afReserve(c, 16);
if (!c->write)
return;
c->newOff[o] = n;
for (u32 i = 0; i < 3; i++) /* start/end/count */
afWr32(c, n + 4 * i, afRd32(c, o + 4 * i));
}
static void afFixupEnvelope(struct afCtx *c, u32 o)
{
int r = afEnter(c, o);
if (r == 1)
return;
if (r < 0 || o + 14 > c->srcSize) { c->errors++; return; }
u32 n = afReserve(c, 16);
if (!c->write)
return;
c->newOff[o] = n;
for (u32 i = 0; i < 3; i++) /* ALMicroTime x3 */
afWr32(c, n + 4 * i, afRd32(c, o + 4 * i));
c->dst[n + 12] = c->src[o + 12]; /* u8 volumes: identity */
c->dst[n + 13] = c->src[o + 13];
/* D202/M-65 diag (temporary): decayTime == -1 is what sets
* SOUND_FLAG_LOOPED, and a looped sound never releases its voice on its
* own. Print the SOURCE ROM bytes so genuine data can be told from a
* conversion artifact. Remove once root-caused. */
if (getenv("GE_KEYMAPDUMP")) {
sysLogPrintf(LOG_NOTE,
"ENVSRC src@0x%06X -> img@0x%06X : raw=%02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X "
"attack=%d decay=%d release=%d",
o, n,
c->src[o], c->src[o+1], c->src[o+2], c->src[o+3],
c->src[o+4], c->src[o+5], c->src[o+6], c->src[o+7],
c->src[o+8], c->src[o+9], c->src[o+10], c->src[o+11],
(int)afRd32(c, o), (int)afRd32(c, o + 4), (int)afRd32(c, o + 8));
}
}
static void afFixupWaveTable(struct afCtx *c, u32 o)
{
int r = afEnter(c, o);
if (r == 1)
return;
if (r < 0 || o + 20 > c->srcSize) { c->errors++; return; }
u32 base = afRd32(c, o); /* offset into wavetable data */
s32 len = (s32)afRd32(c, o + 4);
u8 type = c->src[o + 8];
u32 loop, book;
if (type == 0 /* AL_ADPCM_WAVE */) {
loop = afRd32(c, o + 12);
book = afRd32(c, o + 16);
if (loop) afFixupAdpcmLoop(c, loop); /* children first */
if (book) afFixupBook(c, book);
} else if (type == 1 /* AL_RAW16_WAVE */) {
loop = afRd32(c, o + 12);
book = 0;
if (loop) afFixupRawLoop(c, loop);
} else {
sysLogPrintf(LOG_ERROR,
"romdataFixupAudioBank: unknown wavetable type %d @0x%X",
type, o);
loop = book = 0;
}
u32 n = afReserve(c, 32);
if (!c->write)
return;
c->newOff[o] = n;
afWrPtr(c, n, base); /* stays an offset; rebased with `table` */
afWr32(c, n + 8, (u32)len);
c->dst[n + 12] = type;
c->dst[n + 13] = c->src[o + 9]; /* flags: identity */
if (type == 0) {
afWrPtr(c, n + 16, afPtr(c, loop));
afWrPtr(c, n + 24, afPtr(c, book));
} else {
afWrPtr(c, n + 16, afPtr(c, loop));
}
}
static void afFixupSound(struct afCtx *c, u32 o)
{
int r = afEnter(c, o);
if (r == 1)
return;
if (r < 0 || o + 15 > c->srcSize) { c->errors++; return; }
u32 env = afRd32(c, o);
u32 km = afRd32(c, o + 4);
u32 wt = afRd32(c, o + 8);
if (env) afFixupEnvelope(c, env); /* children first */
if (km) {
int kr = afEnter(c, km);
if (kr == -1 || km + 6 > c->srcSize)
c->errors++;
else if (kr == 0) {
u32 kn = afReserve(c, 8);
if (c->write) {
c->newOff[km] = kn;
memcpy(c->dst + kn, c->src + km, 6); /* ALKeyMap: all u8 */
/* D202/M-65 diag (temporary): print the SOURCE ROM bytes of
* every ALKeyMap, so "the game sees zeros" can be separated
* from "the ROM holds zeros". Remove once root-caused. */
if (getenv("GE_KEYMAPDUMP")) {
sysLogPrintf(LOG_NOTE,
"KEYMAP src@0x%06X -> img@0x%06X : velMin=%u velMax=%u "
"keyMin=%u keyMax=0x%02X keyBase=%u detune=%d",
km, kn, c->src[km], c->src[km + 1], c->src[km + 2],
c->src[km + 3], c->src[km + 4], (int)(s8)c->src[km + 5]);
}
}
}
}
if (wt) afFixupWaveTable(c, wt);
u32 n = afReserve(c, 32);
if (!c->write)
return;
c->newOff[o] = n;
afWrPtr(c, n, afPtr(c, env));
afWrPtr(c, n + 8, afPtr(c, km));
afWrPtr(c, n + 16, afPtr(c, wt));
c->dst[n + 24] = c->src[o + 12]; /* pan/volume/flags: identity */
c->dst[n + 25] = c->src[o + 13];
c->dst[n + 26] = c->src[o + 14];
}
static void afFixupInst(struct afCtx *c, u32 o)
{
int r = afEnter(c, o);
if (r == 1)
return;
if (r < 0 || o + 16 > c->srcSize) { c->errors++; return; }
s16 scount = (s16)afRd16(c, o + 14); /* soundCount */
u32 maxSounds = (c->srcSize - (o + 16)) / 4;
if ((u32)scount > maxSounds) {
sysLogPrintf(LOG_ERROR,
"romdataFixupAudioBank: soundCount %d @0x%X exceeds blob capacity %u",
scount, o, maxSounds);
scount = (s16)maxSounds;
}
for (u32 i = 0; i < (u32)scount; i++) { /* children first */
u32 soff = afRd32(c, o + 16 + 4 * i);
/* D202/M-65 diag (temporary): dump the SOURCE bank tree per
* soundIndex -- instArray[0]->soundArray[i] is exactly how snd.c
* resolves a soundIndex. decayTime == -1 is what makes a sound
* SOUND_FLAG_LOOPED (and therefore un-releasable without an owner),
* so this says whether that flag is genuine ROM data for a given
* index or a conversion artifact. Remove once root-caused. */
if (getenv("GE_BANKDUMP") && soff && soff + 12 <= c->srcSize) {
u32 env = afRd32(c, soff);
u32 km = afRd32(c, soff + 4);
sysLogPrintf(LOG_NOTE,
"BANKSRC inst@0x%06X soundIndex=%u sound@0x%06X env@0x%06X km@0x%06X decay=%d",
o, i, soff, env, km,
(env && env + 8 <= c->srcSize) ? (int)afRd32(c, env + 4) : 0);
}
if (soff)
afFixupSound(c, soff);
}
u32 n = afReserve(c, 16 + 8u * (u32)scount);
if (!c->write)
return;
c->newOff[o] = n;
memcpy(c->dst + n, c->src + o, 12); /* 12 x u8: identity */
afWr16(c, n + 12, afRd16(c, o + 12)); /* bendRange */
afWr16(c, n + 14, (u16)scount);
for (u32 i = 0; i < (u32)scount; i++) {
u32 soff = afRd32(c, o + 16 + 4 * i);
afWrPtr(c, n + 16 + 8 * i, afPtr(c, soff));
}
}
static void afFixupBank(struct afCtx *c, u32 o)
{
int r = afEnter(c, o);
if (r == 1)
return;
if (r < 0 || o + 12 > c->srcSize) { c->errors++; return; }
s16 icount = (s16)afRd16(c, o); /* instCount */
u32 perc = afRd32(c, o + 8);
u32 maxInsts = (c->srcSize - (o + 12)) / 4;
if ((u32)icount > maxInsts) {
sysLogPrintf(LOG_ERROR,
"romdataFixupAudioBank: instCount %d @0x%X exceeds blob capacity %u",
icount, o, maxInsts);
icount = (s16)maxInsts;
}
if (perc) afFixupInst(c, perc); /* children first */
for (u32 i = 0; i < (u32)icount; i++) {
u32 ioff = afRd32(c, o + 12 + 4 * i);
if (ioff)
afFixupInst(c, ioff);
}
u32 n = afReserve(c, 16 + 8u * (u32)icount);
if (!c->write)
return;
c->newOff[o] = n;
afWr16(c, n, (u16)icount);
c->dst[n + 2] = c->src[o + 2]; /* flags: identity */
c->dst[n + 3] = c->src[o + 3]; /* pad : identity */
afWr32(c, n + 4, afRd32(c, o + 4)); /* sampleRate */
afWrPtr(c, n + 8, afPtr(c, perc));
for (u32 i = 0; i < (u32)icount; i++) {
u32 ioff = afRd32(c, o + 12 + 4 * i);
afWrPtr(c, n + 16 + 8 * i, afPtr(c, ioff));
}
}
/* One pass over the tree. Returns the image size (16-aligned). */
static u32 afWalk(struct afCtx *c)
{
c->revision = (s16)afRd16(c, 0);
c->bankCount = (s16)afRd16(c, 2);
if (c->revision != 0x4231 /* AL_BANK_VERSION 'B1' */) {
sysLogPrintf(LOG_ERROR,
"romdataFixupAudioBank: bad bank revision 0x%04X",
(u32)c->revision);
}
u32 maxBanks = (c->srcSize - 4) / 4;
if ((u32)c->bankCount > maxBanks) {
sysLogPrintf(LOG_ERROR,
"romdataFixupAudioBank: bankCount %d exceeds blob capacity %u",
c->bankCount, maxBanks);
c->bankCount = (s16)maxBanks;
}
/* The ALBankFile header must sit at image offset 0 (alBnkfNew rebases
* bankArray entries relative to the blob start), so reserve it first. */
afReserve(c, 8 + 8u * (u32)c->bankCount);
for (s16 i = 0; i < c->bankCount; i++) {
u32 off = afRd32(c, 4 + 4 * (u32)i);
if (off)
afFixupBank(c, off);
}
if (c->write) {
afWr16(c, 0, (u16)c->revision);
afWr16(c, 2, (u16)c->bankCount);
for (s16 i = 0; i < c->bankCount; i++) {
u32 off = afRd32(c, 4 + 4 * (u32)i);
afWrPtr(c, 8 + 8 * (u32)i, afPtr(c, off));
}
}
return ((c->pos + 15u) & ~15u);
}
static void afPass(struct afCtx *c, int write)
{
c->write = write;
c->pos = 0;
memset(c->visited, 0, c->srcSize);
if (c->newOff)
for (u32 i = 0; i < c->srcSize; i++)
c->newOff[i] = AF_UNSET;
afWalk(c);
}
u32 romdataAudioBankPcSize(const u8 *src, u32 srcSize)
{
if (!src || srcSize < 4)
return 0;
struct afCtx c = { .src = src, .srcSize = srcSize, .dst = NULL,
.allocSize = 0, .write = 0, .pos = 0,
.visited = NULL, .newOff = NULL, .errors = 0 };
c.visited = (u8 *)malloc(srcSize);
if (!c.visited)
return 0;
memset(c.visited, 0, srcSize);
u32 n = afWalk(&c);
free(c.visited);
return n;
}
void romdataFixupAudioBank(u8 *blob, u32 srcSize, u32 allocSize)
{
if (!blob || srcSize < 4) {
sysLogPrintf(LOG_ERROR, "romdataFixupAudioBank: blob too small (%u)",
srcSize);
return;
}
/* The write pass lays the image out over the pristine bytes (dst ==
* blob), so both passes must read from an untouched copy. */
struct afCtx c = { .src = NULL, .srcSize = srcSize, .dst = blob,
.allocSize = allocSize, .write = 0, .pos = 0,
.visited = NULL, .newOff = NULL, .errors = 0 };
c.src = (const u8 *)malloc(srcSize);
c.visited = (u8 *)malloc(srcSize);
c.newOff = (u32 *)malloc(4 * (size_t)srcSize);
if (!c.src || !c.visited || !c.newOff) {
free((void *)c.src);
free(c.visited);
free(c.newOff);
sysLogPrintf(LOG_ERROR, "romdataFixupAudioBank: OOM");
return;
}
memcpy((void *)c.src, blob, srcSize);
/* Measure pass, then write pass over the same arrays. */
afPass(&c, 0);
if (c.pos > allocSize) {
sysLogPrintf(LOG_ERROR,
"romdataFixupAudioBank: image 0x%X exceeds allocation 0x%X",
c.pos, allocSize);
free((void *)c.src);
free(c.visited);
free(c.newOff);
return;
}
afPass(&c, 1);
free((void *)c.src);
free(c.visited);
free(c.newOff);
if (c.errors) {
sysLogPrintf(LOG_ERROR,
"romdataFixupAudioBank: %d out-of-range sub-struct(s)",
c.errors);
}
}