2014-03-30 22:15:17 +02:00
|
|
|
/* FCE Ultra - NES/Famicom Emulator
|
|
|
|
|
*
|
|
|
|
|
* Copyright notice for this file:
|
|
|
|
|
* Copyright (C) 2002 Xodnizel
|
|
|
|
|
*
|
|
|
|
|
* 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 <string.h>
|
|
|
|
|
|
2016-02-22 18:36:09 +01:00
|
|
|
#include "fceu.h"
|
2014-03-30 22:15:17 +02:00
|
|
|
#include "fceu-types.h"
|
|
|
|
|
#include "x6502.h"
|
|
|
|
|
#include "fceu.h"
|
|
|
|
|
#include "sound.h"
|
|
|
|
|
|
|
|
|
|
X6502 X;
|
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
|
|
|
uint8_t encryptOpcodes =0;
|
2014-03-30 22:15:17 +02:00
|
|
|
|
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
|
|
|
uint32_t timestamp;
|
|
|
|
|
uint32_t sound_timestamp;
|
2014-03-30 22:15:17 +02:00
|
|
|
void FP_FASTAPASS(1) (*MapIRQHook)(int a);
|
|
|
|
|
|
|
|
|
|
#define _PC X.PC
|
|
|
|
|
#define _A X.A
|
|
|
|
|
#define _X X.X
|
|
|
|
|
#define _Y X.Y
|
|
|
|
|
#define _S X.S
|
|
|
|
|
#define _P X.P
|
|
|
|
|
#define _PI X.mooPI
|
|
|
|
|
#define _DB X.DB
|
|
|
|
|
#define _count X.count
|
|
|
|
|
#define _tcount X.tcount
|
|
|
|
|
#define _IRQlow X.IRQlow
|
|
|
|
|
#define _jammed X.jammed
|
|
|
|
|
|
|
|
|
|
#define ADDCYC(x) { \
|
|
|
|
|
int __x = x; \
|
|
|
|
|
_tcount += __x; \
|
|
|
|
|
_count -= __x * 48; \
|
2017-08-24 20:58:04 +08:00
|
|
|
timestamp += __x; \
|
2021-06-05 15:05:07 +02:00
|
|
|
if (!overclocked) sound_timestamp += __x; \
|
2014-03-30 22:15:17 +02:00
|
|
|
}
|
|
|
|
|
|
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 INLINE uint8_t RdMemNorm(uint32_t A) {
|
2014-03-30 22:15:17 +02:00
|
|
|
return(_DB = ARead[A](A));
|
|
|
|
|
}
|
|
|
|
|
|
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 INLINE void WrMemNorm(uint32_t A, uint8_t V) {
|
2014-03-30 22:15:17 +02:00
|
|
|
BWrite[A](A, V);
|
|
|
|
|
}
|
|
|
|
|
|
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 INLINE uint8_t RdRAMFast(uint32_t A) {
|
2014-03-30 22:15:17 +02:00
|
|
|
return(_DB = RAM[A]);
|
|
|
|
|
}
|
|
|
|
|
|
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 INLINE void WrRAMFast(uint32_t A, uint8_t V) {
|
2014-03-30 22:15:17 +02:00
|
|
|
RAM[A] = V;
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
uint8_t FASTAPASS(1) X6502_DMR(uint32_t A) {
|
2014-03-30 22:15:17 +02:00
|
|
|
ADDCYC(1);
|
|
|
|
|
return(X.DB = ARead[A](A));
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
void FASTAPASS(2) X6502_DMW(uint32_t A, uint8_t V) {
|
2014-03-30 22:15:17 +02:00
|
|
|
ADDCYC(1);
|
|
|
|
|
BWrite[A](A, V);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define PUSH(V) { \
|
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
|
|
|
uint8_t VTMP = V; \
|
2014-03-30 22:15:17 +02:00
|
|
|
WrRAM(0x100 + _S, VTMP); \
|
|
|
|
|
_S--; \
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define POP() RdRAM(0x100 + (++_S))
|
|
|
|
|
|
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 ZNTable[256];
|
2017-10-15 03:13:11 +08:00
|
|
|
/* Some of these operations will only make sense if you know what the flag constants are. */
|
2014-03-30 22:15:17 +02:00
|
|
|
|
|
|
|
|
#define X_ZN(zort) _P &= ~(Z_FLAG | N_FLAG); _P |= ZNTable[zort]
|
|
|
|
|
#define X_ZNT(zort) _P |= ZNTable[zort]
|
|
|
|
|
|
|
|
|
|
#define JR(cond) { \
|
|
|
|
|
if (cond) \
|
|
|
|
|
{ \
|
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
|
|
|
uint32_t tmp; \
|
|
|
|
|
int32_t disp; \
|
|
|
|
|
disp = (int8_t)RdMem(_PC); \
|
2014-03-30 22:15:17 +02:00
|
|
|
_PC++; \
|
|
|
|
|
ADDCYC(1); \
|
|
|
|
|
tmp = _PC; \
|
|
|
|
|
_PC += disp; \
|
|
|
|
|
if ((tmp ^ _PC) & 0x100) \
|
|
|
|
|
ADDCYC(1); \
|
|
|
|
|
} else _PC++; \
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define LDA _A = x; X_ZN(_A)
|
|
|
|
|
#define LDX _X = x; X_ZN(_X)
|
|
|
|
|
#define LDY _Y = x; X_ZN(_Y)
|
|
|
|
|
|
2017-10-15 03:13:11 +08:00
|
|
|
/* All of the freaky arithmetic operations. */
|
2014-03-30 22:15:17 +02:00
|
|
|
#define AND _A &= x; X_ZN(_A)
|
|
|
|
|
#define BIT _P &= ~(Z_FLAG | V_FLAG | N_FLAG); _P |= ZNTable[x & _A] & Z_FLAG; _P |= x & (V_FLAG | N_FLAG)
|
|
|
|
|
#define EOR _A ^= x; X_ZN(_A)
|
|
|
|
|
#define ORA _A |= x; X_ZN(_A)
|
|
|
|
|
|
|
|
|
|
#define ADC { \
|
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
|
|
|
uint32_t l = _A + x + (_P & 1); \
|
2014-03-30 22:15:17 +02:00
|
|
|
_P &= ~(Z_FLAG | C_FLAG | N_FLAG | V_FLAG); \
|
|
|
|
|
_P |= ((((_A ^ x) & 0x80) ^ 0x80) & ((_A ^ l) & 0x80)) >> 1; \
|
|
|
|
|
_P |= (l >> 8) & C_FLAG; \
|
|
|
|
|
_A = l; \
|
|
|
|
|
X_ZNT(_A); \
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define SBC { \
|
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
|
|
|
uint32_t l = _A - x - ((_P & 1) ^ 1); \
|
2014-03-30 22:15:17 +02:00
|
|
|
_P &= ~(Z_FLAG | C_FLAG | N_FLAG | V_FLAG); \
|
|
|
|
|
_P |= ((_A ^ l) & (_A ^ x) & 0x80) >> 1; \
|
|
|
|
|
_P |= ((l >> 8) & C_FLAG) ^ C_FLAG; \
|
|
|
|
|
_A = l; \
|
|
|
|
|
X_ZNT(_A); \
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define CMPL(a1, a2) { \
|
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
|
|
|
uint32_t t = a1 - a2; \
|
2014-03-30 22:15:17 +02:00
|
|
|
X_ZN(t & 0xFF); \
|
|
|
|
|
_P &= ~C_FLAG; \
|
|
|
|
|
_P |= ((t >> 8) & C_FLAG) ^ C_FLAG; \
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-15 03:13:11 +08:00
|
|
|
/* Special undocumented operation. Very similar to CMP. */
|
2014-03-30 22:15:17 +02:00
|
|
|
#define AXS { \
|
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
|
|
|
uint32_t t = (_A & _X) - x; \
|
2014-03-30 22:15:17 +02:00
|
|
|
X_ZN(t & 0xFF); \
|
|
|
|
|
_P &= ~C_FLAG; \
|
|
|
|
|
_P |= ((t >> 8) & C_FLAG) ^ C_FLAG; \
|
|
|
|
|
_X = t; \
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define CMP CMPL(_A, x)
|
|
|
|
|
#define CPX CMPL(_X, x)
|
|
|
|
|
#define CPY CMPL(_Y, x)
|
|
|
|
|
|
2017-10-15 03:13:11 +08:00
|
|
|
/* The following operations modify the byte being worked on. */
|
2014-03-30 22:15:17 +02:00
|
|
|
#define DEC x--; X_ZN(x)
|
|
|
|
|
#define INC x++; X_ZN(x)
|
|
|
|
|
|
|
|
|
|
#define ASL _P &= ~C_FLAG; _P |= x >> 7; x <<= 1; X_ZN(x)
|
|
|
|
|
#define LSR _P &= ~(C_FLAG | N_FLAG | Z_FLAG); _P |= x & 1; x >>= 1; X_ZNT(x)
|
|
|
|
|
|
2017-10-15 03:13:11 +08:00
|
|
|
/* For undocumented instructions, maybe for other things later... */
|
2014-03-30 22:15:17 +02:00
|
|
|
#define LSRA _P &= ~(C_FLAG | N_FLAG | Z_FLAG); _P |= _A & 1; _A >>= 1; X_ZNT(_A)
|
|
|
|
|
|
|
|
|
|
#define ROL { \
|
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
|
|
|
uint8_t l = x >> 7; \
|
2014-03-30 22:15:17 +02:00
|
|
|
x <<= 1; \
|
|
|
|
|
x |= _P & C_FLAG; \
|
|
|
|
|
_P &= ~(Z_FLAG | N_FLAG | C_FLAG); \
|
|
|
|
|
_P |= l; \
|
|
|
|
|
X_ZNT(x); \
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define ROR { \
|
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
|
|
|
uint8_t l = x & 1; \
|
2014-03-30 22:15:17 +02:00
|
|
|
x >>= 1; \
|
|
|
|
|
x |= (_P & C_FLAG) << 7; \
|
|
|
|
|
_P &= ~(Z_FLAG | N_FLAG | C_FLAG); \
|
|
|
|
|
_P |= l; \
|
|
|
|
|
X_ZNT(x); \
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-15 03:13:11 +08:00
|
|
|
/* Icky icky thing for some undocumented instructions. Can easily be
|
|
|
|
|
* broken if names of local variables are changed.
|
|
|
|
|
*/
|
2014-03-30 22:15:17 +02:00
|
|
|
|
2017-10-15 03:13:11 +08:00
|
|
|
/* Absolute */
|
2014-03-30 22:15:17 +02:00
|
|
|
#define GetAB(target) { \
|
|
|
|
|
target = RdMem(_PC); \
|
|
|
|
|
_PC++; \
|
|
|
|
|
target |= RdMem(_PC) << 8; \
|
|
|
|
|
_PC++; \
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-15 03:13:11 +08:00
|
|
|
/* Absolute Indexed(for reads) */
|
2014-03-30 22:15:17 +02:00
|
|
|
#define GetABIRD(target, i) { \
|
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
|
|
|
uint32_t tmp; \
|
2014-03-30 22:15:17 +02:00
|
|
|
GetAB(tmp); \
|
|
|
|
|
target = tmp; \
|
|
|
|
|
target += i; \
|
|
|
|
|
if ((target ^ tmp) & 0x100) { \
|
|
|
|
|
target &= 0xFFFF; \
|
|
|
|
|
RdMem(target ^ 0x100); \
|
|
|
|
|
ADDCYC(1); \
|
|
|
|
|
} \
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-15 03:13:11 +08:00
|
|
|
/* Absolute Indexed(for writes and rmws) */
|
2014-03-30 22:15:17 +02:00
|
|
|
#define GetABIWR(target, i) { \
|
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
|
|
|
uint32_t rt; \
|
2014-03-30 22:15:17 +02:00
|
|
|
GetAB(rt); \
|
|
|
|
|
target = rt; \
|
|
|
|
|
target += i; \
|
|
|
|
|
target &= 0xFFFF; \
|
|
|
|
|
RdMem((target & 0x00FF) | (rt & 0xFF00)); \
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-15 03:13:11 +08:00
|
|
|
/* Zero Page */
|
2014-03-30 22:15:17 +02:00
|
|
|
#define GetZP(target) { \
|
|
|
|
|
target = RdMem(_PC); \
|
|
|
|
|
_PC++; \
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-15 03:13:11 +08:00
|
|
|
/* Zero Page Indexed */
|
2014-03-30 22:15:17 +02:00
|
|
|
#define GetZPI(target, i) { \
|
|
|
|
|
target = i + RdMem(_PC); \
|
|
|
|
|
_PC++; \
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-15 03:13:11 +08:00
|
|
|
/* Indexed Indirect */
|
2014-03-30 22:15:17 +02:00
|
|
|
#define GetIX(target) { \
|
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
|
|
|
uint8_t tmp; \
|
2014-03-30 22:15:17 +02:00
|
|
|
tmp = RdMem(_PC); \
|
|
|
|
|
_PC++; \
|
|
|
|
|
tmp += _X; \
|
|
|
|
|
target = RdRAM(tmp); \
|
|
|
|
|
tmp++; \
|
|
|
|
|
target |= RdRAM(tmp) << 8; \
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-15 03:13:11 +08:00
|
|
|
/* Indirect Indexed(for reads) */
|
2014-03-30 22:15:17 +02:00
|
|
|
#define GetIYRD(target) { \
|
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
|
|
|
uint32_t rt; \
|
|
|
|
|
uint8_t tmp; \
|
2014-03-30 22:15:17 +02:00
|
|
|
tmp = RdMem(_PC); \
|
|
|
|
|
_PC++; \
|
|
|
|
|
rt = RdRAM(tmp); \
|
|
|
|
|
tmp++; \
|
|
|
|
|
rt |= RdRAM(tmp) << 8; \
|
|
|
|
|
target = rt; \
|
|
|
|
|
target += _Y; \
|
|
|
|
|
if ((target ^ rt) & 0x100) { \
|
|
|
|
|
target &= 0xFFFF; \
|
|
|
|
|
RdMem(target ^ 0x100); \
|
|
|
|
|
ADDCYC(1); \
|
|
|
|
|
} \
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-15 03:13:11 +08:00
|
|
|
/* Indirect Indexed(for writes and rmws) */
|
2014-03-30 22:15:17 +02:00
|
|
|
#define GetIYWR(target) { \
|
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
|
|
|
uint32_t rt; \
|
|
|
|
|
uint8_t tmp; \
|
2014-03-30 22:15:17 +02:00
|
|
|
tmp = RdMem(_PC); \
|
|
|
|
|
_PC++; \
|
|
|
|
|
rt = RdRAM(tmp); \
|
|
|
|
|
tmp++; \
|
|
|
|
|
rt |= RdRAM(tmp) << 8; \
|
|
|
|
|
target = rt; \
|
|
|
|
|
target += _Y; \
|
|
|
|
|
target &= 0xFFFF; \
|
|
|
|
|
RdMem((target & 0x00FF) | (rt & 0xFF00)); \
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Now come the macros to wrap up all of the above stuff addressing mode functions
|
|
|
|
|
and operation macros. Note that operation macros will always operate(redundant
|
|
|
|
|
redundant) on the variable "x".
|
|
|
|
|
*/
|
|
|
|
|
|
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
|
|
|
#define RMW_A(op) { uint8_t x = _A; op; _A = x; break; } /* Meh... */
|
|
|
|
|
#define RMW_AB(op) { uint32_t A; uint8_t x; GetAB(A); x = RdMem(A); WrMem(A, x); op; WrMem(A, x); break; }
|
|
|
|
|
#define RMW_ABI(reg, op) { uint32_t A; uint8_t x; GetABIWR(A, reg); x = RdMem(A); WrMem(A, x); op; WrMem(A, x); break; }
|
2014-03-30 22:15:17 +02:00
|
|
|
#define RMW_ABX(op) RMW_ABI(_X, op)
|
|
|
|
|
#define RMW_ABY(op) RMW_ABI(_Y, op)
|
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
|
|
|
#define RMW_IX(op) { uint32_t A; uint8_t x; GetIX(A); x = RdMem(A); WrMem(A, x); op; WrMem(A, x); break; }
|
|
|
|
|
#define RMW_IY(op) { uint32_t A; uint8_t x; GetIYWR(A); x = RdMem(A); WrMem(A, x); op; WrMem(A, x); break; }
|
|
|
|
|
#define RMW_ZP(op) { uint8_t A; uint8_t x; GetZP(A); x = RdRAM(A); op; WrRAM(A, x); break; }
|
|
|
|
|
#define RMW_ZPX(op) { uint8_t A; uint8_t x; GetZPI(A, _X); x = RdRAM(A); op; WrRAM(A, x); break; }
|
|
|
|
|
|
|
|
|
|
#define LD_IM(op) { uint8_t x; x = RdMem(_PC); _PC++; op; break; }
|
|
|
|
|
#define LD_ZP(op) { uint8_t A; uint8_t x; GetZP(A); x = RdRAM(A); op; break; }
|
|
|
|
|
#define LD_ZPX(op) { uint8_t A; uint8_t x; GetZPI(A, _X); x = RdRAM(A); op; break; }
|
|
|
|
|
#define LD_ZPY(op) { uint8_t A; uint8_t x; GetZPI(A, _Y); x = RdRAM(A); op; break; }
|
|
|
|
|
#define LD_AB(op) { uint32_t A; uint8_t x; GetAB(A); x = RdMem(A); op; break; }
|
|
|
|
|
#define LD_ABI(reg, op) { uint32_t A; uint8_t x; GetABIRD(A, reg); x = RdMem(A); op; break; }
|
2014-03-30 22:15:17 +02:00
|
|
|
#define LD_ABX(op) LD_ABI(_X, op)
|
|
|
|
|
#define LD_ABY(op) LD_ABI(_Y, op)
|
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
|
|
|
#define LD_IX(op) { uint32_t A; uint8_t x; GetIX(A); x = RdMem(A); op; break; }
|
|
|
|
|
#define LD_IY(op) { uint32_t A; uint8_t x; GetIYRD(A); x = RdMem(A); op; break; }
|
|
|
|
|
|
|
|
|
|
#define ST_ZP(r) { uint8_t A; GetZP(A); WrRAM(A, r); break; }
|
|
|
|
|
#define ST_ZPX(r) { uint8_t A; GetZPI(A, _X); WrRAM(A, r); break; }
|
|
|
|
|
#define ST_ZPY(r) { uint8_t A; GetZPI(A, _Y); WrRAM(A, r); break; }
|
|
|
|
|
#define ST_AB(r) { uint32_t A; GetAB(A); WrMem(A, r); break; }
|
|
|
|
|
#define ST_ABI(reg, r) { uint32_t A; GetABIWR(A, reg); WrMem(A, r); break; }
|
2014-03-30 22:15:17 +02:00
|
|
|
#define ST_ABX(r) ST_ABI(_X, r)
|
|
|
|
|
#define ST_ABY(r) ST_ABI(_Y, r)
|
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
|
|
|
#define ST_IX(r) { uint32_t A; GetIX(A); WrMem(A, r); break; }
|
|
|
|
|
#define ST_IY(r) { uint32_t A; GetIYWR(A); WrMem(A, r); break; }
|
2014-03-30 22:15:17 +02:00
|
|
|
|
core: OOM safety, sign-compare cleanup, dead-code removal, const-correctness
Omnibus cleanup pass. Build is clean on `make platform=unix` with
zero errors and zero warnings, including under
`-Wsign-compare -Wstrict-aliasing=2 -Wcast-align`. The
`-Wsign-compare` flag is now permanently enabled in
WARNING_DEFINES.
================================================================
A. FCEU_gmalloc no longer exit()s on OOM
================================================================
A libretro core must not call exit(): doing so tears down the entire
frontend. FCEU_gmalloc previously did exactly that on allocation
failure ("Doing a hard exit"). It now returns NULL with the same
diagnostic message.
Loader-level call sites that can fail their parent function (i.e.
return 0 from FDSLoad/iNESLoad/NSFLoad to refuse the cart) now check
the return value:
- ines.c: trainerpoo, ExtraNTARAM
- nsf.c: ExWRAM (both branches)
- fds.c: FDSBIOS, CHRRAM, FDSRAM
Mapper-level callers (~200 sites) are intentionally left as-is:
they live in void-returning Init/Power functions where graceful
failure isn't possible without a much larger restructuring. With
NULL returns those mappers will null-deref on first access, which
is contained to the core - the libretro frontend stays up. This is
strictly better than the previous behaviour of exit()ing the entire
frontend.
================================================================
B. -Wsign-compare cleanup (27 warnings -> 0)
================================================================
Surveyed every signed/unsigned comparison the compiler flagged and
fixed each one. Most fell into a few patterns:
Loop variables: changed `int x` to `uint32_t x` for loops over
uint32_t counts (TotalSides in fds.c, the trainer-copy loop in
6_8_12_17_561_562.c, soundOffset->SOUNDTS in 594.c).
scanline (signed) vs totalscanlines/normal_scanlines (unsigned)
in ppu.c: cast (unsigned)scanline at the comparison sites. The
scanline values are guaranteed >= 0 at every comparison site
(the loop initialises scanline=0 and increments).
rate_adjust macro in emu2413.c: the ?: was returning either
signed or unsigned depending on `rate`. Cast both branches to
uint32_t.
Mixed ?: branches in cartram.c (PRGRamSaveSize signed vs
WRAMSize unsigned): cast PRGRamSaveSize to uint32_t at use.
Other one-off casts in libretro.c, n625092.c, nsf.c, sound.c,
zapper.c, eeprom_93Cx6.c.
The Makefile change keeps -Wsign-compare permanently enabled so
new sign-compare bugs trip CI immediately.
Files touched: 594.c, 6_8_12_17_561_562.c, cartram.c,
eeprom_93Cx6.c, emu2413.c, n625092.c, fds.c, nsf.c, ppu.c,
sound.c, input/zapper.c, drivers/libretro/libretro.c, plus
Makefile.libretro.
================================================================
C. NULL-deref hardening on libretro callbacks
================================================================
retro_serialize and retro_unserialize now reject NULL data
pointers before passing them to memstream_set_buffer (which
would have null-deref'd inside the memstream code).
Other retro_* entry points that take pointers were already guarded
in earlier passes (retro_set_controller_port_device,
retro_get_memory_data, retro_get_memory_size, retro_load_game,
retro_cheat_set).
================================================================
D. Mapper coverage spot-check
================================================================
Wrote a heuristic scanner to find the savestate-load-array-index
bug class fixed in pass 1 (variable masked at write but unmasked
at restore -> OOB index). Scanner flagged 3 candidates across 424
mappers; manual review confirmed all three are false positives:
- sachen.c `cmd`: theoretical bug, but only one writer is wired
per game and that writer masks at use.
- unrom512.c `flash_state` and `latcha`: both already clamped
in StateRestore (added in pass 1).
The systematic bug class was thoroughly addressed in pass 1.
================================================================
E. Strip never-defined #ifdef symbols
================================================================
Surveyed every #ifdef symbol in the build and cross-checked
against #defines (in source and in build files). Removed code
gated on symbols that are never defined for any platform target:
- DEBUG_MAPPER (datalatch.c, 2 sites): a debug-print NROMWrite
handler. Dead.
- FRAMESKIP (fceu.h, driver.h, ppu.c, 4 sites): a legacy-FCEU
frameskip path. The libretro driver's `skip` argument to
FCEUI_Emulate is always 0, and FCEUI_FrameSkip is never
called by any libretro frontend. Removed the conditional
rendering branch in FCEUPPU_Loop along with the
FCEU_PutImageDummy declaration.
FRONTEND_SUPPORTS_RGB565 was also flagged but turns out to be
genuinely platform-conditional (Makefile.common defines it when
WANT_32BPP=0). Kept.
================================================================
F. assert() audit
================================================================
Two assert() calls live in src/ntsc/nes_ntsc_impl.h - third-party
NTSC filter code from blargg, both NaN sanity checks
(`assert(x == x)`). NDEBUG is in the build flags so they compile
to no-ops. No fceumm code uses assert. Nothing to do here.
================================================================
G. const-correctness
================================================================
Function signatures that take strings they don't modify now take
const char *:
FCEU_printf, FCEU_PrintError (const char *format)
FCEUD_PrintError, FCEUD_Message (const char *)
FCEU_MakeFName (const char *cd1)
md5_update (const uint8_t *input)
md5_process (const uint8_t data[64])
also made `static`
================================================================
H. Const-fold static lookup tables
================================================================
Marked static lookup tables const where they are never written:
x6502.c: CycTable[256] (cycle counts)
md5.c: md5_padding[64] (MD5 padding)
vsuni.c: secdata[2][32], secptr (VS security data)
palette.c: rtmul/gtmul/btmul[7] (palette multipliers)
input/cursor.c: GunSight, FCEUcursor (sprite data)
input/pec586kb.c, fkb.c, suborkb.c: matrix (key matrices)
boards/8237.c: regperm, adrperm, protarray (mapper perms)
boards/datalatch.c: M538Banks (bank table)
boards/187.c: prot_data
boards/121.c: prot_array
boards/bonza.c: sim0reset
boards/pec-586.c: bs_tbl, br_tbl
boards/178.c: step_size, step_adj (ADPCM tables)
boards/244.c: prg_perm, chr_perm
boards/bmc42in1r.c: banks
boards/emu2413.c: SL (sustain levels)
NSFROM in nsf.c looks like a lookup table but is rewritten at
runtime to patch in addresses, so it stays mutable.
================================================================
I. Reduce strlen calls
================================================================
Replaced `strlen(STRING_LITERAL)` with `sizeof(STRING_LITERAL) - 1`
where the argument is a compile-time-known string literal:
- libretro.c retro_set_environment APU loop: was calling
strlen("fceumm_apu_") on every loop iteration to compute the
same constant offset.
- nsf.c visualizer: strlen("Song:").
Other strlen sites in cheat.c, libretro.c, unif.c either already
cache to a local size_t or operate on runtime-supplied strings
where caching would not help.
================================================================
Build status
================================================================
`make platform=unix` clean: zero errors, zero warnings.
With -Wsign-compare -Wstrict-aliasing=2 -Wcast-align: zero warnings.
audit_determinism.py: 0 issues.
Output binary 4,388,408 bytes (was 4,388,576 from upstream
004c147; -168 bytes from dead-code removal).
2026-05-04 03:55:23 +02:00
|
|
|
static const uint8_t CycTable[256] =
|
2014-03-30 22:15:17 +02:00
|
|
|
{
|
|
|
|
|
/*0x00*/ 7, 6, 2, 8, 3, 3, 5, 5, 3, 2, 2, 2, 4, 4, 6, 6,
|
|
|
|
|
/*0x10*/ 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
|
|
|
|
|
/*0x20*/ 6, 6, 2, 8, 3, 3, 5, 5, 4, 2, 2, 2, 4, 4, 6, 6,
|
|
|
|
|
/*0x30*/ 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
|
|
|
|
|
/*0x40*/ 6, 6, 2, 8, 3, 3, 5, 5, 3, 2, 2, 2, 3, 4, 6, 6,
|
|
|
|
|
/*0x50*/ 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
|
|
|
|
|
/*0x60*/ 6, 6, 2, 8, 3, 3, 5, 5, 4, 2, 2, 2, 5, 4, 6, 6,
|
|
|
|
|
/*0x70*/ 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
|
|
|
|
|
/*0x80*/ 2, 6, 2, 6, 3, 3, 3, 3, 2, 2, 2, 2, 4, 4, 4, 4,
|
|
|
|
|
/*0x90*/ 2, 6, 2, 6, 4, 4, 4, 4, 2, 5, 2, 5, 5, 5, 5, 5,
|
|
|
|
|
/*0xA0*/ 2, 6, 2, 6, 3, 3, 3, 3, 2, 2, 2, 2, 4, 4, 4, 4,
|
|
|
|
|
/*0xB0*/ 2, 5, 2, 5, 4, 4, 4, 4, 2, 4, 2, 4, 4, 4, 4, 4,
|
|
|
|
|
/*0xC0*/ 2, 6, 2, 8, 3, 3, 5, 5, 2, 2, 2, 2, 4, 4, 6, 6,
|
|
|
|
|
/*0xD0*/ 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
|
|
|
|
|
/*0xE0*/ 2, 6, 2, 8, 3, 3, 5, 5, 2, 2, 2, 2, 4, 4, 6, 6,
|
|
|
|
|
/*0xF0*/ 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void FASTAPASS(1) X6502_IRQBegin(int w) {
|
|
|
|
|
_IRQlow |= w;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FASTAPASS(1) X6502_IRQEnd(int w) {
|
|
|
|
|
_IRQlow &= ~w;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TriggerNMI(void) {
|
|
|
|
|
_IRQlow |= FCEU_IQNMI;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TriggerNMI2(void) {
|
|
|
|
|
_IRQlow |= FCEU_IQNMI2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X6502_Reset(void) {
|
|
|
|
|
_IRQlow = FCEU_IQRESET;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X6502_Init(void) {
|
|
|
|
|
int x;
|
|
|
|
|
|
|
|
|
|
memset((void*)&X, 0, sizeof(X));
|
|
|
|
|
for (x = 0; x < 256; x++)
|
|
|
|
|
if (!x)
|
|
|
|
|
ZNTable[x] = Z_FLAG;
|
|
|
|
|
else if (x & 0x80)
|
|
|
|
|
ZNTable[x] = N_FLAG;
|
|
|
|
|
else
|
|
|
|
|
ZNTable[x] = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X6502_Power(void) {
|
|
|
|
|
_count = _tcount = _IRQlow = _PC = _A = _X = _Y = _P = _PI = _DB = _jammed = 0;
|
|
|
|
|
_S = 0xFD;
|
2017-08-24 20:58:04 +08:00
|
|
|
timestamp = sound_timestamp = 0;
|
2014-03-30 22:15:17 +02:00
|
|
|
X6502_Reset();
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
void X6502_Run(int32_t cycles)
|
2014-03-30 22:15:17 +02:00
|
|
|
{
|
|
|
|
|
#define RdRAM RdRAMFast
|
|
|
|
|
#define WrRAM WrRAMFast
|
|
|
|
|
#define RdMem RdMemNorm
|
|
|
|
|
#define WrMem WrMemNorm
|
|
|
|
|
|
|
|
|
|
#if (defined(C80x86) && defined(__GNUC__))
|
2017-10-15 03:13:11 +08:00
|
|
|
/* Gives a nice little speed boost. */
|
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
|
|
|
register uint16_t pbackus asm ("edi");
|
2014-03-30 22:15:17 +02:00
|
|
|
#else
|
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
|
|
|
uint16_t pbackus;
|
2014-03-30 22:15:17 +02:00
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
pbackus = _PC;
|
|
|
|
|
|
|
|
|
|
#undef _PC
|
|
|
|
|
#define _PC pbackus
|
|
|
|
|
|
|
|
|
|
if (PAL)
|
2017-10-15 03:13:11 +08:00
|
|
|
cycles *= 15; /* 15*4=60 */
|
2014-03-30 22:15:17 +02:00
|
|
|
else
|
2017-10-15 03:13:11 +08:00
|
|
|
cycles *= 16; /* 16*4=64 */
|
2014-03-30 22:15:17 +02:00
|
|
|
|
|
|
|
|
_count += cycles;
|
|
|
|
|
|
|
|
|
|
while (_count > 0) {
|
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
|
|
|
int32_t temp;
|
|
|
|
|
uint8_t b1;
|
2014-03-30 22:15:17 +02:00
|
|
|
|
|
|
|
|
if (_IRQlow) {
|
|
|
|
|
if (_IRQlow & FCEU_IQRESET) {
|
|
|
|
|
_PC = RdMem(0xFFFC);
|
|
|
|
|
_PC |= RdMem(0xFFFD) << 8;
|
|
|
|
|
_jammed = 0;
|
|
|
|
|
_PI = _P = I_FLAG;
|
|
|
|
|
_IRQlow &= ~FCEU_IQRESET;
|
|
|
|
|
} else if (_IRQlow & FCEU_IQNMI2) {
|
|
|
|
|
_IRQlow &= ~FCEU_IQNMI2;
|
|
|
|
|
_IRQlow |= FCEU_IQNMI;
|
|
|
|
|
} else if (_IRQlow & FCEU_IQNMI) {
|
|
|
|
|
if (!_jammed) {
|
|
|
|
|
ADDCYC(7);
|
|
|
|
|
PUSH(_PC >> 8);
|
|
|
|
|
PUSH(_PC);
|
|
|
|
|
PUSH((_P & ~B_FLAG) | (U_FLAG));
|
|
|
|
|
_P |= I_FLAG;
|
|
|
|
|
_PC = RdMem(0xFFFA);
|
|
|
|
|
_PC |= RdMem(0xFFFB) << 8;
|
|
|
|
|
_IRQlow &= ~FCEU_IQNMI;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (!(_PI & I_FLAG) && !_jammed) {
|
|
|
|
|
ADDCYC(7);
|
|
|
|
|
PUSH(_PC >> 8);
|
|
|
|
|
PUSH(_PC);
|
|
|
|
|
PUSH((_P & ~B_FLAG) | (U_FLAG));
|
|
|
|
|
_P |= I_FLAG;
|
|
|
|
|
_PC = RdMem(0xFFFE);
|
|
|
|
|
_PC |= RdMem(0xFFFF) << 8;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_IRQlow &= ~(FCEU_IQTEMP);
|
|
|
|
|
if (_count <= 0) {
|
|
|
|
|
_PI = _P;
|
|
|
|
|
X.PC = pbackus;
|
|
|
|
|
return;
|
2017-10-15 03:13:11 +08:00
|
|
|
} /* Should increase accuracy without a
|
|
|
|
|
* major speed hit.
|
|
|
|
|
*/
|
2014-03-30 22:15:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_PI = _P;
|
|
|
|
|
b1 = RdMem(_PC);
|
|
|
|
|
|
|
|
|
|
ADDCYC(CycTable[b1]);
|
|
|
|
|
|
|
|
|
|
temp = _tcount;
|
|
|
|
|
_tcount = 0;
|
|
|
|
|
if (MapIRQHook) MapIRQHook(temp);
|
2021-06-05 15:05:07 +02:00
|
|
|
if (!overclocked)
|
2017-03-09 17:49:28 +08:00
|
|
|
FCEU_SoundCPUHook(temp);
|
2014-03-30 22:15:17 +02:00
|
|
|
X.PC = pbackus;
|
|
|
|
|
_PC++;
|
2025-04-25 01:01:56 +02:00
|
|
|
if (encryptOpcodes ==12) b1 =b1 &0x39 | b1 >>1 &0x42 | b1 <<1 &0x84;
|
|
|
|
|
if (encryptOpcodes ==14) b1 =b1 &0x3F | b1 >>1 &0x40 | b1 <<1 &0x80;
|
2014-03-30 22:15:17 +02:00
|
|
|
switch (b1) {
|
|
|
|
|
#include "ops.h"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#undef _PC
|
|
|
|
|
#define _PC X.PC
|
|
|
|
|
_PC = pbackus;
|
|
|
|
|
#undef RdRAM
|
|
|
|
|
#undef WrRAM
|
|
|
|
|
}
|