Files
ci-libretro-fceumm/src/boards/106.c

109 lines
2.7 KiB
C
Raw Normal View History

/* FCE Ultra - NES/Famicom Emulator
*
* Copyright notice for this file:
* Copyright (C) 2007 CaH4e3
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "mapinc.h"
core: stdint typedefs, LE optimizations, frame determinism Three follow-up audit passes on top of the memory-safety / leak / savestate-portability work in 1185db8. ============================================================== Pass 1: replace custom typedefs with C99 stdint types throughout ============================================================== The custom uint8 / uint16 / uint32 / uint64 / int8 / int16 / int32 / int64 typedefs in src/fceu-types.h were just simple aliases for the C99 stdint.h types. Replace them with the standard names directly. - 498 files modified - ~3,400 token replacements (uint8 -> uint8_t, etc) - fceu-types.h slimmed down to just INLINE / GINLINE / FASTAPASS macros and the readfunc / writefunc function-pointer typedefs (those now use uint8_t / uint32_t natively) - Build clean on `make platform=unix` with zero new warnings - Output binary size unchanged - confirming semantic equivalence Mechanical replacement done with a Python script that uses word- boundary regex to avoid false positives (e.g. 'uint32_t' was correctly left alone because '_' is a word character so 'uint32' is not a complete word inside it). ================================================================ Pass 2: prefer memcpy on LE hosts for endian read/write helpers ================================================================ fceu-endian.c's write32le_mem, FCEU_en32lsb, and FCEU_de32lsb performed bytewise composition/decomposition unconditionally. On LE hosts the in-memory representation already matches the desired LE on-disk format, so a single memcpy is equivalent and lets the compiler emit a single load/store rather than four byte ops. - The bytewise path is kept inside #ifdef MSB_FIRST for BE hosts where it implements the actual byte swap - Both forms produce identical results; this is a code-clarity change more than a performance one (the optimizer was already merging the shifts on LE), but it documents the intent and removes a strict-aliasing-flavoured cast through *(uint32_t*)Bufo - Added missing #include <string.h> in fceu-endian.c which was relying on transitive includes for memcpy Other MSB_FIRST sites in the codebase (state.c FlipByteOrder guards, ppu.c sprite-line rendering, boards/unrom512.c flash-write- counter access) were already optimized for LE; they were verified correct rather than changed. ================================================================ Pass 3: frame determinism for replay and netplay ================================================================ Two libc rand() sites in core were replaced with a local xorshift32 PRNG so that NES games which read uninitialised memory or hit hardware "weak bit" emulation produce reproducible behaviour across runs. NES titles routinely read uninitialised RAM (variables not zeroed before use, sprite Y-position set by junk-on-stack), so the RAM contents at power-on subtly affect game behaviour. With libc rand(), those contents depend on whether anyone else seeded rand() in the same process - a different libretro frontend, a different audio backend init order, or any frontend that does srand(time(0)) all break replay / netplay frame-determinism. 1. fceu.c FCEU_MemoryRand. Used to fill RAM (PowerNES) and CHR-RAM (iNES_Init) at power-on when option_ramstate=2 (random init). Replaced with a local xorshift32 PRNG, exposed via a new FCEU_MemoryRand_Reseed(uint32_t) function called once per power-on: - PowerNES seeds from the first 4 bytes of GameInfo->MD5 (set by all loaders before PowerNES runs) so identical ROMs produce identical RAM, different ROMs differ - iNES_Init seeds from iNESCart.PRGCRC32 before the CHR-RAM fill so two builds of the same ROM get the same CHR-RAM - The PRNG state advances across multiple FCEU_MemoryRand calls within one power-on so RAM and CHR-RAM get different content (matching NES hardware reality) 2. boards/rt-01.c UNLRT01Read. The RT-01 board has 'weak bit' protected EPROM regions; reads of 0xCE80-0xCEFF and 0xFE80- 0xFEFF return 0xF2 with the low 3 bits randomised. Replaced libc rand() with a local xorshift32 seeded at power-on, and added the PRNG state to the savestate via AddExState with key "WBKS" so save / load / rewind / netplay rollback all stay deterministic. In addition, two long-double-to-int truncations were changed to double for cross-platform FP determinism: - sound.c SetSoundVariables: soundtsinc - boards/n106.c DoNamcoSound: inc long double has platform-dependent precision (80-bit on x87, 64-bit with -mfpmath=sse, 128-bit on PowerPC), so the truncated integer result varied across these platforms. double is guaranteed 64-bit IEEE-754 portably. After this pass, the core has no time(), clock(), gettimeofday(), clock_gettime(), getpid(), getuid(), getgid(), getenv(), gethostid(), pthread, std::thread, OpenMP, signal handler, or non-deterministic- malloc dependency. Verified with a Python scanner that greps the source for these patterns; runs clean. The PPU / APU / CPU power-on already explicitly memset all state buffers to 0 (deterministic), and ROM/CHR-ROM allocation already memsets to 0xFF before partial fread (deterministic regardless of file truncation). Combined with the memory-safety hardening in 1185db8 (which prevents savestate-loaded indices from going out-of-bounds and producing unpredictable behaviour), the core now offers genuine frame-deterministic replay across runs, builds, and host endian.
2026-05-04 02:46:34 +02:00
static uint8_t reg[16], IRQa;
static uint32_t IRQCount;
static uint8_t *WRAM = NULL;
static uint32_t WRAMSIZE;
static SFORMAT StateRegs[] =
{
{ &IRQa, 1, "IRQA" },
core: memory-safety, leak, and savestate-portability audit fixes Squashed series of ~50 distinct bugs found during a multi-day security/correctness audit, ranging from ROM-triggerable heap corruption and savestate-triggered OOB read primitives down to obscure cross-platform savestate breakage. Build is clean on `make platform=unix` with zero new warnings. CRITICAL (ROM-triggerable, exploitable on the user's machine) ============================================================= * ines.c iNES 2.0 PRG/CHR exponent overflow leading to undersized allocation followed by heap buffer overflow on fread (pow() with attacker-chosen exponent up to 63). Cap exponent at 30 and compute size in uint32 with explicit cap below 2 GiB before storing in int. * ines.c miscROMSize int wraparound from attacker-controlled PRG/ CHR sizes; previous '& 0x8000000' check missed common underflow cases. Compute in int64 and reject <= 0 or > 128 MiB. * unif.c MAPR chunk OOB heap read on chunks of size < 4. Validate chunk size before allocating board-name buffer. * unif.c chunk size truncation: int conversion + missing cap allowed a 4 GiB chunk size to wrap. Cap at 16 MiB. * unif.c FixRomSize infinite loop on size > 0x80000000. Cap input. * unif.c DINF chunk: months[(m - 1) % 12] is UB for m==0; m comes from the .unf file. Clamp m into [1..12] before subtracting. * unif.c NAME chunk: GameInfo->name = malloc(...) was unchecked. * nsf.c size underflow: FCEU_fgetsize() - 0x80 wraps to ~UINT64_MAX on tiny files, propagating a huge size into uppow2/FCEU_malloc. Validate size > 0x80 before subtracting. * nsf.c NSFMaxBank * 4096 cap tightened from 1<<20 (UB on signed-int overflow) to 1<<19 (fits in int). * general.c uppow2 had signed-int UB (1 << 32) and wrapped to 0 for inputs near uint32 max. Cap at 0x80000000. * cheat.c SubCheats[256] BSS overflow when more than 256 GG/PAR substitution cheats are active. The array is adjacent to MMapPtrs[64] in BSS which is exposed via retro_get_memory_data, making this overflow visible from outside the core. * libretro.c retro_cheat_set strcpy stack overflow with arbitrary- length cheat strings from the frontend. Use strlcpy. CRITICAL (savestate-triggerable on a malicious .fcs file) ========================================================= * fds.c FDS savestate OOB read primitive: InDisk loaded from savestate is used as index into 8-element diskdata[] static pointer array. A value 8..254 dereferences arbitrary memory as a pointer (heap-read primitive). Also bounds-checked SelectDisk, mapperFDS_block, mapperFDS_blockstart, mapperFDS_diskaddr, mapperFDS_blocklen so blockstart+diskaddr stays within the 65500- byte disk buffer. * 11 mappers had savestate-loaded variables masked at write time but not at restore time, used as indices into fixed arrays: - 88.c, KS7037.c, 112.c cmd indexes reg[8] - sachen.c (S8259, S74LS374N) cmd indexes latch[8] - 357.c dipswitch indexes outer_bank[4] - unrom512.c flash_state indexes erase_a/d/b[5] - 368.c preg indexes banks[8] - 69.c sndcmd indexes sreg[14] - mmc2and4.c latch0/latch1 index creg[4] None individually exploitable into RCE - the array writes corrupt adjacent BSS with constrained data flowing in - but each is an out-of-bounds read or write from attacker-controllable input. HIGH (memory-safety, reachable on any load) =========================================== * fceu-memory.c FCEU_malloc deref-of-NULL on allocation failure ('ret = 0; memset(ret, 0, size);'). * file.c multiple FCEUFILE/MakeMemWrap/MakeMemWrapBuffer unchecked allocations and unchecked filestream_tell return. * libretro.c GameInfo NULL-derefs in three entry points (retro_set_controller_port_device, retro_get_memory_data, retro_get_memory_size) reachable on operations called before a successful load. * libretro.c framebuffer leak ~256 KB per failed FCEUI_LoadGame (the libretro frontend doesn't call retro_unload_game on a failed load). * libretro.c 3DS retro_deinit unchecked linearFree. * libretro.c stereo / NTSC filter unchecked malloc returns. * fceu.c FCEUI_LoadGame unchecked GameInfo malloc. * fds.c SubLoad and FDSLoad unchecked FCEU_malloc on diskdatao backup buffers (NULL-deref in subsequent memcpy). * fceu.c AllocGenieRW partial-failure leak: AReadG allocated but BWriteG malloc fails -> 256 KB leak per retry. * input/bworld.c Update(): unbounded strcpy from attacker-supplied data into 20-byte bdata. Replaced with length-bounded copy. * cart.c setprg2r/4r and setchr1r/2r/4r/8r mask-underflow guards. SetupCartPRGMapping/SetupCartCHRMapping compute (size >> N) - 1 which underflows to 0xFFFFFFFF for chips smaller than the unit. setprg8r/16r/32r and setchr8r already had defensive size checks; the smaller units did not. malee.c (2 KB chip) and mapper 218 (2 KB NTARAM-as-CHR) accidentally avoid OOB; the primitive is unsafe for any future board. * video.c FCEU_InitVirtualVideo XBuf/XDBuf partial-failure leak. * video.c, fceu.c FCEU_DispMessage / FCEU_printf / FCEU_PrintError: vsprintf into fixed stack buffers replaced with vsnprintf. * core: validate magic-string read length on FDS/UNIF/NSF load (reject files too short to contain the magic so previous static buffer / stack garbage doesn't spuriously match). LEAKS (every cart load/unload cycle) ==================================== * mmc5.c MMC5 cart-side: WRAM (up to 64K) + MMC5fill (1K) + ExRAM (1K) leaked per cycle. NSFMMC5_Close existed but was only used for NSF code path. * onebus.c Mapper 270/436: 8 KB CHRRAM leaked per cycle. * 330.c, 375.c, 528.c: 8 KB WRAM each, no Close at all. CORRECTNESS (UB / ABI / endianness) =================================== * fceu-endian.c FlipByteOrder loop bound was 'count' instead of 'count/2', making it a no-op for every even count and silently breaking savestate portability for every FCEUSTATE_RLSB-marked field. The '#ifndef GEKKO' workarounds scattered through the codebase (sound.c, vrc7.c, vrc6.c) were symptoms of this root cause. * state.c AddExState bounds check ran AFTER writing the entry, so when SFEXINDEX hit the 63-entry cap the next call would write the entry then immediately overwrite it with the terminator. * state.c FCEUSS_Save_Mem: post-save callback was guarded by 'if (SPreSave)' instead of 'if (SPostSave)'. * Sequence-point UB in counter expressions across 4 board files, e.g. 'x = !x ? a : --x' and 'x = ++x % n'. GCC -Wsequence-point flags all five sites (285.c, 413.c, asic_mmc3.c, jyasic.c). * SFORMAT size mismatches between declared variable type and AddExState size argument: - asic_vrc3.c VRC3_count/VRC3_reload (uint16, saved as 1 byte) - mmc3.c m555_count (uint32, saved as 2 bytes), m555_count_- expired (uint8, saved as 2 bytes - OOB write into adjacent BSS). * unrom512.c UNROM-512 mapper 30 .srm save format: host-endian uint32 flash write counters. Now stored as LE on disk regardless of host (Battle Kid 2, Twin Dragons, Lizard, Sole, etc.). * ~70 multi-byte single-variable SFORMAT/AddExState entries across 36 board files were saved as host-byte-order. After the FlipByteOrder fix, these are now byte-swapped on BE so cross- platform savestates round-trip correctly. * coolgirl.c new ExStateLE() macro added; 22 multi-byte sites. * pic16c5x.c 11 multi-byte AddExState calls (m_PC, m_PREVPC, m_CONFIG, m_WDT, m_prescaler, m_opcode, m_STACK[0/1], m_icount, m_delay_timer, m_rtcc, m_inst_cycles, m_clock2cycle). * onebus.c PowerJoy Supermax submapper detection used non-portable *(uint32*)&info->MD5 cast that read different bytes on LE vs BE. * fds.c clean up redundant FCEUSTATE_RLSB encoding in AddExState calls that also passed type=1 (idempotent OR; readability fix). DOCUMENTATION ============= * input.c UpdateGP's *(uint32*)data cast looks like a typical endian bug but is actually correct (the libretro frontend builds JSReturn with matching host-uint32 shifts). Comment added to prevent future "fixes" from breaking it. Limitations not addressed ========================= * Element-stride-aware byte swapping. The savestate byte-swap mechanism (FlipByteOrder over the entire SFORMAT entry buffer) is structurally wrong for arrays of multi-byte values: it reverses the whole buffer end-to-end instead of byte-swapping each element. Several places that need cross-platform-portable arrays (VRC7 sound state, jyasic chr[8]) work around this by either splitting arrays into per-element SFORMAT entries (n106 PlayIndex, bandai reg) or by skipping save entirely on BE via #ifndef GEKKO. A proper fix would extend the size encoding with an element-stride field. Left for a future change because it would change the savestate format. * Strict-aliasing UB in ppu.c (around 9 sites doing *(uint32*)uint8_buf for fast 4-byte writes via FCEU_dwmemset). Works in practice with all common compilers because the patterns are byte-symmetric, but is formally UB. * FCEU_gmalloc calls exit(1) on OOM. A libretro core should never exit() because that takes down the whole frontend. Used by 100+ call sites; refactoring to return-NULL is out of scope here. Testing ======= * Build: clean on `make platform=unix` with -O2; no new warnings. * FlipByteOrder fix verified by hand-trace and a standalone unit test for counts 2, 4, 8. * uppow2 fix verified by unit test across 13 boundary cases. * SFORMAT size mismatches and missing-RLSB cases identified by Python static-analysis scripts that cross-reference SFORMAT entries against variable declarations. * iNES 2.0 exponent fix verified by hand-tracing what byte 0xFF produces post-fix: exp=30 (capped), mult=7, size=3 GiB nominal, capped to 1 GiB, capped to 2 GiB by uppow2, FCEU_malloc returns NULL on most systems, loader returns 0. No heap overflow for any input byte. * Savestate-loaded array index audit: built a Python scanner that extracts each AddExState/SFORMAT entry and cross-references the variable name against array-index uses in the same file. All flagged sites covered. * A libFuzzer harness and seed corpus generator (fuzz_main.c, gen_seed_corpus.py) accompany this submission for ongoing regression testing.
2026-05-04 02:15:40 +02:00
{ &IRQCount, 4 | FCEUSTATE_RLSB, "IRQC" },
{ reg, 16, "REGS" },
{ 0 }
};
static void Sync(void) {
setchr1(0x0000, reg[0] & 0xfe);
setchr1(0x0400, reg[1] | 1);
setchr1(0x0800, reg[2] & 0xfe);
setchr1(0x0c00, reg[3] | 1);
setchr1(0x1000, reg[4]);
setchr1(0x1400, reg[5]);
setchr1(0x1800, reg[6]);
setchr1(0x1c00, reg[7]);
setprg8r(0x10, 0x6000, 0);
setprg8(0x8000, (reg[0x8] & 0xf) | 0x10);
setprg8(0xA000, (reg[0x9] & 0x1f));
setprg8(0xC000, (reg[0xa] & 0x1f));
setprg8(0xE000, (reg[0xb] & 0xf) | 0x10);
setmirror((reg[0xc] & 1) ^ 1);
}
static DECLFW(M106Write) {
A &= 0xF;
switch (A) {
case 0xD: IRQa = 0; IRQCount = 0; X6502_IRQEnd(FCEU_IQEXT); break;
case 0xE: IRQCount = (IRQCount & 0xFF00) | V; break;
case 0xF: IRQCount = (IRQCount & 0x00FF) | (V << 8); IRQa = 1; break;
default: reg[A] = V; Sync(); break;
}
}
static void M106Power(void) {
reg[8] = reg[9] = reg[0xa] = reg[0xb] = -1;
Sync();
SetReadHandler(0x6000, 0x7FFF, CartBR);
SetReadHandler(0x8000, 0xFFFF, CartBR);
SetWriteHandler(0x6000, 0x7FFF, CartBW);
SetWriteHandler(0x8000, 0xFFFF, M106Write);
}
static void M106Reset(void) {
}
static void M106Close(void) {
if (WRAM)
FCEU_gfree(WRAM);
WRAM = NULL;
}
core: stride-aware savestate, iNES2 helpers, -Wundef, -Wmissing-prototypes Audit pass 5 - five distinct cleanups bundled into one omnibus. 1. Element-stride byte-swapping for savestate fields (state.h, state.c, fceu-endian.{h,c}) The SFORMAT 's' field was previously {bit 31 = RLSB, bits 0..30 = byte size}. RLSB triggers FlipByteOrder() on MSB hosts, which reverses the entire entry buffer end-to-end. That is correct for a single primitive (size <= 8 bytes) but wrong for an array of multi-byte primitives - reversing the whole buffer would swap element 0 with element N-1 and reverse their bytes too, scrambling the data. The previous workaround was either splitting an N-element array into N separate single-primitive entries with distinct chunk IDs (n106 PlayIndex split into IDX0..IDX7) or skipping the entry entirely on big-endian hosts (the GEKKO #ifndef in vrc6.c / vrc7.c). Both approaches mean BE saves are not portable to LE and vice versa, and force the same workaround at every new array site. This pass adds proper stride support: * SFORMAT 's' encoding is now {bit 31 = RLSB, bits 24..30 = stride in bytes (0 = legacy/unset), bits 0..23 = byte size}. 16 MiB max size, well above any actual savestate field. * FCEUSTATE_RLSB_ARRAY(stride) macro for the new pattern. * FlipByteOrderStrided() byte-swaps each element of an array independently. Round-trip identity verified: [01 00 00 00 ...] -> [00 00 00 01 ...] -> [01 00 00 00 ...]. * state.c's SubWrite / ReadStateChunk / CheckS use new helpers sf_size() / sf_stride() / sf_flip() that mask the size out of the new bit layout and dispatch to the strided variant when stride < size. Backwards compatible: legacy single-primitive entries (size == 1, 2, 4, 8) leave the stride bits at zero, which sf_stride() reads as "stride equals size" and falls through to FlipByteOrder() as before. No on-disk format change. Existing single-primitive RLSB sites are unchanged. The infrastructure is now in place so any future SFORMAT entry that is an array of multi-byte primitives can be expressed as a single entry (e.g. "{ buf, sizeof(buf) | FCEUSTATE_RLSB_ARRAY(4), "BUF." }") without splitting or skipping. The existing PlayIndex split and GEKKO #ifndefs are intentionally left untouched - migrating them would alter the on-disk savestate format and is a separate decision. 2. iNES1-vs-iNES2 sizing helpers (cart.h) Twelve sites across the codebase encoded the same conditional: info->iNES2 ? (info->PRGRamSize + info->PRGRamSaveSize) : default Sometimes for PRGRAM, sometimes for CHRRAM, sometimes in bytes, sometimes after dividing by 1024. The pattern is verbose and easy to write inconsistently. Added two inline helpers in cart.h: - CartInfo_PRGRAM_bytes(info, default_bytes) - CartInfo_CHRRAM_bytes(info, default_bytes) Migrated 9 of the 13 sites: cartram.c (2), 162.c, 163.c, 134.c, 399.c, 478.c, 480.c, 484.c. The remaining 4 are non-helper-fitting variants (164.c special masking, 2 cartram SaveGameLen sites with different fallback semantics, mmc3.c Boogerman submapper detection). 3. -Wundef enabled permanently (Makefile.libretro) Zero warnings out of the box - no #if-on-undefined-macro footguns in the codebase. Now part of WARNING_DEFINES alongside the existing -Wsign-compare. 4. -Wmissing-prototypes enabled permanently (Makefile.libretro) Started at 198 warnings, cleared all of them: * Mass-static-ified ~96 functions across 75 files that were defined non-static but only used within their own translation unit. (See static_prototype_fixer.py in the development notes.) * K&R-style empty-parens prototypes "()" replaced with explicit "(void)" across all asic_*.{c,h} files - GCC treats "()" as "any args" and refuses to match it against a separate K&R definition. * Added missing forward declarations to public headers: - fds.h (FDSLoad) - nsf.h (NSFLoad) - ines.h (iNESLoad) - unif.h (UNIFLoad) - latch.h (LatchHardReset, K&R fix) - eeprom_93Cx6.h (eeprom_93Cx6_read, K&R fix) Each header gained an "#include "file.h"" where needed. * fds_apu.c now includes its own fds_apu.h header (was missing). * fds_apu.h: removed unused FDSSoundRead declaration (the function is internal-static). * cartram.h: removed unused CartRAM_close declaration (the function is internal-static). * input.h: added a centralised block of FCEU_Init* prototypes (Zapper, Mouse, Powerpad, Arkanoid, VirtualBoy, FKB, SuborKB, PEC586KB, HS, Mahjong, FamilyTrainerA/B, OekaKids, TopRider, BarcodeWorld, BattleBox, QuizKing, FTrainerA/B, SpaceShadow, LCDCompZapper, ArkanoidFC) plus FCEU_ZapperSetTolerance. These were previously declared as "extern" inside src/input.c. * Static-ified FP_FASTAPASS callbacks in 106.c, 65.c, 67.c, asic_h3001.c, asic_vrc3.c (those with no external callers); left non-static for those that have header decls or are referenced from sibling .c files (asic_mmc1, asic_vrc6, asic_vrc7, flashrom). * For a small set of cross-file functions where adding a header was disproportionate to the value (MMC5_hb, NSFMMC5_Close, GetKeyboard, FCEU_GetJoyJoy), placed a forward declaration immediately above the definition. This satisfies -Wmissing-prototypes (which checks for any prior declaration in scope) without churning the public-header layout. 5. -Wshadow partial cleanup (not enabled permanently) Fixed five real shadows that were either bugs or actively misleading: * src/boards/476.c: removed an inner "int i" that shadowed the outer loop counter. * src/boards/mmc5.c MMC5_hb: parameter "scanline" renamed to "sl_param" (was shadowing the global "scanline"). * src/boards/n106.c DoNamcoSound: parameter "Wave" renamed to "WaveBuf" (was shadowing the global Wave audio buffer); also updated the forward declaration and the matching parameter on sound.h's NeoFill function pointer typedef. * src/boards/vrc7.c UpdateOPLNEO: same Wave -> WaveBuf rename. * src/ntsc/nes_ntsc_impl.h: renamed an inner loop counter "n" that shadowed an outer "n". * src/drivers/libretro/libretro.c FCEUD_RegionOverride: local "pal" renamed to "is_pal" (was shadowing the typedef "pal" from palette.h). * src/palette.c FCEUI_SetPaletteArray: parameter "pal" renamed to "data" (same shadow); driver.h declaration updated to match. -Wshadow itself is NOT enabled permanently because the remaining warnings are deliberate parameter naming conventions (XBuf in draw functions, X in cpu hooks) and third-party blargg ntsc code. In addition, four files were touched as part of an MSVC-build fix that came up mid-pass: src/fds.c, src/nsf.c, src/ines.c, and src/drivers/libretro/libretro_dipswitch.c had snprintf() calls introduced in pass 4 that fail to link on pre-MSVC2015 toolchains when STATIC_LINKING=1 (the libretro-common compat_snprintf.c shim isn't compiled in those configurations). Replaced each snprintf with either sprintf-into-bounded-buffer (the format strings have known maximum output) or strlcpy/strlcat for the dipswitch key-build case. All output is still bounded; truncation happens via strl*'s normal truncation semantics where applicable. All added code is C89-clean (top-of-block declarations only, no mixed decls, no // comments, INLINE macro from fceu-types.h instead of bare "inline"). Builds clean under -std=gnu11 with -Wno-write- strings -Wsign-compare -Wundef -Wmissing-prototypes; zero errors, zero warnings. Determinism audit (audit_determinism.py): no rand/time/long double/threads issues introduced.
2026-05-04 04:44:52 +02:00
static void FP_FASTAPASS(1) M106CpuHook(int a) {
if (IRQa) {
IRQCount += a;
if (IRQCount > 0x10000) {
X6502_IRQBegin(FCEU_IQEXT);
IRQa = 0;
}
}
}
static void StateRestore(int version) {
Sync();
}
void Mapper106_Init(CartInfo *info) {
info->Reset = M106Reset;
info->Power = M106Power;
info->Close = M106Close;
MapIRQHook = M106CpuHook;
GameStateRestore = StateRestore;
WRAMSIZE = 8192;
core: stdint typedefs, LE optimizations, frame determinism Three follow-up audit passes on top of the memory-safety / leak / savestate-portability work in 1185db8. ============================================================== Pass 1: replace custom typedefs with C99 stdint types throughout ============================================================== The custom uint8 / uint16 / uint32 / uint64 / int8 / int16 / int32 / int64 typedefs in src/fceu-types.h were just simple aliases for the C99 stdint.h types. Replace them with the standard names directly. - 498 files modified - ~3,400 token replacements (uint8 -> uint8_t, etc) - fceu-types.h slimmed down to just INLINE / GINLINE / FASTAPASS macros and the readfunc / writefunc function-pointer typedefs (those now use uint8_t / uint32_t natively) - Build clean on `make platform=unix` with zero new warnings - Output binary size unchanged - confirming semantic equivalence Mechanical replacement done with a Python script that uses word- boundary regex to avoid false positives (e.g. 'uint32_t' was correctly left alone because '_' is a word character so 'uint32' is not a complete word inside it). ================================================================ Pass 2: prefer memcpy on LE hosts for endian read/write helpers ================================================================ fceu-endian.c's write32le_mem, FCEU_en32lsb, and FCEU_de32lsb performed bytewise composition/decomposition unconditionally. On LE hosts the in-memory representation already matches the desired LE on-disk format, so a single memcpy is equivalent and lets the compiler emit a single load/store rather than four byte ops. - The bytewise path is kept inside #ifdef MSB_FIRST for BE hosts where it implements the actual byte swap - Both forms produce identical results; this is a code-clarity change more than a performance one (the optimizer was already merging the shifts on LE), but it documents the intent and removes a strict-aliasing-flavoured cast through *(uint32_t*)Bufo - Added missing #include <string.h> in fceu-endian.c which was relying on transitive includes for memcpy Other MSB_FIRST sites in the codebase (state.c FlipByteOrder guards, ppu.c sprite-line rendering, boards/unrom512.c flash-write- counter access) were already optimized for LE; they were verified correct rather than changed. ================================================================ Pass 3: frame determinism for replay and netplay ================================================================ Two libc rand() sites in core were replaced with a local xorshift32 PRNG so that NES games which read uninitialised memory or hit hardware "weak bit" emulation produce reproducible behaviour across runs. NES titles routinely read uninitialised RAM (variables not zeroed before use, sprite Y-position set by junk-on-stack), so the RAM contents at power-on subtly affect game behaviour. With libc rand(), those contents depend on whether anyone else seeded rand() in the same process - a different libretro frontend, a different audio backend init order, or any frontend that does srand(time(0)) all break replay / netplay frame-determinism. 1. fceu.c FCEU_MemoryRand. Used to fill RAM (PowerNES) and CHR-RAM (iNES_Init) at power-on when option_ramstate=2 (random init). Replaced with a local xorshift32 PRNG, exposed via a new FCEU_MemoryRand_Reseed(uint32_t) function called once per power-on: - PowerNES seeds from the first 4 bytes of GameInfo->MD5 (set by all loaders before PowerNES runs) so identical ROMs produce identical RAM, different ROMs differ - iNES_Init seeds from iNESCart.PRGCRC32 before the CHR-RAM fill so two builds of the same ROM get the same CHR-RAM - The PRNG state advances across multiple FCEU_MemoryRand calls within one power-on so RAM and CHR-RAM get different content (matching NES hardware reality) 2. boards/rt-01.c UNLRT01Read. The RT-01 board has 'weak bit' protected EPROM regions; reads of 0xCE80-0xCEFF and 0xFE80- 0xFEFF return 0xF2 with the low 3 bits randomised. Replaced libc rand() with a local xorshift32 seeded at power-on, and added the PRNG state to the savestate via AddExState with key "WBKS" so save / load / rewind / netplay rollback all stay deterministic. In addition, two long-double-to-int truncations were changed to double for cross-platform FP determinism: - sound.c SetSoundVariables: soundtsinc - boards/n106.c DoNamcoSound: inc long double has platform-dependent precision (80-bit on x87, 64-bit with -mfpmath=sse, 128-bit on PowerPC), so the truncated integer result varied across these platforms. double is guaranteed 64-bit IEEE-754 portably. After this pass, the core has no time(), clock(), gettimeofday(), clock_gettime(), getpid(), getuid(), getgid(), getenv(), gethostid(), pthread, std::thread, OpenMP, signal handler, or non-deterministic- malloc dependency. Verified with a Python scanner that greps the source for these patterns; runs clean. The PPU / APU / CPU power-on already explicitly memset all state buffers to 0 (deterministic), and ROM/CHR-ROM allocation already memsets to 0xFF before partial fread (deterministic regardless of file truncation). Combined with the memory-safety hardening in 1185db8 (which prevents savestate-loaded indices from going out-of-bounds and producing unpredictable behaviour), the core now offers genuine frame-deterministic replay across runs, builds, and host endian.
2026-05-04 02:46:34 +02:00
WRAM = (uint8_t*)FCEU_gmalloc(WRAMSIZE);
SetupCartPRGMapping(0x10, WRAM, WRAMSIZE, 1);
AddExState(WRAM, WRAMSIZE, 0, "WRAM");
AddExState(&StateRegs, ~0, 0, 0);
}