2014-03-30 22:15:17 +02:00
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdint.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
2014-03-30 22:35:00 +02:00
|
|
|
#include <stdarg.h>
|
2020-09-09 11:51:04 +08:00
|
|
|
#include <ctype.h>
|
2014-03-30 22:15:17 +02:00
|
|
|
|
2021-10-20 16:31:45 +01:00
|
|
|
#ifdef _MSC_VER
|
|
|
|
|
#include <compat/msvc.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
2017-12-16 18:41:41 +01:00
|
|
|
#include <libretro.h>
|
2021-10-20 16:31:45 +01:00
|
|
|
#include <string/stdstring.h>
|
|
|
|
|
#include <file/file_path.h>
|
|
|
|
|
#include <streams/file_stream.h>
|
2020-05-05 13:35:30 +08:00
|
|
|
#include <streams/memory_stream.h>
|
|
|
|
|
#include <libretro_dipswitch.h>
|
|
|
|
|
#include <libretro_core_options.h>
|
2014-03-30 22:15:17 +02:00
|
|
|
|
2014-03-30 22:35:00 +02:00
|
|
|
#include "../../fceu.h"
|
|
|
|
|
#include "../../fceu-endian.h"
|
|
|
|
|
#include "../../input.h"
|
2014-04-18 00:51:21 +02:00
|
|
|
#include "../../state.h"
|
2014-03-30 22:35:00 +02:00
|
|
|
#include "../../ppu.h"
|
|
|
|
|
#include "../../cart.h"
|
|
|
|
|
#include "../../x6502.h"
|
|
|
|
|
#include "../../git.h"
|
|
|
|
|
#include "../../palette.h"
|
|
|
|
|
#include "../../sound.h"
|
|
|
|
|
#include "../../file.h"
|
|
|
|
|
#include "../../cheat.h"
|
|
|
|
|
#include "../../ines.h"
|
|
|
|
|
#include "../../unif.h"
|
|
|
|
|
#include "../../fds.h"
|
2015-09-24 12:52:41 -04:00
|
|
|
#include "../../vsuni.h"
|
2016-02-22 18:36:09 +01:00
|
|
|
#include "../../video.h"
|
2014-03-30 22:15:17 +02:00
|
|
|
|
2020-12-21 21:58:23 +01:00
|
|
|
#ifdef PSP
|
|
|
|
|
#include "pspgu.h"
|
|
|
|
|
#endif
|
|
|
|
|
|
2019-01-21 22:19:46 +01:00
|
|
|
#if defined(RENDER_GSKIT_PS2)
|
|
|
|
|
#include "libretro-common/include/libretro_gskit_ps2.h"
|
|
|
|
|
#endif
|
|
|
|
|
|
2025-09-01 22:07:53 +08:00
|
|
|
#if defined (PSP)
|
|
|
|
|
#define RED_SHIFT 0
|
|
|
|
|
#define GREEN_SHIFT 5
|
|
|
|
|
#define BLUE_SHIFT 11
|
|
|
|
|
#define RED_EXPAND 3
|
|
|
|
|
#define GREEN_EXPAND 2
|
|
|
|
|
#define BLUE_EXPAND 3
|
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
|
|
|
typedef uint16_t bpp_t;
|
2025-09-01 22:07:53 +08:00
|
|
|
#elif defined (FRONTEND_SUPPORTS_ABGR1555)
|
|
|
|
|
#define RED_SHIFT 0
|
|
|
|
|
#define GREEN_SHIFT 5
|
|
|
|
|
#define BLUE_SHIFT 10
|
|
|
|
|
#define RED_EXPAND 3
|
|
|
|
|
#define GREEN_EXPAND 3
|
|
|
|
|
#define BLUE_EXPAND 3
|
|
|
|
|
#define RED_MASK 0x1F
|
|
|
|
|
#define GREEN_MASK 0x3E0
|
|
|
|
|
#define BLUE_MASK 0x7C00
|
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
|
|
|
typedef uint16_t bpp_t;
|
2025-09-01 22:07:53 +08:00
|
|
|
#elif defined (FRONTEND_SUPPORTS_RGB888)
|
|
|
|
|
#define RED_SHIFT 16
|
|
|
|
|
#define GREEN_SHIFT 8
|
|
|
|
|
#define BLUE_SHIFT 0
|
|
|
|
|
#define RED_EXPAND 0
|
|
|
|
|
#define GREEN_EXPAND 0
|
|
|
|
|
#define BLUE_EXPAND 0
|
|
|
|
|
#define RED_MASK 0xFF0000
|
|
|
|
|
#define GREEN_MASK 0x00FF00
|
|
|
|
|
#define BLUE_MASK 0x0000FF
|
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
|
|
|
typedef uint32_t bpp_t;
|
2025-09-01 22:07:53 +08:00
|
|
|
#elif defined (FRONTEND_SUPPORTS_RGB565)
|
|
|
|
|
#define RED_SHIFT 11
|
|
|
|
|
#define GREEN_SHIFT 5
|
|
|
|
|
#define BLUE_SHIFT 0
|
|
|
|
|
#define RED_EXPAND 3
|
|
|
|
|
#define GREEN_EXPAND 2
|
|
|
|
|
#define BLUE_EXPAND 3
|
|
|
|
|
#define RED_MASK 0xF800
|
|
|
|
|
#define GREEN_MASK 0x7e0
|
|
|
|
|
#define BLUE_MASK 0x1f
|
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
|
|
|
typedef uint16_t bpp_t;
|
2025-09-01 22:07:53 +08:00
|
|
|
#else
|
|
|
|
|
#define RED_SHIFT 10
|
|
|
|
|
#define GREEN_SHIFT 5
|
|
|
|
|
#define BLUE_SHIFT 0
|
|
|
|
|
#define RED_EXPAND 3
|
|
|
|
|
#define GREEN_EXPAND 3
|
|
|
|
|
#define BLUE_EXPAND 3
|
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
|
|
|
typedef uint16_t bpp_t;
|
2025-09-01 22:07:53 +08:00
|
|
|
#endif
|
|
|
|
|
|
2017-11-13 08:22:46 +08:00
|
|
|
#define MAX_PLAYERS 4 /* max supported players */
|
|
|
|
|
#define MAX_PORTS 2 /* max controller ports,
|
|
|
|
|
* port 0 for player 1/3, port 1 for player 2/4 */
|
2017-11-12 15:38:04 +08:00
|
|
|
|
2022-08-27 23:01:26 -05:00
|
|
|
#define RETRO_DEVICE_AUTO RETRO_DEVICE_JOYPAD
|
|
|
|
|
#define RETRO_DEVICE_GAMEPAD RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 1)
|
|
|
|
|
#define RETRO_DEVICE_ZAPPER RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_MOUSE, 0)
|
|
|
|
|
#define RETRO_DEVICE_ARKANOID RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_MOUSE, 1)
|
2022-12-31 21:27:46 -06:00
|
|
|
#define RETRO_DEVICE_POWERPADA RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_KEYBOARD, 0)
|
|
|
|
|
#define RETRO_DEVICE_POWERPADB RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_KEYBOARD, 1)
|
2022-08-27 23:01:26 -05:00
|
|
|
|
|
|
|
|
#define RETRO_DEVICE_FC_ARKANOID RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_MOUSE, 2)
|
|
|
|
|
#define RETRO_DEVICE_FC_OEKAKIDS RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_MOUSE, 3)
|
|
|
|
|
#define RETRO_DEVICE_FC_SHADOW RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_MOUSE, 4)
|
|
|
|
|
#define RETRO_DEVICE_FC_4PLAYERS RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 2)
|
2021-04-10 15:20:50 +08:00
|
|
|
#define RETRO_DEVICE_FC_HYPERSHOT RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 3)
|
2023-09-15 19:48:52 -04:00
|
|
|
#define RETRO_DEVICE_FC_FTRAINERA RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_KEYBOARD, 2)
|
|
|
|
|
#define RETRO_DEVICE_FC_FTRAINERB RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_KEYBOARD, 3)
|
2025-09-13 11:27:38 +02:00
|
|
|
#define RETRO_DEVICE_FC_FKB RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_KEYBOARD, 4)
|
|
|
|
|
#define RETRO_DEVICE_FC_SUBORKB RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_KEYBOARD, 5)
|
|
|
|
|
#define RETRO_DEVICE_FC_PEC586KB RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_KEYBOARD, 6)
|
2022-08-27 23:01:26 -05:00
|
|
|
#define RETRO_DEVICE_FC_AUTO RETRO_DEVICE_JOYPAD
|
2017-10-02 01:18:06 +08:00
|
|
|
|
2018-12-05 16:05:55 +08:00
|
|
|
#define NES_WIDTH 256
|
|
|
|
|
#define NES_HEIGHT 240
|
2018-01-10 11:39:36 +08:00
|
|
|
#define NES_8_7_PAR ((width * (8.0 / 7.0)) / height)
|
|
|
|
|
#define NES_4_3 ((width / (height * (256.0 / 240.0))) * 4.0 / 3.0)
|
2021-04-28 18:15:10 -05:00
|
|
|
#define NES_PP ((width / (height * (256.0 / 240.0))) * 16.0 / 15.0)
|
2016-05-21 01:33:59 -05:00
|
|
|
|
2021-10-21 15:52:40 +01:00
|
|
|
#define NES_PAL_FPS (838977920.0 / 16777215.0)
|
|
|
|
|
#define NES_NTSC_FPS (1008307711.0 / 16777215.0)
|
|
|
|
|
|
2015-04-17 22:15:28 +01:00
|
|
|
#if defined(_3DS)
|
|
|
|
|
void* linearMemAlign(size_t size, size_t alignment);
|
|
|
|
|
void linearFree(void* mem);
|
|
|
|
|
#endif
|
2014-03-31 00:17:47 +02:00
|
|
|
|
2019-02-16 18:41:46 +01:00
|
|
|
#if defined(RENDER_GSKIT_PS2)
|
|
|
|
|
RETRO_HW_RENDER_INTEFACE_GSKIT_PS2 *ps2 = NULL;
|
|
|
|
|
#endif
|
|
|
|
|
|
2021-06-05 15:05:07 +02:00
|
|
|
extern void FCEU_ZapperSetTolerance(int t);
|
|
|
|
|
|
2014-03-30 22:15:17 +02:00
|
|
|
static retro_video_refresh_t video_cb = NULL;
|
|
|
|
|
static retro_input_poll_t poll_cb = NULL;
|
|
|
|
|
static retro_input_state_t input_cb = NULL;
|
|
|
|
|
static retro_audio_sample_batch_t audio_batch_cb = NULL;
|
2019-05-11 23:11:55 +08:00
|
|
|
retro_environment_t environ_cb = NULL;
|
2017-09-02 02:40:28 +08:00
|
|
|
#ifdef PSP
|
2020-10-02 19:24:00 +08:00
|
|
|
static bool crop_overscan;
|
2017-09-02 02:40:28 +08:00
|
|
|
#endif
|
2023-03-10 21:47:35 -05:00
|
|
|
static int crop_overscan_h_left;
|
|
|
|
|
static int crop_overscan_h_right;
|
|
|
|
|
static int crop_overscan_v_top;
|
|
|
|
|
static int crop_overscan_v_bottom;
|
2020-05-05 13:35:30 +08:00
|
|
|
|
2015-03-19 23:16:52 +01:00
|
|
|
static bool use_raw_palette;
|
2021-04-28 18:15:10 -05:00
|
|
|
static int aspect_ratio_par;
|
2020-05-05 13:35:30 +08:00
|
|
|
|
2026-05-04 07:16:54 +02:00
|
|
|
/* Pixel format negotiated with the frontend at retro_load_game. Cached
|
|
|
|
|
* here for retro_run_blit so it can validate that
|
|
|
|
|
* GET_CURRENT_SOFTWARE_FRAMEBUFFER returned a format we can write into
|
|
|
|
|
* directly. */
|
|
|
|
|
static enum retro_pixel_format active_pixformat = RETRO_PIXEL_FORMAT_UNKNOWN;
|
|
|
|
|
|
2020-05-05 13:35:30 +08:00
|
|
|
/*
|
|
|
|
|
* Flags to keep track of whether turbo
|
|
|
|
|
* buttons toggled on or off.
|
|
|
|
|
*
|
|
|
|
|
* There are two values in array
|
|
|
|
|
* for Turbo A and Turbo B for
|
|
|
|
|
* each player
|
|
|
|
|
*/
|
|
|
|
|
|
2021-12-10 23:53:45 +02:00
|
|
|
#define MAX_BUTTONS 9
|
2020-05-05 13:35:30 +08:00
|
|
|
#define TURBO_BUTTONS 2
|
2021-06-05 15:05:07 +02:00
|
|
|
unsigned char turbo_button_toggle[MAX_PLAYERS][TURBO_BUTTONS] = { {0} };
|
2020-05-05 13:35:30 +08:00
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
|
{
|
|
|
|
|
unsigned retro;
|
|
|
|
|
unsigned nes;
|
|
|
|
|
} keymap;
|
|
|
|
|
|
|
|
|
|
static const keymap turbomap[] = {
|
|
|
|
|
{ RETRO_DEVICE_ID_JOYPAD_X, JOY_A },
|
|
|
|
|
{ RETRO_DEVICE_ID_JOYPAD_Y, JOY_B },
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const keymap bindmap[] = {
|
|
|
|
|
{ RETRO_DEVICE_ID_JOYPAD_A, JOY_A },
|
|
|
|
|
{ RETRO_DEVICE_ID_JOYPAD_B, JOY_B },
|
2021-12-10 23:53:45 +02:00
|
|
|
{ RETRO_DEVICE_ID_JOYPAD_L3, JOY_A | JOY_B },
|
2020-05-05 13:35:30 +08:00
|
|
|
{ RETRO_DEVICE_ID_JOYPAD_SELECT, JOY_SELECT },
|
|
|
|
|
{ RETRO_DEVICE_ID_JOYPAD_START, JOY_START },
|
|
|
|
|
{ RETRO_DEVICE_ID_JOYPAD_UP, JOY_UP },
|
|
|
|
|
{ RETRO_DEVICE_ID_JOYPAD_DOWN, JOY_DOWN },
|
|
|
|
|
{ RETRO_DEVICE_ID_JOYPAD_LEFT, JOY_LEFT },
|
|
|
|
|
{ RETRO_DEVICE_ID_JOYPAD_RIGHT, JOY_RIGHT },
|
|
|
|
|
};
|
|
|
|
|
|
2023-01-07 11:07:14 -06:00
|
|
|
static const uint32_t powerpadmap[] = {
|
2022-12-31 21:27:46 -06:00
|
|
|
RETROK_q, RETROK_w, RETROK_e, RETROK_r,
|
|
|
|
|
RETROK_a, RETROK_s, RETROK_d, RETROK_f,
|
|
|
|
|
RETROK_z, RETROK_x, RETROK_c, RETROK_v,
|
|
|
|
|
};
|
|
|
|
|
|
2025-09-13 11:27:38 +02:00
|
|
|
static const uint32_t fkbmap[0x48] = {
|
|
|
|
|
RETROK_F1,RETROK_F2,RETROK_F3,RETROK_F4,RETROK_F5,RETROK_F6,RETROK_F7,RETROK_F8,
|
|
|
|
|
RETROK_1,RETROK_2,RETROK_3,RETROK_4,RETROK_5,RETROK_6,RETROK_7,RETROK_8,RETROK_9,RETROK_0,RETROK_MINUS,RETROK_EQUALS,RETROK_BACKSLASH,RETROK_BACKSPACE,
|
|
|
|
|
RETROK_ESCAPE,RETROK_q,RETROK_w,RETROK_e,RETROK_r,RETROK_t,RETROK_y,RETROK_u,RETROK_i,RETROK_o,RETROK_p,RETROK_TILDE,RETROK_LEFTBRACKET,RETROK_RETURN,
|
|
|
|
|
RETROK_LCTRL,RETROK_a,RETROK_s,RETROK_d,RETROK_f,RETROK_g,RETROK_h,RETROK_j,RETROK_k,RETROK_l,RETROK_SEMICOLON,RETROK_QUOTE,RETROK_RIGHTBRACKET,RETROK_INSERT,
|
|
|
|
|
RETROK_LSHIFT,RETROK_z,RETROK_x,RETROK_c,RETROK_v,RETROK_b,RETROK_n,RETROK_m,RETROK_COMMA,RETROK_PERIOD,RETROK_SLASH,RETROK_RALT,RETROK_RSHIFT,RETROK_LALT,RETROK_SPACE,
|
|
|
|
|
RETROK_DELETE,RETROK_END,RETROK_PAGEDOWN,
|
|
|
|
|
RETROK_UP,RETROK_LEFT,RETROK_RIGHT,RETROK_DOWN
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const uint32_t suborkbmap[0x65] = {
|
|
|
|
|
RETROK_ESCAPE,RETROK_F1,RETROK_F2,RETROK_F3,RETROK_F4,RETROK_F5,RETROK_F6,RETROK_F7,RETROK_F8,RETROK_F9,
|
|
|
|
|
RETROK_F10,RETROK_F11,RETROK_F12,RETROK_NUMLOCK,RETROK_CARET,RETROK_1,RETROK_2,RETROK_3,RETROK_4,RETROK_5,
|
|
|
|
|
RETROK_6,RETROK_7,RETROK_8,RETROK_9,RETROK_0,RETROK_MINUS,RETROK_EQUALS,RETROK_BACKSPACE,RETROK_INSERT,RETROK_HOME,
|
|
|
|
|
RETROK_PAGEUP,RETROK_PAUSE,RETROK_KP_DIVIDE,RETROK_KP_MULTIPLY,RETROK_KP_MINUS,RETROK_TAB,RETROK_q,RETROK_w,RETROK_e,RETROK_r,
|
|
|
|
|
RETROK_t,RETROK_y,RETROK_u,RETROK_i,RETROK_o,RETROK_p,RETROK_LEFTBRACKET,RETROK_RIGHTBRACKET,RETROK_RETURN,RETROK_DELETE,
|
|
|
|
|
RETROK_END,RETROK_PAGEDOWN,RETROK_KP7,RETROK_KP8,RETROK_KP9,RETROK_KP_PLUS,RETROK_CAPSLOCK,RETROK_a,RETROK_s,RETROK_d,
|
|
|
|
|
RETROK_f,RETROK_g,RETROK_h,RETROK_j,RETROK_k,RETROK_l,RETROK_SEMICOLON,RETROK_QUOTE,RETROK_KP4,RETROK_KP5,
|
|
|
|
|
RETROK_KP6,RETROK_LSHIFT,RETROK_z,RETROK_x,RETROK_c,RETROK_v,RETROK_b,RETROK_n,RETROK_m,RETROK_COMMA,
|
|
|
|
|
RETROK_PERIOD,RETROK_SLASH,RETROK_BACKSLASH,RETROK_UP,RETROK_KP1,RETROK_KP2,RETROK_KP3,RETROK_LCTRL,RETROK_LALT,RETROK_SPACE,
|
|
|
|
|
RETROK_LEFT,RETROK_DOWN,RETROK_RIGHT,RETROK_KP0,RETROK_KP_PERIOD,RETROK_UNKNOWN,RETROK_UNKNOWN,RETROK_UNKNOWN,RETROK_UNKNOWN,RETROK_UNKNOWN,
|
|
|
|
|
RETROK_UNKNOWN
|
|
|
|
|
};
|
|
|
|
|
|
2022-12-31 21:27:46 -06:00
|
|
|
|
2020-05-05 13:35:30 +08:00
|
|
|
typedef struct {
|
|
|
|
|
bool enable_4player; /* four-score / 4-player adapter used */
|
|
|
|
|
bool up_down_allowed; /* disabled simultaneous up+down and left+right dpad combinations */
|
|
|
|
|
|
|
|
|
|
/* turbo related */
|
|
|
|
|
uint32_t turbo_enabler[MAX_PLAYERS];
|
|
|
|
|
uint32_t turbo_delay;
|
|
|
|
|
|
|
|
|
|
uint32_t type[MAX_PLAYERS + 1]; /* 4-players + famicom expansion */
|
|
|
|
|
|
|
|
|
|
/* input data */
|
2022-08-27 23:01:26 -05:00
|
|
|
uint32_t JSReturn; /* player input data, 1 byte per player (1-4) */
|
|
|
|
|
uint32_t MouseData[MAX_PORTS][4]; /* nes mouse data */
|
|
|
|
|
uint32_t FamicomData[3]; /* Famicom expansion port data */
|
2022-12-31 21:27:46 -06:00
|
|
|
uint32_t PowerPadData;
|
2025-09-13 11:27:38 +02:00
|
|
|
uint8_t FamilyKeyboardData[0x48];
|
|
|
|
|
uint8_t SuborKeyboardData[0x65];
|
2020-05-05 13:35:30 +08:00
|
|
|
} NES_INPUT_T;
|
|
|
|
|
|
|
|
|
|
static NES_INPUT_T nes_input = { 0 };
|
2022-08-27 23:01:26 -05:00
|
|
|
enum RetroZapperInputModes{RetroCLightgun, RetroSTLightgun, RetroMouse, RetroPointer};
|
|
|
|
|
enum RetroZapperInputModes zappermode = RetroCLightgun;
|
2023-03-11 11:40:58 -06:00
|
|
|
enum RetroArkanoidInputModes{RetroArkanoidMouse, RetroArkanoidPointer, RetroArkanoidAbsMouse, RetroArkanoidStelladaptor};
|
|
|
|
|
enum RetroArkanoidInputModes arkanoidmode = RetroArkanoidMouse;
|
|
|
|
|
static int mouseSensitivity = 100;
|
2022-08-27 23:01:26 -05:00
|
|
|
extern int switchZapper;
|
2014-03-30 22:15:17 +02:00
|
|
|
|
2020-05-05 13:35:30 +08:00
|
|
|
static bool libretro_supports_bitmasks = false;
|
2021-10-19 17:33:56 +01:00
|
|
|
static bool libretro_supports_option_categories = false;
|
2021-10-21 15:52:40 +01:00
|
|
|
static unsigned libretro_msg_interface_version = 0;
|
2020-05-05 13:35:30 +08:00
|
|
|
|
2014-03-30 22:15:17 +02:00
|
|
|
/* emulator-specific variables */
|
|
|
|
|
|
2019-11-06 16:02:50 -08:00
|
|
|
const size_t PPU_BIT = 1ULL << 31ULL;
|
|
|
|
|
|
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
|
|
|
extern uint8_t NTARAM[0x800], PALRAM[0x20], SPRAM[0x100], PPU[4];
|
2021-06-05 15:05:07 +02:00
|
|
|
|
|
|
|
|
/* overclock the console by adding dummy scanlines to PPU loop
|
|
|
|
|
* disables DMC DMA and WaveHi filling for these dummies
|
|
|
|
|
* doesn't work with new PPU */
|
|
|
|
|
unsigned overclock_enabled = -1;
|
|
|
|
|
unsigned overclocked = 0;
|
|
|
|
|
unsigned skip_7bit_overclocking = 1; /* 7-bit samples have priority over overclocking */
|
|
|
|
|
unsigned totalscanlines = 0;
|
|
|
|
|
unsigned normal_scanlines = 240;
|
|
|
|
|
unsigned extrascanlines = 0;
|
|
|
|
|
unsigned vblankscanlines = 0;
|
|
|
|
|
unsigned dendy = 0;
|
|
|
|
|
|
2020-02-07 14:53:14 +08:00
|
|
|
static unsigned systemRegion = 0;
|
|
|
|
|
static unsigned opt_region = 0;
|
2021-10-19 17:33:56 +01:00
|
|
|
static bool opt_showAdvSoundOptions = true;
|
|
|
|
|
static bool opt_showAdvSystemOptions = true;
|
2020-02-07 14:53:14 +08:00
|
|
|
|
2020-12-21 21:58:23 +01:00
|
|
|
#if defined(PSP) || defined(PS2)
|
2014-03-30 22:35:00 +02:00
|
|
|
static __attribute__((aligned(16))) uint16_t retro_palette[256];
|
|
|
|
|
#else
|
2025-09-01 22:07:53 +08:00
|
|
|
static uint32_t retro_palette[1024];
|
2014-03-30 22:35:00 +02:00
|
|
|
#endif
|
2019-01-21 22:19:46 +01:00
|
|
|
#if defined(RENDER_GSKIT_PS2)
|
|
|
|
|
static uint8_t* fceu_video_out;
|
|
|
|
|
#else
|
2025-09-01 22:07:53 +08:00
|
|
|
static bpp_t* fceu_video_out;
|
2019-01-21 22:19:46 +01:00
|
|
|
#endif
|
2014-03-30 22:35:00 +02:00
|
|
|
|
2014-03-31 00:17:47 +02:00
|
|
|
/* Some timing-related variables. */
|
2018-12-15 10:23:43 +08:00
|
|
|
static unsigned sndsamplerate;
|
|
|
|
|
static unsigned sndquality;
|
|
|
|
|
static unsigned sndvolume;
|
2021-06-05 15:05:07 +02:00
|
|
|
unsigned swapDuty;
|
2014-03-31 00:17:47 +02:00
|
|
|
|
2015-08-28 12:17:56 +02:00
|
|
|
static int32_t *sound = 0;
|
2018-11-23 01:59:20 +08:00
|
|
|
static uint32_t Dummy = 0;
|
2015-08-28 12:17:56 +02:00
|
|
|
static uint32_t current_palette = 0;
|
2018-12-15 10:23:43 +08:00
|
|
|
static unsigned serialize_size;
|
|
|
|
|
|
2021-06-05 15:05:07 +02:00
|
|
|
/* extern forward decls.*/
|
|
|
|
|
extern FCEUGI *GameInfo;
|
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
|
|
|
extern uint8_t *XBuf;
|
2021-06-05 15:05:07 +02:00
|
|
|
extern CartInfo iNESCart;
|
|
|
|
|
extern CartInfo UNIFCart;
|
|
|
|
|
extern int show_crosshair;
|
|
|
|
|
extern int option_ramstate;
|
2022-08-27 23:01:26 -05:00
|
|
|
extern int zapper_trigger_invert_option;
|
|
|
|
|
extern int zapper_sensor_invert_option;
|
2021-06-05 15:05:07 +02:00
|
|
|
|
2014-03-30 22:15:17 +02:00
|
|
|
/* emulator-specific callback functions */
|
|
|
|
|
|
core: stride-aware savestate, iNES2 helpers, -Wundef, -Wmissing-prototypes
Audit pass 5 - five distinct cleanups bundled into one omnibus.
1. Element-stride byte-swapping for savestate fields (state.h, state.c,
fceu-endian.{h,c})
The SFORMAT 's' field was previously {bit 31 = RLSB, bits 0..30 =
byte size}. RLSB triggers FlipByteOrder() on MSB hosts, which
reverses the entire entry buffer end-to-end. That is correct for a
single primitive (size <= 8 bytes) but wrong for an array of
multi-byte primitives - reversing the whole buffer would swap
element 0 with element N-1 and reverse their bytes too, scrambling
the data.
The previous workaround was either splitting an N-element array
into N separate single-primitive entries with distinct chunk IDs
(n106 PlayIndex split into IDX0..IDX7) or skipping the entry
entirely on big-endian hosts (the GEKKO #ifndef in vrc6.c / vrc7.c).
Both approaches mean BE saves are not portable to LE and vice
versa, and force the same workaround at every new array site.
This pass adds proper stride support:
* SFORMAT 's' encoding is now {bit 31 = RLSB, bits 24..30 =
stride in bytes (0 = legacy/unset), bits 0..23 = byte size}.
16 MiB max size, well above any actual savestate field.
* FCEUSTATE_RLSB_ARRAY(stride) macro for the new pattern.
* FlipByteOrderStrided() byte-swaps each element of an array
independently. Round-trip identity verified: [01 00 00 00 ...]
-> [00 00 00 01 ...] -> [01 00 00 00 ...].
* state.c's SubWrite / ReadStateChunk / CheckS use new helpers
sf_size() / sf_stride() / sf_flip() that mask the size out of
the new bit layout and dispatch to the strided variant when
stride < size.
Backwards compatible: legacy single-primitive entries (size == 1,
2, 4, 8) leave the stride bits at zero, which sf_stride() reads
as "stride equals size" and falls through to FlipByteOrder() as
before. No on-disk format change. Existing single-primitive RLSB
sites are unchanged.
The infrastructure is now in place so any future SFORMAT entry
that is an array of multi-byte primitives can be expressed as a
single entry (e.g. "{ buf, sizeof(buf) | FCEUSTATE_RLSB_ARRAY(4),
"BUF." }") without splitting or skipping. The existing PlayIndex
split and GEKKO #ifndefs are intentionally left untouched -
migrating them would alter the on-disk savestate format and is a
separate decision.
2. iNES1-vs-iNES2 sizing helpers (cart.h)
Twelve sites across the codebase encoded the same conditional:
info->iNES2 ? (info->PRGRamSize + info->PRGRamSaveSize) : default
Sometimes for PRGRAM, sometimes for CHRRAM, sometimes in bytes,
sometimes after dividing by 1024. The pattern is verbose and easy
to write inconsistently.
Added two inline helpers in cart.h:
- CartInfo_PRGRAM_bytes(info, default_bytes)
- CartInfo_CHRRAM_bytes(info, default_bytes)
Migrated 9 of the 13 sites: cartram.c (2), 162.c, 163.c, 134.c,
399.c, 478.c, 480.c, 484.c. The remaining 4 are non-helper-fitting
variants (164.c special masking, 2 cartram SaveGameLen sites with
different fallback semantics, mmc3.c Boogerman submapper detection).
3. -Wundef enabled permanently (Makefile.libretro)
Zero warnings out of the box - no #if-on-undefined-macro footguns
in the codebase. Now part of WARNING_DEFINES alongside the existing
-Wsign-compare.
4. -Wmissing-prototypes enabled permanently (Makefile.libretro)
Started at 198 warnings, cleared all of them:
* Mass-static-ified ~96 functions across 75 files that were
defined non-static but only used within their own translation
unit. (See static_prototype_fixer.py in the development notes.)
* K&R-style empty-parens prototypes "()" replaced with explicit
"(void)" across all asic_*.{c,h} files - GCC treats "()" as
"any args" and refuses to match it against a separate K&R
definition.
* Added missing forward declarations to public headers:
- fds.h (FDSLoad)
- nsf.h (NSFLoad)
- ines.h (iNESLoad)
- unif.h (UNIFLoad)
- latch.h (LatchHardReset, K&R fix)
- eeprom_93Cx6.h (eeprom_93Cx6_read, K&R fix)
Each header gained an "#include "file.h"" where needed.
* fds_apu.c now includes its own fds_apu.h header (was missing).
* fds_apu.h: removed unused FDSSoundRead declaration (the function
is internal-static).
* cartram.h: removed unused CartRAM_close declaration (the function
is internal-static).
* input.h: added a centralised block of FCEU_Init* prototypes
(Zapper, Mouse, Powerpad, Arkanoid, VirtualBoy, FKB, SuborKB,
PEC586KB, HS, Mahjong, FamilyTrainerA/B, OekaKids, TopRider,
BarcodeWorld, BattleBox, QuizKing, FTrainerA/B, SpaceShadow,
LCDCompZapper, ArkanoidFC) plus FCEU_ZapperSetTolerance. These
were previously declared as "extern" inside src/input.c.
* Static-ified FP_FASTAPASS callbacks in 106.c, 65.c, 67.c,
asic_h3001.c, asic_vrc3.c (those with no external callers);
left non-static for those that have header decls or are
referenced from sibling .c files (asic_mmc1, asic_vrc6,
asic_vrc7, flashrom).
* For a small set of cross-file functions where adding a header
was disproportionate to the value (MMC5_hb, NSFMMC5_Close,
GetKeyboard, FCEU_GetJoyJoy), placed a forward declaration
immediately above the definition. This satisfies
-Wmissing-prototypes (which checks for any prior declaration
in scope) without churning the public-header layout.
5. -Wshadow partial cleanup (not enabled permanently)
Fixed five real shadows that were either bugs or actively
misleading:
* src/boards/476.c: removed an inner "int i" that shadowed the
outer loop counter.
* src/boards/mmc5.c MMC5_hb: parameter "scanline" renamed to
"sl_param" (was shadowing the global "scanline").
* src/boards/n106.c DoNamcoSound: parameter "Wave" renamed to
"WaveBuf" (was shadowing the global Wave audio buffer); also
updated the forward declaration and the matching parameter
on sound.h's NeoFill function pointer typedef.
* src/boards/vrc7.c UpdateOPLNEO: same Wave -> WaveBuf rename.
* src/ntsc/nes_ntsc_impl.h: renamed an inner loop counter "n"
that shadowed an outer "n".
* src/drivers/libretro/libretro.c FCEUD_RegionOverride: local
"pal" renamed to "is_pal" (was shadowing the typedef "pal" from
palette.h).
* src/palette.c FCEUI_SetPaletteArray: parameter "pal" renamed
to "data" (same shadow); driver.h declaration updated to match.
-Wshadow itself is NOT enabled permanently because the remaining
warnings are deliberate parameter naming conventions (XBuf in
draw functions, X in cpu hooks) and third-party blargg ntsc code.
In addition, four files were touched as part of an MSVC-build fix
that came up mid-pass: src/fds.c, src/nsf.c, src/ines.c, and
src/drivers/libretro/libretro_dipswitch.c had snprintf() calls
introduced in pass 4 that fail to link on pre-MSVC2015 toolchains
when STATIC_LINKING=1 (the libretro-common compat_snprintf.c shim
isn't compiled in those configurations). Replaced each snprintf with
either sprintf-into-bounded-buffer (the format strings have known
maximum output) or strlcpy/strlcat for the dipswitch key-build case.
All output is still bounded; truncation happens via strl*'s normal
truncation semantics where applicable.
All added code is C89-clean (top-of-block declarations only, no
mixed decls, no // comments, INLINE macro from fceu-types.h instead
of bare "inline"). Builds clean under -std=gnu11 with -Wno-write-
strings -Wsign-compare -Wundef -Wmissing-prototypes; zero errors,
zero warnings.
Determinism audit (audit_determinism.py): no rand/time/long
double/threads issues introduced.
2026-05-04 04:44:52 +02:00
|
|
|
const char *GetKeyboard(void); /* used by src/boards/transformer.c */
|
2014-03-30 22:35:00 +02:00
|
|
|
const char * GetKeyboard(void)
|
|
|
|
|
{
|
|
|
|
|
return "";
|
2014-03-30 22:15:17 +02:00
|
|
|
}
|
|
|
|
|
|
2015-02-25 14:53:09 +01:00
|
|
|
#define BUILD_PIXEL_RGB565(R,G,B) (((int) ((R)&0x1f) << RED_SHIFT) | ((int) ((G)&0x3f) << GREEN_SHIFT) | ((int) ((B)&0x1f) << BLUE_SHIFT))
|
2014-12-10 22:28:25 +01: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
|
|
|
void FCEUD_SetPalette(uint16_t index, uint8_t r, uint8_t g, uint8_t b)
|
2014-03-30 22:35:00 +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
|
|
|
uint16_t index_to_write = index;
|
2019-01-21 22:19:46 +01:00
|
|
|
#if defined(RENDER_GSKIT_PS2)
|
|
|
|
|
/* Index correction for PS2 GS */
|
|
|
|
|
int modi = index & 63;
|
|
|
|
|
if ((modi >= 8 && modi < 16) || (modi >= 40 && modi < 48)) {
|
|
|
|
|
index_to_write += 8;
|
|
|
|
|
} else if ((modi >= 16 && modi < 24) || (modi >= 48 && modi < 56)) {
|
|
|
|
|
index_to_write -= 8;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2024-10-15 22:35:46 +08:00
|
|
|
#if defined(PSP) || defined(PS2)
|
|
|
|
|
/* PS2 / PSP will only have 256 colors */
|
|
|
|
|
if (index >= 256)
|
|
|
|
|
return;
|
|
|
|
|
#endif
|
|
|
|
|
|
2014-12-08 20:56:42 +01:00
|
|
|
#ifdef FRONTEND_SUPPORTS_RGB565
|
2019-01-21 22:19:46 +01:00
|
|
|
retro_palette[index_to_write] = BUILD_PIXEL_RGB565(r >> RED_EXPAND, g >> GREEN_EXPAND, b >> BLUE_EXPAND);
|
2014-12-08 20:56:42 +01:00
|
|
|
#else
|
2019-01-21 22:19:46 +01:00
|
|
|
retro_palette[index_to_write] =
|
2015-08-28 12:47:18 +02:00
|
|
|
((r >> RED_EXPAND) << RED_SHIFT) | ((g >> GREEN_EXPAND) << GREEN_SHIFT) | ((b >> BLUE_EXPAND) << BLUE_SHIFT);
|
2014-12-08 20:56:42 +01:00
|
|
|
#endif
|
2014-03-30 22:15:17 +02:00
|
|
|
}
|
|
|
|
|
|
2014-04-18 19:16:52 +02:00
|
|
|
static struct retro_log_callback log_cb;
|
2014-04-18 00:55:04 +02:00
|
|
|
|
|
|
|
|
static void default_logger(enum retro_log_level level, const char *fmt, ...) {}
|
|
|
|
|
|
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
|
|
|
void FCEUD_PrintError(const char *c)
|
2014-04-18 00:55:04 +02:00
|
|
|
{
|
2014-04-18 19:16:52 +02:00
|
|
|
log_cb.log(RETRO_LOG_WARN, "%s", c);
|
2014-04-18 00:55:04 +02:00
|
|
|
}
|
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
|
|
|
void FCEUD_Message(const char *s)
|
2014-04-18 00:55:04 +02:00
|
|
|
{
|
2014-04-18 19:16:52 +02:00
|
|
|
log_cb.log(RETRO_LOG_INFO, "%s", s);
|
2014-04-18 00:55:04 +02:00
|
|
|
}
|
2014-03-30 22:15:17 +02:00
|
|
|
|
2021-10-22 13:30:27 +01:00
|
|
|
void FCEUD_DispMessage(enum retro_log_level level, unsigned duration, const char *str)
|
2021-10-21 15:52:40 +01:00
|
|
|
{
|
|
|
|
|
if (!environ_cb)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (libretro_msg_interface_version >= 1)
|
|
|
|
|
{
|
|
|
|
|
struct retro_message_ext msg;
|
|
|
|
|
unsigned priority;
|
|
|
|
|
|
|
|
|
|
switch (level)
|
|
|
|
|
{
|
|
|
|
|
case RETRO_LOG_ERROR:
|
|
|
|
|
priority = 5;
|
|
|
|
|
break;
|
|
|
|
|
case RETRO_LOG_WARN:
|
|
|
|
|
priority = 4;
|
|
|
|
|
break;
|
|
|
|
|
case RETRO_LOG_INFO:
|
|
|
|
|
priority = 3;
|
|
|
|
|
break;
|
|
|
|
|
case RETRO_LOG_DEBUG:
|
|
|
|
|
default:
|
|
|
|
|
priority = 1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
msg.msg = str;
|
|
|
|
|
msg.duration = duration;
|
|
|
|
|
msg.priority = priority;
|
|
|
|
|
msg.level = level;
|
|
|
|
|
msg.target = RETRO_MESSAGE_TARGET_OSD;
|
|
|
|
|
msg.type = RETRO_MESSAGE_TYPE_NOTIFICATION_ALT;
|
|
|
|
|
msg.progress = -1;
|
|
|
|
|
|
|
|
|
|
environ_cb(RETRO_ENVIRONMENT_SET_MESSAGE_EXT, &msg);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
float fps = (FSettings.PAL || dendy) ? NES_PAL_FPS : NES_NTSC_FPS;
|
|
|
|
|
unsigned frames = (unsigned)(((float)duration * fps / 1000.0f) + 0.5f);
|
|
|
|
|
struct retro_message msg;
|
|
|
|
|
|
|
|
|
|
msg.msg = str;
|
|
|
|
|
msg.frames = frames;
|
|
|
|
|
|
|
|
|
|
environ_cb(RETRO_ENVIRONMENT_SET_MESSAGE, &msg);
|
|
|
|
|
}
|
2017-03-19 02:14:43 +08:00
|
|
|
}
|
|
|
|
|
|
core: stride-aware savestate, iNES2 helpers, -Wundef, -Wmissing-prototypes
Audit pass 5 - five distinct cleanups bundled into one omnibus.
1. Element-stride byte-swapping for savestate fields (state.h, state.c,
fceu-endian.{h,c})
The SFORMAT 's' field was previously {bit 31 = RLSB, bits 0..30 =
byte size}. RLSB triggers FlipByteOrder() on MSB hosts, which
reverses the entire entry buffer end-to-end. That is correct for a
single primitive (size <= 8 bytes) but wrong for an array of
multi-byte primitives - reversing the whole buffer would swap
element 0 with element N-1 and reverse their bytes too, scrambling
the data.
The previous workaround was either splitting an N-element array
into N separate single-primitive entries with distinct chunk IDs
(n106 PlayIndex split into IDX0..IDX7) or skipping the entry
entirely on big-endian hosts (the GEKKO #ifndef in vrc6.c / vrc7.c).
Both approaches mean BE saves are not portable to LE and vice
versa, and force the same workaround at every new array site.
This pass adds proper stride support:
* SFORMAT 's' encoding is now {bit 31 = RLSB, bits 24..30 =
stride in bytes (0 = legacy/unset), bits 0..23 = byte size}.
16 MiB max size, well above any actual savestate field.
* FCEUSTATE_RLSB_ARRAY(stride) macro for the new pattern.
* FlipByteOrderStrided() byte-swaps each element of an array
independently. Round-trip identity verified: [01 00 00 00 ...]
-> [00 00 00 01 ...] -> [01 00 00 00 ...].
* state.c's SubWrite / ReadStateChunk / CheckS use new helpers
sf_size() / sf_stride() / sf_flip() that mask the size out of
the new bit layout and dispatch to the strided variant when
stride < size.
Backwards compatible: legacy single-primitive entries (size == 1,
2, 4, 8) leave the stride bits at zero, which sf_stride() reads
as "stride equals size" and falls through to FlipByteOrder() as
before. No on-disk format change. Existing single-primitive RLSB
sites are unchanged.
The infrastructure is now in place so any future SFORMAT entry
that is an array of multi-byte primitives can be expressed as a
single entry (e.g. "{ buf, sizeof(buf) | FCEUSTATE_RLSB_ARRAY(4),
"BUF." }") without splitting or skipping. The existing PlayIndex
split and GEKKO #ifndefs are intentionally left untouched -
migrating them would alter the on-disk savestate format and is a
separate decision.
2. iNES1-vs-iNES2 sizing helpers (cart.h)
Twelve sites across the codebase encoded the same conditional:
info->iNES2 ? (info->PRGRamSize + info->PRGRamSaveSize) : default
Sometimes for PRGRAM, sometimes for CHRRAM, sometimes in bytes,
sometimes after dividing by 1024. The pattern is verbose and easy
to write inconsistently.
Added two inline helpers in cart.h:
- CartInfo_PRGRAM_bytes(info, default_bytes)
- CartInfo_CHRRAM_bytes(info, default_bytes)
Migrated 9 of the 13 sites: cartram.c (2), 162.c, 163.c, 134.c,
399.c, 478.c, 480.c, 484.c. The remaining 4 are non-helper-fitting
variants (164.c special masking, 2 cartram SaveGameLen sites with
different fallback semantics, mmc3.c Boogerman submapper detection).
3. -Wundef enabled permanently (Makefile.libretro)
Zero warnings out of the box - no #if-on-undefined-macro footguns
in the codebase. Now part of WARNING_DEFINES alongside the existing
-Wsign-compare.
4. -Wmissing-prototypes enabled permanently (Makefile.libretro)
Started at 198 warnings, cleared all of them:
* Mass-static-ified ~96 functions across 75 files that were
defined non-static but only used within their own translation
unit. (See static_prototype_fixer.py in the development notes.)
* K&R-style empty-parens prototypes "()" replaced with explicit
"(void)" across all asic_*.{c,h} files - GCC treats "()" as
"any args" and refuses to match it against a separate K&R
definition.
* Added missing forward declarations to public headers:
- fds.h (FDSLoad)
- nsf.h (NSFLoad)
- ines.h (iNESLoad)
- unif.h (UNIFLoad)
- latch.h (LatchHardReset, K&R fix)
- eeprom_93Cx6.h (eeprom_93Cx6_read, K&R fix)
Each header gained an "#include "file.h"" where needed.
* fds_apu.c now includes its own fds_apu.h header (was missing).
* fds_apu.h: removed unused FDSSoundRead declaration (the function
is internal-static).
* cartram.h: removed unused CartRAM_close declaration (the function
is internal-static).
* input.h: added a centralised block of FCEU_Init* prototypes
(Zapper, Mouse, Powerpad, Arkanoid, VirtualBoy, FKB, SuborKB,
PEC586KB, HS, Mahjong, FamilyTrainerA/B, OekaKids, TopRider,
BarcodeWorld, BattleBox, QuizKing, FTrainerA/B, SpaceShadow,
LCDCompZapper, ArkanoidFC) plus FCEU_ZapperSetTolerance. These
were previously declared as "extern" inside src/input.c.
* Static-ified FP_FASTAPASS callbacks in 106.c, 65.c, 67.c,
asic_h3001.c, asic_vrc3.c (those with no external callers);
left non-static for those that have header decls or are
referenced from sibling .c files (asic_mmc1, asic_vrc6,
asic_vrc7, flashrom).
* For a small set of cross-file functions where adding a header
was disproportionate to the value (MMC5_hb, NSFMMC5_Close,
GetKeyboard, FCEU_GetJoyJoy), placed a forward declaration
immediately above the definition. This satisfies
-Wmissing-prototypes (which checks for any prior declaration
in scope) without churning the public-header layout.
5. -Wshadow partial cleanup (not enabled permanently)
Fixed five real shadows that were either bugs or actively
misleading:
* src/boards/476.c: removed an inner "int i" that shadowed the
outer loop counter.
* src/boards/mmc5.c MMC5_hb: parameter "scanline" renamed to
"sl_param" (was shadowing the global "scanline").
* src/boards/n106.c DoNamcoSound: parameter "Wave" renamed to
"WaveBuf" (was shadowing the global Wave audio buffer); also
updated the forward declaration and the matching parameter
on sound.h's NeoFill function pointer typedef.
* src/boards/vrc7.c UpdateOPLNEO: same Wave -> WaveBuf rename.
* src/ntsc/nes_ntsc_impl.h: renamed an inner loop counter "n"
that shadowed an outer "n".
* src/drivers/libretro/libretro.c FCEUD_RegionOverride: local
"pal" renamed to "is_pal" (was shadowing the typedef "pal" from
palette.h).
* src/palette.c FCEUI_SetPaletteArray: parameter "pal" renamed
to "data" (same shadow); driver.h declaration updated to match.
-Wshadow itself is NOT enabled permanently because the remaining
warnings are deliberate parameter naming conventions (XBuf in
draw functions, X in cpu hooks) and third-party blargg ntsc code.
In addition, four files were touched as part of an MSVC-build fix
that came up mid-pass: src/fds.c, src/nsf.c, src/ines.c, and
src/drivers/libretro/libretro_dipswitch.c had snprintf() calls
introduced in pass 4 that fail to link on pre-MSVC2015 toolchains
when STATIC_LINKING=1 (the libretro-common compat_snprintf.c shim
isn't compiled in those configurations). Replaced each snprintf with
either sprintf-into-bounded-buffer (the format strings have known
maximum output) or strlcpy/strlcat for the dipswitch key-build case.
All output is still bounded; truncation happens via strl*'s normal
truncation semantics where applicable.
All added code is C89-clean (top-of-block declarations only, no
mixed decls, no // comments, INLINE macro from fceu-types.h instead
of bare "inline"). Builds clean under -std=gnu11 with -Wno-write-
strings -Wsign-compare -Wundef -Wmissing-prototypes; zero errors,
zero warnings.
Determinism audit (audit_determinism.py): no rand/time/long
double/threads issues introduced.
2026-05-04 04:44:52 +02:00
|
|
|
static void FCEUD_SoundToggle (void)
|
2014-03-30 22:35:00 +02:00
|
|
|
{
|
2017-09-09 14:54:02 +08:00
|
|
|
FCEUI_SetSoundVolume(sndvolume);
|
2014-03-30 22:15:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*palette for FCEU*/
|
2022-06-09 13:58:15 +08:00
|
|
|
#define PAL_INTERNAL sizeof(palettes) / sizeof(palettes[0]) /* Number of palettes in palettes[] */
|
2021-10-22 13:30:27 +01:00
|
|
|
#define PAL_DEFAULT (PAL_INTERNAL + 1)
|
|
|
|
|
#define PAL_RAW (PAL_INTERNAL + 2)
|
|
|
|
|
#define PAL_CUSTOM (PAL_INTERNAL + 3)
|
|
|
|
|
#define PAL_TOTAL PAL_CUSTOM
|
2021-06-05 15:05:07 +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 uint8_t external_palette_exist = false;
|
2014-03-30 22:15:17 +02:00
|
|
|
|
2020-02-25 07:13:42 +08:00
|
|
|
/* table for currently loaded palette */
|
|
|
|
|
static uint8_t base_palette[192];
|
|
|
|
|
|
2014-03-30 22:15:17 +02:00
|
|
|
struct st_palettes {
|
2020-02-29 09:48:09 +08:00
|
|
|
char name[32];
|
|
|
|
|
char desc[32];
|
|
|
|
|
unsigned int data[64];
|
2014-03-30 22:15:17 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct st_palettes palettes[] = {
|
2014-03-30 22:35:00 +02:00
|
|
|
{ "asqrealc", "AspiringSquire's Real palette",
|
|
|
|
|
{ 0x6c6c6c, 0x00268e, 0x0000a8, 0x400094,
|
2020-02-29 09:48:09 +08:00
|
|
|
0x700070, 0x780040, 0x700000, 0x621600,
|
|
|
|
|
0x442400, 0x343400, 0x005000, 0x004444,
|
|
|
|
|
0x004060, 0x000000, 0x101010, 0x101010,
|
|
|
|
|
0xbababa, 0x205cdc, 0x3838ff, 0x8020f0,
|
|
|
|
|
0xc000c0, 0xd01474, 0xd02020, 0xac4014,
|
|
|
|
|
0x7c5400, 0x586400, 0x008800, 0x007468,
|
|
|
|
|
0x00749c, 0x202020, 0x101010, 0x101010,
|
|
|
|
|
0xffffff, 0x4ca0ff, 0x8888ff, 0xc06cff,
|
|
|
|
|
0xff50ff, 0xff64b8, 0xff7878, 0xff9638,
|
|
|
|
|
0xdbab00, 0xa2ca20, 0x4adc4a, 0x2ccca4,
|
|
|
|
|
0x1cc2ea, 0x585858, 0x101010, 0x101010,
|
|
|
|
|
0xffffff, 0xb0d4ff, 0xc4c4ff, 0xe8b8ff,
|
|
|
|
|
0xffb0ff, 0xffb8e8, 0xffc4c4, 0xffd4a8,
|
|
|
|
|
0xffe890, 0xf0f4a4, 0xc0ffc0, 0xacf4f0,
|
|
|
|
|
0xa0e8ff, 0xc2c2c2, 0x202020, 0x101010 }
|
|
|
|
|
},
|
2025-04-23 17:19:41 -05:00
|
|
|
{ "restored-wii-vc", "Restored Wii VC palette",
|
|
|
|
|
{ 0x666666, 0x000095, 0x10008B, 0x39007D,
|
|
|
|
|
0x5C0068, 0x660000, 0x5C0000, 0x391800,
|
|
|
|
|
0x223700, 0x004316, 0x004300, 0x003916,
|
|
|
|
|
0x022D5E, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xA39EA3, 0x0043B9, 0x4502F1, 0x6902CF,
|
|
|
|
|
0x8C00AC, 0x960050, 0x962E02, 0x7E4200,
|
|
|
|
|
0x5C6600, 0x227D02, 0x167D02, 0x027D46,
|
|
|
|
|
0x02667E, 0x161616, 0x000000, 0x000000,
|
|
|
|
|
0xF2F2F2, 0x689EFF, 0x8C7BFF, 0xB970FF,
|
|
|
|
|
0xE671F2, 0xF266B9, 0xFE8968, 0xCF9E46,
|
|
|
|
|
0xACA03B, 0x7EBC02, 0x4EC745, 0x45C77E,
|
|
|
|
|
0x50C7C6, 0x4E4E4E, 0x000000, 0x000000,
|
|
|
|
|
0xFFFFFF, 0xC4DCFE, 0xC6C7F4, 0xDBC7FF,
|
|
|
|
|
0xE9BDFF, 0xF2C6DC, 0xF4D2C4, 0xDBC8AE,
|
|
|
|
|
0xDBDDA0, 0xCFE9AE, 0xB9EAAC, 0xAEDCB9,
|
|
|
|
|
0xA1D2C6, 0xDEDEDE, 0x000000, 0x000000 }
|
|
|
|
|
},
|
|
|
|
|
{ "wii-vc", "Wii Virtual Console palette",
|
2020-02-29 09:48:09 +08:00
|
|
|
{ 0x494949, 0x00006a, 0x090063, 0x290059,
|
|
|
|
|
0x42004a, 0x490000, 0x420000, 0x291100,
|
|
|
|
|
0x182700, 0x003010, 0x003000, 0x002910,
|
|
|
|
|
0x012043, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0x747174, 0x003084, 0x3101ac, 0x4b0194,
|
|
|
|
|
0x64007b, 0x6b0039, 0x6b2101, 0x5a2f00,
|
|
|
|
|
0x424900, 0x185901, 0x105901, 0x015932,
|
|
|
|
|
0x01495a, 0x101010, 0x000000, 0x000000,
|
|
|
|
|
0xadadad, 0x4a71b6, 0x6458d5, 0x8450e6,
|
|
|
|
|
0xa451ad, 0xad4984, 0xb5624a, 0x947132,
|
|
|
|
|
0x7b722a, 0x5a8601, 0x388e31, 0x318e5a,
|
|
|
|
|
0x398e8d, 0x383838, 0x000000, 0x000000,
|
|
|
|
|
0xb6b6b6, 0x8c9db5, 0x8d8eae, 0x9c8ebc,
|
|
|
|
|
0xa687bc, 0xad8d9d, 0xae968c, 0x9c8f7c,
|
|
|
|
|
0x9c9e72, 0x94a67c, 0x84a77b, 0x7c9d84,
|
|
|
|
|
0x73968d, 0xdedede, 0x000000, 0x000000 }
|
2016-05-18 19:49:13 -05:00
|
|
|
},
|
2017-09-04 23:58:06 +08:00
|
|
|
{ "rgb", "Nintendo RGB PPU palette",
|
2020-02-29 09:48:09 +08:00
|
|
|
{ 0x6D6D6D, 0x002492, 0x0000DB, 0x6D49DB,
|
|
|
|
|
0x92006D, 0xB6006D, 0xB62400, 0x924900,
|
|
|
|
|
0x6D4900, 0x244900, 0x006D24, 0x009200,
|
|
|
|
|
0x004949, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xB6B6B6, 0x006DDB, 0x0049FF, 0x9200FF,
|
|
|
|
|
0xB600FF, 0xFF0092, 0xFF0000, 0xDB6D00,
|
|
|
|
|
0x926D00, 0x249200, 0x009200, 0x00B66D,
|
|
|
|
|
0x009292, 0x242424, 0x000000, 0x000000,
|
|
|
|
|
0xFFFFFF, 0x6DB6FF, 0x9292FF, 0xDB6DFF,
|
|
|
|
|
0xFF00FF, 0xFF6DFF, 0xFF9200, 0xFFB600,
|
|
|
|
|
0xDBDB00, 0x6DDB00, 0x00FF00, 0x49FFDB,
|
|
|
|
|
0x00FFFF, 0x494949, 0x000000, 0x000000,
|
|
|
|
|
0xFFFFFF, 0xB6DBFF, 0xDBB6FF, 0xFFB6FF,
|
|
|
|
|
0xFF92FF, 0xFFB6B6, 0xFFDB92, 0xFFFF49,
|
|
|
|
|
0xFFFF6D, 0xB6FF49, 0x92FF6D, 0x49FFDB,
|
|
|
|
|
0x92DBFF, 0x929292, 0x000000, 0x000000 }
|
2017-09-04 23:58:06 +08:00
|
|
|
},
|
2016-08-11 21:42:27 -05:00
|
|
|
{ "yuv-v3", "FBX's YUV-V3 palette",
|
2020-02-29 09:48:09 +08:00
|
|
|
{ 0x666666, 0x002A88, 0x1412A7, 0x3B00A4,
|
|
|
|
|
0x5C007E, 0x6E0040, 0x6C0700, 0x561D00,
|
|
|
|
|
0x333500, 0x0C4800, 0x005200, 0x004C18,
|
|
|
|
|
0x003E5B, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xADADAD, 0x155FD9, 0x4240FF, 0x7527FE,
|
|
|
|
|
0xA01ACC, 0xB71E7B, 0xB53120, 0x994E00,
|
|
|
|
|
0x6B6D00, 0x388700, 0x0D9300, 0x008C47,
|
|
|
|
|
0x007AA0, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xFFFFFF, 0x64B0FF, 0x9290FF, 0xC676FF,
|
|
|
|
|
0xF26AFF, 0xFF6ECC, 0xFF8170, 0xEA9E22,
|
|
|
|
|
0xBCBE00, 0x88D800, 0x5CE430, 0x45E082,
|
|
|
|
|
0x48CDDE, 0x4F4F4F, 0x000000, 0x000000,
|
|
|
|
|
0xFFFFFF, 0xC0DFFF, 0xD3D2FF, 0xE8C8FF,
|
|
|
|
|
0xFAC2FF, 0xFFC4EA, 0xFFCCC5, 0xF7D8A5,
|
|
|
|
|
0xE4E594, 0xCFEF96, 0xBDF4AB, 0xB3F3CC,
|
|
|
|
|
0xB5EBF2, 0xB8B8B8, 0x000000, 0x000000 }
|
2016-05-18 20:09:54 -05:00
|
|
|
},
|
2016-11-24 13:42:06 -06:00
|
|
|
{ "unsaturated-final", "FBX's Unsaturated-Final palette",
|
2020-02-29 09:48:09 +08:00
|
|
|
{ 0x676767, 0x001F8E, 0x23069E, 0x40008E,
|
|
|
|
|
0x600067, 0x67001C, 0x5B1000, 0x432500,
|
|
|
|
|
0x313400, 0x074800, 0x004F00, 0x004622,
|
|
|
|
|
0x003A61, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xB3B3B3, 0x205ADF, 0x5138FB, 0x7A27EE,
|
|
|
|
|
0xA520C2, 0xB0226B, 0xAD3702, 0x8D5600,
|
|
|
|
|
0x6E7000, 0x2E8A00, 0x069200, 0x008A47,
|
|
|
|
|
0x037B9B, 0x101010, 0x000000, 0x000000,
|
|
|
|
|
0xFFFFFF, 0x62AEFF, 0x918BFF, 0xBC78FF,
|
|
|
|
|
0xE96EFF, 0xFC6CCD, 0xFA8267, 0xE29B26,
|
|
|
|
|
0xC0B901, 0x84D200, 0x58DE38, 0x46D97D,
|
|
|
|
|
0x49CED2, 0x494949, 0x000000, 0x000000,
|
|
|
|
|
0xFFFFFF, 0xC1E3FF, 0xD5D4FF, 0xE7CCFF,
|
|
|
|
|
0xFBC9FF, 0xFFC7F0, 0xFFD0C5, 0xF8DAAA,
|
|
|
|
|
0xEBE69A, 0xD1F19A, 0xBEF7AF, 0xB6F4CD,
|
|
|
|
|
0xB7F0EF, 0xB2B2B2, 0x000000, 0x000000 }
|
2016-05-18 20:56:39 -05:00
|
|
|
},
|
|
|
|
|
{ "sony-cxa2025as-us", "Sony CXA2025AS US palette",
|
2020-02-29 09:48:09 +08:00
|
|
|
{ 0x585858, 0x00238C, 0x00139B, 0x2D0585,
|
|
|
|
|
0x5D0052, 0x7A0017, 0x7A0800, 0x5F1800,
|
|
|
|
|
0x352A00, 0x093900, 0x003F00, 0x003C22,
|
|
|
|
|
0x00325D, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xA1A1A1, 0x0053EE, 0x153CFE, 0x6028E4,
|
|
|
|
|
0xA91D98, 0xD41E41, 0xD22C00, 0xAA4400,
|
|
|
|
|
0x6C5E00, 0x2D7300, 0x007D06, 0x007852,
|
|
|
|
|
0x0069A9, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xFFFFFF, 0x1FA5FE, 0x5E89FE, 0xB572FE,
|
|
|
|
|
0xFE65F6, 0xFE6790, 0xFE773C, 0xFE9308,
|
|
|
|
|
0xC4B200, 0x79CA10, 0x3AD54A, 0x11D1A4,
|
|
|
|
|
0x06BFFE, 0x424242, 0x000000, 0x000000,
|
|
|
|
|
0xFFFFFF, 0xA0D9FE, 0xBDCCFE, 0xE1C2FE,
|
|
|
|
|
0xFEBCFB, 0xFEBDD0, 0xFEC5A9, 0xFED18E,
|
|
|
|
|
0xE9DE86, 0xC7E992, 0xA8EEB0, 0x95ECD9,
|
|
|
|
|
0x91E4FE, 0xACACAC, 0x000000, 0x000000 }
|
2016-05-18 20:56:39 -05:00
|
|
|
},
|
|
|
|
|
{ "pal", "PAL palette",
|
2020-02-29 09:48:09 +08:00
|
|
|
{ 0x808080, 0x0000BA, 0x3700BF, 0x8400A6,
|
|
|
|
|
0xBB006A, 0xB7001E, 0xB30000, 0x912600,
|
|
|
|
|
0x7B2B00, 0x003E00, 0x00480D, 0x003C22,
|
|
|
|
|
0x002F66, 0x000000, 0x050505, 0x050505,
|
|
|
|
|
0xC8C8C8, 0x0059FF, 0x443CFF, 0xB733CC,
|
|
|
|
|
0xFE33AA, 0xFE375E, 0xFE371A, 0xD54B00,
|
|
|
|
|
0xC46200, 0x3C7B00, 0x1D8415, 0x009566,
|
|
|
|
|
0x0084C4, 0x111111, 0x090909, 0x090909,
|
|
|
|
|
0xFEFEFE, 0x0095FF, 0x6F84FF, 0xD56FFF,
|
|
|
|
|
0xFE77CC, 0xFE6F99, 0xFE7B59, 0xFE915F,
|
|
|
|
|
0xFEA233, 0xA6BF00, 0x51D96A, 0x4DD5AE,
|
|
|
|
|
0x00D9FF, 0x666666, 0x0D0D0D, 0x0D0D0D,
|
|
|
|
|
0xFEFEFE, 0x84BFFF, 0xBBBBFF, 0xD0BBFF,
|
|
|
|
|
0xFEBFEA, 0xFEBFCC, 0xFEC4B7, 0xFECCAE,
|
|
|
|
|
0xFED9A2, 0xCCE199, 0xAEEEB7, 0xAAF8EE,
|
|
|
|
|
0xB3EEFF, 0xDDDDDD, 0x111111, 0x111111 }
|
2016-08-11 22:08:17 -05:00
|
|
|
},
|
|
|
|
|
{ "bmf-final2", "BMF's Final 2 palette",
|
2020-02-29 09:48:09 +08:00
|
|
|
{ 0x525252, 0x000080, 0x08008A, 0x2C007E,
|
|
|
|
|
0x4A004E, 0x500006, 0x440000, 0x260800,
|
|
|
|
|
0x0A2000, 0x002E00, 0x003200, 0x00260A,
|
|
|
|
|
0x001C48, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xA4A4A4, 0x0038CE, 0x3416EC, 0x5E04DC,
|
|
|
|
|
0x8C00B0, 0x9A004C, 0x901800, 0x703600,
|
|
|
|
|
0x4C5400, 0x0E6C00, 0x007400, 0x006C2C,
|
|
|
|
|
0x005E84, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xFFFFFF, 0x4C9CFF, 0x7C78FF, 0xA664FF,
|
|
|
|
|
0xDA5AFF, 0xF054C0, 0xF06A56, 0xD68610,
|
|
|
|
|
0xBAA400, 0x76C000, 0x46CC1A, 0x2EC866,
|
|
|
|
|
0x34C2BE, 0x3A3A3A, 0x000000, 0x000000,
|
|
|
|
|
0xFFFFFF, 0xB6DAFF, 0xC8CAFF, 0xDAC2FF,
|
|
|
|
|
0xF0BEFF, 0xFCBCEE, 0xFAC2C0, 0xF2CCA2,
|
|
|
|
|
0xE6DA92, 0xCCE68E, 0xB8EEA2, 0xAEEABE,
|
|
|
|
|
0xAEE8E2, 0xB0B0B0, 0x000000, 0x000000 }
|
2016-08-11 22:08:17 -05:00
|
|
|
},
|
|
|
|
|
{ "bmf-final3", "BMF's Final 3 palette",
|
2020-02-29 09:48:09 +08:00
|
|
|
{ 0x686868, 0x001299, 0x1A08AA, 0x51029A,
|
|
|
|
|
0x7E0069, 0x8E001C, 0x7E0301, 0x511800,
|
|
|
|
|
0x1F3700, 0x014E00, 0x005A00, 0x00501C,
|
|
|
|
|
0x004061, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xB9B9B9, 0x0C5CD7, 0x5035F0, 0x8919E0,
|
|
|
|
|
0xBB0CB3, 0xCE0C61, 0xC02B0E, 0x954D01,
|
|
|
|
|
0x616F00, 0x1F8B00, 0x01980C, 0x00934B,
|
|
|
|
|
0x00819B, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xFFFFFF, 0x63B4FF, 0x9B91FF, 0xD377FF,
|
|
|
|
|
0xEF6AFF, 0xF968C0, 0xF97D6C, 0xED9B2D,
|
|
|
|
|
0xBDBD16, 0x7CDA1C, 0x4BE847, 0x35E591,
|
|
|
|
|
0x3FD9DD, 0x606060, 0x000000, 0x000000,
|
|
|
|
|
0xFFFFFF, 0xACE7FF, 0xD5CDFF, 0xEDBAFF,
|
|
|
|
|
0xF8B0FF, 0xFEB0EC, 0xFDBDB5, 0xF9D28E,
|
|
|
|
|
0xE8EB7C, 0xBBF382, 0x99F7A2, 0x8AF5D0,
|
|
|
|
|
0x92F4F1, 0xBEBEBE, 0x000000, 0x000000 }
|
2016-11-24 14:42:38 -06:00
|
|
|
},
|
2017-08-29 17:39:29 +08:00
|
|
|
{ "smooth-fbx", "FBX's Smooth palette",
|
2020-02-29 09:48:09 +08:00
|
|
|
{ 0x6A6D6A, 0x001380, 0x1E008A, 0x39007A,
|
|
|
|
|
0x550056, 0x5A0018, 0x4F1000, 0x3D1C00,
|
|
|
|
|
0x253200, 0x003D00, 0x004000, 0x003924,
|
|
|
|
|
0x002E55, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xB9BCB9, 0x1850C7, 0x4B30E3, 0x7322D6,
|
|
|
|
|
0x951FA9, 0x9D285C, 0x983700, 0x7F4C00,
|
|
|
|
|
0x5E6400, 0x227700, 0x027E02, 0x007645,
|
|
|
|
|
0x006E8A, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xFFFFFF, 0x68A6FF, 0x8C9CFF, 0xB586FF,
|
|
|
|
|
0xD975FD, 0xE377B9, 0xE58D68, 0xD49D29,
|
|
|
|
|
0xB3AF0C, 0x7BC211, 0x55CA47, 0x46CB81,
|
|
|
|
|
0x47C1C5, 0x4A4D4A, 0x000000, 0x000000,
|
|
|
|
|
0xFFFFFF, 0xCCEAFF, 0xDDDEFF, 0xECDAFF,
|
|
|
|
|
0xF8D7FE, 0xFCD6F5, 0xFDDBCF, 0xF9E7B5,
|
|
|
|
|
0xF1F0AA, 0xDAFAA9, 0xC9FFBC, 0xC3FBD7,
|
|
|
|
|
0xC4F6F6, 0xBEC1BE, 0x000000, 0x000000 }
|
2017-08-29 17:39:29 +08:00
|
|
|
},
|
2017-01-27 19:26:35 -06:00
|
|
|
{ "composite-direct-fbx", "FBX's Composite Direct palette",
|
2020-02-29 09:48:09 +08:00
|
|
|
{ 0x656565, 0x00127D, 0x18008E, 0x360082,
|
|
|
|
|
0x56005D, 0x5A0018, 0x4F0500, 0x381900,
|
|
|
|
|
0x1D3100, 0x003D00, 0x004100, 0x003B17,
|
|
|
|
|
0x002E55, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xAFAFAF, 0x194EC8, 0x472FE3, 0x6B1FD7,
|
|
|
|
|
0x931BAE, 0x9E1A5E, 0x993200, 0x7B4B00,
|
|
|
|
|
0x5B6700, 0x267A00, 0x008200, 0x007A3E,
|
|
|
|
|
0x006E8A, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xFFFFFF, 0x64A9FF, 0x8E89FF, 0xB676FF,
|
|
|
|
|
0xE06FFF, 0xEF6CC4, 0xF0806A, 0xD8982C,
|
|
|
|
|
0xB9B40A, 0x83CB0C, 0x5BD63F, 0x4AD17E,
|
|
|
|
|
0x4DC7CB, 0x4C4C4C, 0x000000, 0x000000,
|
|
|
|
|
0xFFFFFF, 0xC7E5FF, 0xD9D9FF, 0xE9D1FF,
|
|
|
|
|
0xF9CEFF, 0xFFCCF1, 0xFFD4CB, 0xF8DFB1,
|
|
|
|
|
0xEDEAA4, 0xD6F4A4, 0xC5F8B8, 0xBEF6D3,
|
|
|
|
|
0xBFF1F1, 0xB9B9B9, 0x000000, 0x000000 }
|
2016-11-24 14:42:38 -06:00
|
|
|
},
|
2017-03-12 15:20:39 -05:00
|
|
|
{ "pvm-style-d93-fbx", "FBX's PVM Style D93 palette",
|
2020-02-29 09:48:09 +08:00
|
|
|
{ 0x696B63, 0x001774, 0x1E0087, 0x340073,
|
|
|
|
|
0x560057, 0x5E0013, 0x531A00, 0x3B2400,
|
|
|
|
|
0x243000, 0x063A00, 0x003F00, 0x003B1E,
|
|
|
|
|
0x00334E, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xB9BBB3, 0x1453B9, 0x4D2CDA, 0x671EDE,
|
|
|
|
|
0x98189C, 0x9D2344, 0xA03E00, 0x8D5500,
|
|
|
|
|
0x656D00, 0x2C7900, 0x008100, 0x007D42,
|
|
|
|
|
0x00788A, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xFFFFFF, 0x69A8FF, 0x9691FF, 0xB28AFA,
|
|
|
|
|
0xEA7DFA, 0xF37BC7, 0xF28E59, 0xE6AD27,
|
|
|
|
|
0xD7C805, 0x90DF07, 0x64E53C, 0x45E27D,
|
|
|
|
|
0x48D5D9, 0x4E5048, 0x000000, 0x000000,
|
|
|
|
|
0xFFFFFF, 0xD2EAFF, 0xE2E2FF, 0xE9D8FF,
|
|
|
|
|
0xF5D2FF, 0xF8D9EA, 0xFADEB9, 0xF9E89B,
|
|
|
|
|
0xF3F28C, 0xD3FA91, 0xB8FCA8, 0xAEFACA,
|
|
|
|
|
0xCAF3F3, 0xBEC0B8, 0x000000, 0x000000 }
|
2017-01-27 20:02:10 -06:00
|
|
|
},
|
2017-03-12 15:10:06 -05:00
|
|
|
{ "ntsc-hardware-fbx", "FBX's NTSC Hardware palette",
|
2020-02-29 09:48:09 +08:00
|
|
|
{ 0x6A6D6A, 0x001380, 0x1E008A, 0x39007A,
|
|
|
|
|
0x550056, 0x5A0018, 0x4F1000, 0x382100,
|
|
|
|
|
0x213300, 0x003D00, 0x004000, 0x003924,
|
|
|
|
|
0x002E55, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xB9BCB9, 0x1850C7, 0x4B30E3, 0x7322D6,
|
|
|
|
|
0x951FA9, 0x9D285C, 0x963C00, 0x7A5100,
|
|
|
|
|
0x5B6700, 0x227700, 0x027E02, 0x007645,
|
|
|
|
|
0x006E8A, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xFFFFFF, 0x68A6FF, 0x9299FF, 0xB085FF,
|
|
|
|
|
0xD975FD, 0xE377B9, 0xE58D68, 0xCFA22C,
|
|
|
|
|
0xB3AF0C, 0x7BC211, 0x55CA47, 0x46CB81,
|
|
|
|
|
0x47C1C5, 0x4A4D4A, 0x000000, 0x000000,
|
|
|
|
|
0xFFFFFF, 0xCCEAFF, 0xDDDEFF, 0xECDAFF,
|
|
|
|
|
0xF8D7FE, 0xFCD6F5, 0xFDDBCF, 0xF9E7B5,
|
|
|
|
|
0xF1F0AA, 0xDAFAA9, 0xC9FFBC, 0xC3FBD7,
|
|
|
|
|
0xC4F6F6, 0xBEC1BE, 0x000000, 0x000000 }
|
2017-01-27 20:02:10 -06:00
|
|
|
},
|
2017-01-27 19:39:32 -06:00
|
|
|
{ "nes-classic-fbx-fs", "FBX's NES-Classic FS palette",
|
2020-02-29 09:48:09 +08:00
|
|
|
{ 0x60615F, 0x000083, 0x1D0195, 0x340875,
|
|
|
|
|
0x51055E, 0x56000F, 0x4C0700, 0x372308,
|
|
|
|
|
0x203A0B, 0x0F4B0E, 0x194C16, 0x02421E,
|
|
|
|
|
0x023154, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xA9AAA8, 0x104BBF, 0x4712D8, 0x6300CA,
|
|
|
|
|
0x8800A9, 0x930B46, 0x8A2D04, 0x6F5206,
|
|
|
|
|
0x5C7114, 0x1B8D12, 0x199509, 0x178448,
|
|
|
|
|
0x206B8E, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xFBFBFB, 0x6699F8, 0x8974F9, 0xAB58F8,
|
|
|
|
|
0xD557EF, 0xDE5FA9, 0xDC7F59, 0xC7A224,
|
|
|
|
|
0xA7BE03, 0x75D703, 0x60E34F, 0x3CD68D,
|
|
|
|
|
0x56C9CC, 0x414240, 0x000000, 0x000000,
|
|
|
|
|
0xFBFBFB, 0xBED4FA, 0xC9C7F9, 0xD7BEFA,
|
|
|
|
|
0xE8B8F9, 0xF5BAE5, 0xF3CAC2, 0xDFCDA7,
|
|
|
|
|
0xD9E09C, 0xC9EB9E, 0xC0EDB8, 0xB5F4C7,
|
|
|
|
|
0xB9EAE9, 0xABABAB, 0x000000, 0x000000 }
|
2016-11-24 15:01:49 -06:00
|
|
|
},
|
|
|
|
|
{ "nescap", "RGBSource's NESCAP palette",
|
2020-02-29 09:48:09 +08:00
|
|
|
{ 0x646365, 0x001580, 0x1D0090, 0x380082,
|
|
|
|
|
0x56005D, 0x5A001A, 0x4F0900, 0x381B00,
|
|
|
|
|
0x1E3100, 0x003D00, 0x004100, 0x003A1B,
|
|
|
|
|
0x002F55, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xAFADAF, 0x164BCA, 0x472AE7, 0x6B1BDB,
|
|
|
|
|
0x9617B0, 0x9F185B, 0x963001, 0x7B4800,
|
|
|
|
|
0x5A6600, 0x237800, 0x017F00, 0x00783D,
|
|
|
|
|
0x006C8C, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xFFFFFF, 0x60A6FF, 0x8F84FF, 0xB473FF,
|
|
|
|
|
0xE26CFF, 0xF268C3, 0xEF7E61, 0xD89527,
|
|
|
|
|
0xBAB307, 0x81C807, 0x57D43D, 0x47CF7E,
|
|
|
|
|
0x4BC5CD, 0x4C4B4D, 0x000000, 0x000000,
|
|
|
|
|
0xFFFFFF, 0xC2E0FF, 0xD5D2FF, 0xE3CBFF,
|
|
|
|
|
0xF7C8FF, 0xFEC6EE, 0xFECEC6, 0xF6D7AE,
|
|
|
|
|
0xE9E49F, 0xD3ED9D, 0xC0F2B2, 0xB9F1CC,
|
|
|
|
|
0xBAEDED, 0xBAB9BB, 0x000000, 0x000000 }
|
2017-08-30 23:31:14 -06:00
|
|
|
},
|
2017-08-30 20:51:30 -06:00
|
|
|
{ "wavebeam", "nakedarthur's Wavebeam palette",
|
2020-02-29 09:48:09 +08:00
|
|
|
{ 0X6B6B6B, 0X001B88, 0X21009A, 0X40008C,
|
|
|
|
|
0X600067, 0X64001E, 0X590800, 0X481600,
|
|
|
|
|
0X283600, 0X004500, 0X004908, 0X00421D,
|
|
|
|
|
0X003659, 0X000000, 0X000000, 0X000000,
|
|
|
|
|
0XB4B4B4, 0X1555D3, 0X4337EF, 0X7425DF,
|
|
|
|
|
0X9C19B9, 0XAC0F64, 0XAA2C00, 0X8A4B00,
|
|
|
|
|
0X666B00, 0X218300, 0X008A00, 0X008144,
|
|
|
|
|
0X007691, 0X000000, 0X000000, 0X000000,
|
|
|
|
|
0XFFFFFF, 0X63B2FF, 0X7C9CFF, 0XC07DFE,
|
|
|
|
|
0XE977FF, 0XF572CD, 0XF4886B, 0XDDA029,
|
|
|
|
|
0XBDBD0A, 0X89D20E, 0X5CDE3E, 0X4BD886,
|
|
|
|
|
0X4DCFD2, 0X525252, 0X000000, 0X000000,
|
|
|
|
|
0XFFFFFF, 0XBCDFFF, 0XD2D2FF, 0XE1C8FF,
|
|
|
|
|
0XEFC7FF, 0XFFC3E1, 0XFFCAC6, 0XF2DAAD,
|
|
|
|
|
0XEBE3A0, 0XD2EDA2, 0XBCF4B4, 0XB5F1CE,
|
|
|
|
|
0XB6ECF1, 0XBFBFBF, 0X000000, 0X000000 }
|
2022-06-09 13:58:15 +08:00
|
|
|
},
|
|
|
|
|
{ "digital-prime-fbx", "FBX's Digital Prime palette",
|
|
|
|
|
{ 0x616161, 0x000088, 0x1F0D99, 0x371379,
|
|
|
|
|
0x561260, 0x5D0010, 0x520E00, 0x3A2308,
|
|
|
|
|
0x21350C, 0x0D410E, 0x174417, 0x003A1F,
|
|
|
|
|
0x002F57, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xAAAAAA, 0x0D4DC4, 0x4B24DE, 0x6912CF,
|
|
|
|
|
0x9014AD, 0x9D1C48, 0x923404, 0x735005,
|
|
|
|
|
0x5D6913, 0x167A11, 0x138008, 0x127649,
|
|
|
|
|
0x1C6691, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xFCFCFC, 0x639AFC, 0x8A7EFC, 0xB06AFC,
|
|
|
|
|
0xDD6DF2, 0xE771AB, 0xE38658, 0xCC9E22,
|
|
|
|
|
0xA8B100, 0x72C100, 0x5ACD4E, 0x34C28E,
|
|
|
|
|
0x4FBECE, 0x424242, 0x000000, 0x000000,
|
|
|
|
|
0xFCFCFC, 0xBED4FC, 0xCACAFC, 0xD9C4FC,
|
|
|
|
|
0xECC1FC, 0xFAC3E7, 0xF7CEC3, 0xE2CDA7,
|
|
|
|
|
0xDADB9C, 0xC8E39E, 0xBFE5B8, 0xB2EBC8,
|
|
|
|
|
0xB7E5EB, 0xACACAC, 0x000000, 0x000000 }
|
|
|
|
|
},
|
|
|
|
|
{ "magnum-fbx", "FBX's Magnum palette",
|
|
|
|
|
{ 0x696969, 0x00148F, 0x1E029B, 0x3F008A,
|
|
|
|
|
0x600060, 0x660017, 0x570D00, 0x451B00,
|
|
|
|
|
0x243400, 0x004200, 0x004500, 0x003C1F,
|
|
|
|
|
0x00315C, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xAFAFAF, 0x0F51DD, 0x442FF3, 0x7220E2,
|
|
|
|
|
0xA319B3, 0xAE1C51, 0xA43400, 0x884D00,
|
|
|
|
|
0x676D00, 0x208000, 0x008B00, 0x007F42,
|
|
|
|
|
0x006C97, 0x010101, 0x000000, 0x000000,
|
|
|
|
|
0xFFFFFF, 0x65AAFF, 0x8C96FF, 0xB983FF,
|
|
|
|
|
0xDD6FFF, 0xEA6FBD, 0xEB8466, 0xDCA21F,
|
|
|
|
|
0xBAB403, 0x7ECB07, 0x54D33E, 0x3CD284,
|
|
|
|
|
0x3EC7CC, 0x4B4B4B, 0x000000, 0x000000,
|
|
|
|
|
0xFFFFFF, 0xBDE2FF, 0xCECFFF, 0xE6C2FF,
|
|
|
|
|
0xF6BCFF, 0xF9C2ED, 0xFACFC6, 0xF8DEAC,
|
|
|
|
|
0xEEE9A1, 0xD0F59F, 0xBBF5AF, 0xB3F5CD,
|
|
|
|
|
0xB9EDF0, 0xB9B9B9, 0x000000, 0x000000 }
|
|
|
|
|
},
|
|
|
|
|
{ "smooth-v2-fbx", "FBX's Smooth V2 palette",
|
|
|
|
|
{ 0x6A6A6A, 0x00148F, 0x1E029B, 0x3F008A,
|
|
|
|
|
0x600060, 0x660017, 0x570D00, 0x3C1F00,
|
|
|
|
|
0x1B3300, 0x004200, 0x004500, 0x003C1F,
|
|
|
|
|
0x00315C, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xB9B9B9, 0x0F4BD4, 0x412DEB, 0x6C1DD9,
|
|
|
|
|
0x9C17AB, 0xA71A4D, 0x993200, 0x7C4A00,
|
|
|
|
|
0x546400, 0x1A7800, 0x007F00, 0x00763E,
|
|
|
|
|
0x00678F, 0x010101, 0x000000, 0x000000,
|
|
|
|
|
0xFFFFFF, 0x68A6FF, 0x8C9CFF, 0xB586FF,
|
|
|
|
|
0xD975FD, 0xE377B9, 0xE58D68, 0xD49D29,
|
|
|
|
|
0xB3AF0C, 0x7BC211, 0x55CA47, 0x46CB81,
|
|
|
|
|
0x47C1C5, 0x4A4A4A, 0x000000, 0x000000,
|
|
|
|
|
0xFFFFFF, 0xCCEAFF, 0xDDDEFF, 0xECDAFF,
|
|
|
|
|
0xF8D7FE, 0xFCD6F5, 0xFDDBCF, 0xF9E7B5,
|
|
|
|
|
0xF1F0AA, 0xDAFAA9, 0xC9FFBC, 0xC3FBD7,
|
|
|
|
|
0xC4F6F6, 0xBEBEBE, 0x000000, 0x000000 }
|
|
|
|
|
},
|
|
|
|
|
{ "nes-classic-fbx", "FBX's NES Classic palette",
|
|
|
|
|
{ 0x616161, 0x000088, 0x1F0D99, 0x371379,
|
|
|
|
|
0x561260, 0x5D0010, 0x520E00, 0x3A2308,
|
|
|
|
|
0x21350C, 0x0D410E, 0x174417, 0x003A1F,
|
|
|
|
|
0x002F57, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xAAAAAA, 0x0D4DC4, 0x4B24DE, 0x6912CF,
|
|
|
|
|
0x9014AD, 0x9D1C48, 0x923404, 0x735005,
|
|
|
|
|
0x5D6913, 0x167A11, 0x138008, 0x127649,
|
|
|
|
|
0x1C6691, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xFCFCFC, 0x639AFC, 0x8A7EFC, 0xB06AFC,
|
|
|
|
|
0xDD6DF2, 0xE771AB, 0xE38658, 0xCC9E22,
|
|
|
|
|
0xA8B100, 0x72C100, 0x5ACD4E, 0x34C28E,
|
|
|
|
|
0x4FBECE, 0x424242, 0x000000, 0x000000,
|
|
|
|
|
0xFCFCFC, 0xBED4FC, 0xCACAFC, 0xD9C4FC,
|
|
|
|
|
0xECC1FC, 0xFAC3E7, 0xF7CEC3, 0xE2CDA7,
|
|
|
|
|
0xDADB9C, 0xC8E39E, 0xBFE5B8, 0xB2EBC8,
|
|
|
|
|
0xB7E5EB, 0xACACAC, 0x000000, 0x000000 }
|
2024-03-02 14:26:27 +01:00
|
|
|
},
|
|
|
|
|
{ "royaltea", "Royaltea palette (PVM-2530)",
|
|
|
|
|
{ 0x5A6165, 0x0023A8, 0x0F17B0, 0x28129F,
|
|
|
|
|
0x550B61, 0x6B0A11, 0x6E0D00, 0x5E1900,
|
|
|
|
|
0x3C2402, 0x003104, 0x003508, 0x00341F,
|
|
|
|
|
0x002C55, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xA7B5BC, 0x0059FF, 0x2A44FF, 0x523CF1,
|
|
|
|
|
0x9F34BA, 0xB32846, 0xBB2D09, 0x9E4100,
|
|
|
|
|
0x865A00, 0x246D02, 0x007312, 0x007156,
|
|
|
|
|
0x0066A6, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xFFFFFF, 0x4B9FFF, 0x5A91FF, 0x867EFF,
|
|
|
|
|
0xD97DFF, 0xFF95CF, 0xFF8E76, 0xF7A247,
|
|
|
|
|
0xEFB412, 0x8CC51C, 0x48D04A, 0x10D197,
|
|
|
|
|
0x00C9F0, 0x43484B, 0x000000, 0x000000,
|
|
|
|
|
0xFFFFFF, 0xB1D9FF, 0xB1CFFF, 0xBCC8FF,
|
|
|
|
|
0xE3C8FF, 0xFFD3F7, 0xFFD5CB, 0xFFDEB9,
|
|
|
|
|
0xFFE5AD, 0xDBF6AF, 0xB7FBC4, 0x9CFBE6,
|
|
|
|
|
0x96F7FF, 0xB1C0C7, 0x000000, 0x000000 }
|
|
|
|
|
},
|
|
|
|
|
{ "mugicha", "Mugicha palette",
|
|
|
|
|
{ 0x5c6164, 0x0021a0, 0x00109c, 0x290c91,
|
|
|
|
|
0x520a5c, 0x6d000e, 0x590000, 0x430d00,
|
|
|
|
|
0x352200, 0x003800, 0x003d00, 0x003621,
|
|
|
|
|
0x00294c, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xaab5ba, 0x0059f0, 0x2945f7, 0x523cf7,
|
|
|
|
|
0xac2dc2, 0xbc095d, 0xbd2c08, 0x964200,
|
|
|
|
|
0x825400, 0x007600, 0x007a00, 0x007152,
|
|
|
|
|
0x0062a0, 0x000000, 0x000000, 0x000000,
|
|
|
|
|
0xffffff, 0x2daaff, 0x5a92ff, 0x967eff,
|
|
|
|
|
0xeb7dff, 0xff82d0, 0xff8e73, 0xf79b3d,
|
|
|
|
|
0xe6b610, 0x73c40f, 0x32d141, 0x05cb88,
|
|
|
|
|
0x00c8f0, 0x414548, 0x000000, 0x000000,
|
|
|
|
|
0xffffff, 0xb1d9ff, 0xc8d2ff, 0xe2ccff,
|
|
|
|
|
0xffccff, 0xffc6fb, 0xffd0cb, 0xffdeb9,
|
|
|
|
|
0xffe5ad, 0xdbf6af, 0xb7fbc4, 0x9cfbe6,
|
|
|
|
|
0x96f7ff, 0xb4c0c5, 0x000000, 0x000000 }
|
2017-08-30 20:51:30 -06:00
|
|
|
}
|
2014-03-30 22:15:17 +02:00
|
|
|
};
|
|
|
|
|
|
2021-10-22 13:30:27 +01:00
|
|
|
/* ========================================
|
|
|
|
|
* Palette switching START
|
|
|
|
|
* ======================================== */
|
|
|
|
|
|
|
|
|
|
/* Period in frames between palette switches
|
|
|
|
|
* when holding RetroPad L2 + Left/Right */
|
|
|
|
|
#define PALETTE_SWITCH_PERIOD 30
|
|
|
|
|
|
|
|
|
|
static bool libretro_supports_set_variable = false;
|
|
|
|
|
static bool palette_switch_enabled = false;
|
|
|
|
|
static unsigned palette_switch_counter = 0;
|
|
|
|
|
struct retro_core_option_value *palette_opt_values = NULL;
|
|
|
|
|
static const char *palette_labels[PAL_TOTAL] = {0};
|
|
|
|
|
|
|
|
|
|
static uint32_t palette_switch_get_current_index(void)
|
|
|
|
|
{
|
|
|
|
|
if (current_palette < PAL_INTERNAL)
|
|
|
|
|
return current_palette + 1;
|
|
|
|
|
|
|
|
|
|
switch (current_palette)
|
|
|
|
|
{
|
|
|
|
|
case PAL_DEFAULT:
|
|
|
|
|
return 0;
|
|
|
|
|
case PAL_RAW:
|
|
|
|
|
case PAL_CUSTOM:
|
|
|
|
|
return current_palette - 1;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Cannot happen */
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void palette_switch_init(void)
|
|
|
|
|
{
|
|
|
|
|
size_t i;
|
2021-12-04 14:41:07 +01:00
|
|
|
struct retro_core_option_v2_definition *opt_defs = option_defs;
|
2021-10-22 13:30:27 +01:00
|
|
|
struct retro_core_option_v2_definition *opt_def = NULL;
|
|
|
|
|
#ifndef HAVE_NO_LANGEXTRA
|
|
|
|
|
struct retro_core_option_v2_definition *opt_defs_intl = NULL;
|
|
|
|
|
struct retro_core_option_v2_definition *opt_def_intl = NULL;
|
|
|
|
|
unsigned language = 0;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
libretro_supports_set_variable = false;
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_SET_VARIABLE, NULL))
|
|
|
|
|
libretro_supports_set_variable = true;
|
|
|
|
|
|
|
|
|
|
palette_switch_enabled = libretro_supports_set_variable;
|
|
|
|
|
palette_switch_counter = 0;
|
|
|
|
|
|
|
|
|
|
#ifndef HAVE_NO_LANGEXTRA
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_LANGUAGE, &language) &&
|
|
|
|
|
(language < RETRO_LANGUAGE_LAST) &&
|
|
|
|
|
(language != RETRO_LANGUAGE_ENGLISH) &&
|
|
|
|
|
options_intl[language])
|
|
|
|
|
opt_defs_intl = options_intl[language]->definitions;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
/* Find option corresponding to palettes key */
|
|
|
|
|
for (opt_def = opt_defs; opt_def->key; opt_def++)
|
|
|
|
|
if (!strcmp(opt_def->key, "fceumm_palette"))
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
/* Cache option values array for fast access
|
|
|
|
|
* when setting palette index */
|
|
|
|
|
palette_opt_values = opt_def->values;
|
|
|
|
|
|
|
|
|
|
/* Loop over all palette values and fetch
|
|
|
|
|
* palette labels for notification purposes */
|
|
|
|
|
for (i = 0; i < PAL_TOTAL; i++)
|
|
|
|
|
{
|
|
|
|
|
const char *value = opt_def->values[i].value;
|
|
|
|
|
const char *value_label = NULL;
|
|
|
|
|
|
|
|
|
|
/* Check if we have a localised palette label */
|
|
|
|
|
#ifndef HAVE_NO_LANGEXTRA
|
|
|
|
|
if (opt_defs_intl)
|
|
|
|
|
{
|
|
|
|
|
/* Find localised option corresponding to key */
|
|
|
|
|
for (opt_def_intl = opt_defs_intl;
|
|
|
|
|
opt_def_intl->key;
|
|
|
|
|
opt_def_intl++)
|
|
|
|
|
{
|
|
|
|
|
if (!strcmp(opt_def_intl->key, "fceumm_palette"))
|
|
|
|
|
{
|
|
|
|
|
size_t j = 0;
|
|
|
|
|
|
|
|
|
|
/* Search for current option value */
|
|
|
|
|
for (;;)
|
|
|
|
|
{
|
|
|
|
|
const char *value_intl = opt_def_intl->values[j].value;
|
|
|
|
|
|
|
|
|
|
if (!value_intl)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
if (!strcmp(value, value_intl))
|
|
|
|
|
{
|
|
|
|
|
/* We have a match; fetch localised label */
|
|
|
|
|
value_label = opt_def_intl->values[j].label;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
j++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
/* If localised palette label is unset,
|
|
|
|
|
* use label from option_defs_us or fallback
|
|
|
|
|
* to value itself */
|
|
|
|
|
if (!value_label)
|
|
|
|
|
value_label = opt_def->values[i].label;
|
|
|
|
|
if (!value_label)
|
|
|
|
|
value_label = value;
|
|
|
|
|
|
|
|
|
|
palette_labels[i] = value_label;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void palette_switch_deinit(void)
|
|
|
|
|
{
|
|
|
|
|
libretro_supports_set_variable = false;
|
|
|
|
|
palette_switch_enabled = false;
|
|
|
|
|
palette_switch_counter = 0;
|
|
|
|
|
palette_opt_values = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void palette_switch_set_index(uint32_t palette_index)
|
|
|
|
|
{
|
|
|
|
|
struct retro_variable var = {0};
|
|
|
|
|
|
|
|
|
|
if (palette_index >= PAL_TOTAL)
|
|
|
|
|
palette_index = PAL_TOTAL - 1;
|
|
|
|
|
|
|
|
|
|
/* Notify frontend of option value changes */
|
|
|
|
|
var.key = "fceumm_palette";
|
|
|
|
|
var.value = palette_opt_values[palette_index].value;
|
|
|
|
|
environ_cb(RETRO_ENVIRONMENT_SET_VARIABLE, &var);
|
|
|
|
|
|
|
|
|
|
/* Display notification message */
|
|
|
|
|
FCEUD_DispMessage(RETRO_LOG_INFO, 2000, palette_labels[palette_index]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ========================================
|
|
|
|
|
* Palette switching END
|
|
|
|
|
* ======================================== */
|
|
|
|
|
|
2022-04-06 13:36:22 +01:00
|
|
|
/* ========================================
|
|
|
|
|
* Stereo Filter START
|
|
|
|
|
* ======================================== */
|
|
|
|
|
|
|
|
|
|
enum stereo_filter_type
|
|
|
|
|
{
|
|
|
|
|
STEREO_FILTER_NULL = 0,
|
|
|
|
|
STEREO_FILTER_DELAY
|
|
|
|
|
};
|
|
|
|
|
static enum stereo_filter_type current_stereo_filter = STEREO_FILTER_NULL;
|
|
|
|
|
|
|
|
|
|
#define STEREO_FILTER_DELAY_MS_DEFAULT 15.0f;
|
|
|
|
|
typedef struct
|
|
|
|
|
{
|
|
|
|
|
int32_t *samples;
|
|
|
|
|
size_t samples_size;
|
|
|
|
|
size_t samples_pos;
|
|
|
|
|
size_t delay_count;
|
|
|
|
|
} stereo_filter_delay_t;
|
|
|
|
|
static stereo_filter_delay_t stereo_filter_delay;
|
|
|
|
|
static float stereo_filter_delay_ms = STEREO_FILTER_DELAY_MS_DEFAULT;
|
|
|
|
|
|
|
|
|
|
static void stereo_filter_apply_null(int32_t *sound_buffer, size_t size)
|
|
|
|
|
{
|
|
|
|
|
size_t i;
|
|
|
|
|
/* Each element of sound_buffer is a 16 bit mono sample
|
|
|
|
|
* stored in a 32 bit value. We convert this to stereo
|
|
|
|
|
* by copying the mono sample to both the high and low
|
|
|
|
|
* 16 bit regions of the value and casting sound_buffer
|
|
|
|
|
* to int16_t when uploading to the frontend */
|
|
|
|
|
for (i = 0; i < size; i++)
|
|
|
|
|
sound_buffer[i] = (sound_buffer[i] << 16) |
|
|
|
|
|
(sound_buffer[i] & 0xFFFF);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void stereo_filter_apply_delay(int32_t *sound_buffer, size_t size)
|
|
|
|
|
{
|
|
|
|
|
size_t delay_capacity = stereo_filter_delay.samples_size -
|
|
|
|
|
stereo_filter_delay.samples_pos;
|
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
|
|
/* Copy current samples into the delay buffer
|
|
|
|
|
* (resizing if required) */
|
|
|
|
|
if (delay_capacity < size)
|
|
|
|
|
{
|
|
|
|
|
int32_t *tmp_buffer = NULL;
|
|
|
|
|
size_t tmp_buffer_size;
|
|
|
|
|
|
|
|
|
|
tmp_buffer_size = stereo_filter_delay.samples_size + (size - delay_capacity);
|
|
|
|
|
tmp_buffer_size = (tmp_buffer_size << 1) - (tmp_buffer_size >> 1);
|
|
|
|
|
tmp_buffer = (int32_t *)malloc(tmp_buffer_size * sizeof(int32_t));
|
|
|
|
|
|
core: memory-safety, leak, and savestate-portability audit fixes
Squashed series of ~50 distinct bugs found during a multi-day
security/correctness audit, ranging from ROM-triggerable heap
corruption and savestate-triggered OOB read primitives down to
obscure cross-platform savestate breakage.
Build is clean on `make platform=unix` with zero new warnings.
CRITICAL (ROM-triggerable, exploitable on the user's machine)
=============================================================
* ines.c iNES 2.0 PRG/CHR exponent overflow leading to undersized
allocation followed by heap buffer overflow on fread (pow() with
attacker-chosen exponent up to 63). Cap exponent at 30 and
compute size in uint32 with explicit cap below 2 GiB before
storing in int.
* ines.c miscROMSize int wraparound from attacker-controlled PRG/
CHR sizes; previous '& 0x8000000' check missed common underflow
cases. Compute in int64 and reject <= 0 or > 128 MiB.
* unif.c MAPR chunk OOB heap read on chunks of size < 4. Validate
chunk size before allocating board-name buffer.
* unif.c chunk size truncation: int conversion + missing cap allowed
a 4 GiB chunk size to wrap. Cap at 16 MiB.
* unif.c FixRomSize infinite loop on size > 0x80000000. Cap input.
* unif.c DINF chunk: months[(m - 1) % 12] is UB for m==0; m comes
from the .unf file. Clamp m into [1..12] before subtracting.
* unif.c NAME chunk: GameInfo->name = malloc(...) was unchecked.
* nsf.c size underflow: FCEU_fgetsize() - 0x80 wraps to ~UINT64_MAX
on tiny files, propagating a huge size into uppow2/FCEU_malloc.
Validate size > 0x80 before subtracting.
* nsf.c NSFMaxBank * 4096 cap tightened from 1<<20 (UB on signed-int
overflow) to 1<<19 (fits in int).
* general.c uppow2 had signed-int UB (1 << 32) and wrapped to 0 for
inputs near uint32 max. Cap at 0x80000000.
* cheat.c SubCheats[256] BSS overflow when more than 256 GG/PAR
substitution cheats are active. The array is adjacent to MMapPtrs[64]
in BSS which is exposed via retro_get_memory_data, making this
overflow visible from outside the core.
* libretro.c retro_cheat_set strcpy stack overflow with arbitrary-
length cheat strings from the frontend. Use strlcpy.
CRITICAL (savestate-triggerable on a malicious .fcs file)
=========================================================
* fds.c FDS savestate OOB read primitive: InDisk loaded from
savestate is used as index into 8-element diskdata[] static
pointer array. A value 8..254 dereferences arbitrary memory as
a pointer (heap-read primitive). Also bounds-checked SelectDisk,
mapperFDS_block, mapperFDS_blockstart, mapperFDS_diskaddr,
mapperFDS_blocklen so blockstart+diskaddr stays within the 65500-
byte disk buffer.
* 11 mappers had savestate-loaded variables masked at write time
but not at restore time, used as indices into fixed arrays:
- 88.c, KS7037.c, 112.c cmd indexes reg[8]
- sachen.c (S8259, S74LS374N) cmd indexes latch[8]
- 357.c dipswitch indexes outer_bank[4]
- unrom512.c flash_state indexes erase_a/d/b[5]
- 368.c preg indexes banks[8]
- 69.c sndcmd indexes sreg[14]
- mmc2and4.c latch0/latch1 index creg[4]
None individually exploitable into RCE - the array writes corrupt
adjacent BSS with constrained data flowing in - but each is an
out-of-bounds read or write from attacker-controllable input.
HIGH (memory-safety, reachable on any load)
===========================================
* fceu-memory.c FCEU_malloc deref-of-NULL on allocation failure
('ret = 0; memset(ret, 0, size);').
* file.c multiple FCEUFILE/MakeMemWrap/MakeMemWrapBuffer unchecked
allocations and unchecked filestream_tell return.
* libretro.c GameInfo NULL-derefs in three entry points
(retro_set_controller_port_device, retro_get_memory_data,
retro_get_memory_size) reachable on operations called before a
successful load.
* libretro.c framebuffer leak ~256 KB per failed FCEUI_LoadGame
(the libretro frontend doesn't call retro_unload_game on a
failed load).
* libretro.c 3DS retro_deinit unchecked linearFree.
* libretro.c stereo / NTSC filter unchecked malloc returns.
* fceu.c FCEUI_LoadGame unchecked GameInfo malloc.
* fds.c SubLoad and FDSLoad unchecked FCEU_malloc on diskdatao
backup buffers (NULL-deref in subsequent memcpy).
* fceu.c AllocGenieRW partial-failure leak: AReadG allocated but
BWriteG malloc fails -> 256 KB leak per retry.
* input/bworld.c Update(): unbounded strcpy from attacker-supplied
data into 20-byte bdata. Replaced with length-bounded copy.
* cart.c setprg2r/4r and setchr1r/2r/4r/8r mask-underflow guards.
SetupCartPRGMapping/SetupCartCHRMapping compute (size >> N) - 1
which underflows to 0xFFFFFFFF for chips smaller than the unit.
setprg8r/16r/32r and setchr8r already had defensive size checks;
the smaller units did not. malee.c (2 KB chip) and mapper 218
(2 KB NTARAM-as-CHR) accidentally avoid OOB; the primitive is
unsafe for any future board.
* video.c FCEU_InitVirtualVideo XBuf/XDBuf partial-failure leak.
* video.c, fceu.c FCEU_DispMessage / FCEU_printf / FCEU_PrintError:
vsprintf into fixed stack buffers replaced with vsnprintf.
* core: validate magic-string read length on FDS/UNIF/NSF load
(reject files too short to contain the magic so previous static
buffer / stack garbage doesn't spuriously match).
LEAKS (every cart load/unload cycle)
====================================
* mmc5.c MMC5 cart-side: WRAM (up to 64K) + MMC5fill (1K) + ExRAM
(1K) leaked per cycle. NSFMMC5_Close existed but was only used
for NSF code path.
* onebus.c Mapper 270/436: 8 KB CHRRAM leaked per cycle.
* 330.c, 375.c, 528.c: 8 KB WRAM each, no Close at all.
CORRECTNESS (UB / ABI / endianness)
===================================
* fceu-endian.c FlipByteOrder loop bound was 'count' instead of
'count/2', making it a no-op for every even count and silently
breaking savestate portability for every FCEUSTATE_RLSB-marked
field. The '#ifndef GEKKO' workarounds scattered through the
codebase (sound.c, vrc7.c, vrc6.c) were symptoms of this root
cause.
* state.c AddExState bounds check ran AFTER writing the entry, so
when SFEXINDEX hit the 63-entry cap the next call would write
the entry then immediately overwrite it with the terminator.
* state.c FCEUSS_Save_Mem: post-save callback was guarded by
'if (SPreSave)' instead of 'if (SPostSave)'.
* Sequence-point UB in counter expressions across 4 board files,
e.g. 'x = !x ? a : --x' and 'x = ++x % n'. GCC -Wsequence-point
flags all five sites (285.c, 413.c, asic_mmc3.c, jyasic.c).
* SFORMAT size mismatches between declared variable type and
AddExState size argument:
- asic_vrc3.c VRC3_count/VRC3_reload (uint16, saved as 1 byte)
- mmc3.c m555_count (uint32, saved as 2 bytes), m555_count_-
expired (uint8, saved as 2 bytes - OOB write into adjacent
BSS).
* unrom512.c UNROM-512 mapper 30 .srm save format: host-endian
uint32 flash write counters. Now stored as LE on disk regardless
of host (Battle Kid 2, Twin Dragons, Lizard, Sole, etc.).
* ~70 multi-byte single-variable SFORMAT/AddExState entries across
36 board files were saved as host-byte-order. After the
FlipByteOrder fix, these are now byte-swapped on BE so cross-
platform savestates round-trip correctly.
* coolgirl.c new ExStateLE() macro added; 22 multi-byte sites.
* pic16c5x.c 11 multi-byte AddExState calls (m_PC, m_PREVPC,
m_CONFIG, m_WDT, m_prescaler, m_opcode, m_STACK[0/1], m_icount,
m_delay_timer, m_rtcc, m_inst_cycles, m_clock2cycle).
* onebus.c PowerJoy Supermax submapper detection used non-portable
*(uint32*)&info->MD5 cast that read different bytes on LE vs BE.
* fds.c clean up redundant FCEUSTATE_RLSB encoding in AddExState
calls that also passed type=1 (idempotent OR; readability fix).
DOCUMENTATION
=============
* input.c UpdateGP's *(uint32*)data cast looks like a typical
endian bug but is actually correct (the libretro frontend builds
JSReturn with matching host-uint32 shifts). Comment added to
prevent future "fixes" from breaking it.
Limitations not addressed
=========================
* Element-stride-aware byte swapping. The savestate byte-swap
mechanism (FlipByteOrder over the entire SFORMAT entry buffer)
is structurally wrong for arrays of multi-byte values: it
reverses the whole buffer end-to-end instead of byte-swapping
each element. Several places that need cross-platform-portable
arrays (VRC7 sound state, jyasic chr[8]) work around this by
either splitting arrays into per-element SFORMAT entries (n106
PlayIndex, bandai reg) or by skipping save entirely on BE via
#ifndef GEKKO. A proper fix would extend the size encoding with
an element-stride field. Left for a future change because it
would change the savestate format.
* Strict-aliasing UB in ppu.c (around 9 sites doing
*(uint32*)uint8_buf for fast 4-byte writes via FCEU_dwmemset).
Works in practice with all common compilers because the patterns
are byte-symmetric, but is formally UB.
* FCEU_gmalloc calls exit(1) on OOM. A libretro core should never
exit() because that takes down the whole frontend. Used by
100+ call sites; refactoring to return-NULL is out of scope here.
Testing
=======
* Build: clean on `make platform=unix` with -O2; no new warnings.
* FlipByteOrder fix verified by hand-trace and a standalone unit
test for counts 2, 4, 8.
* uppow2 fix verified by unit test across 13 boundary cases.
* SFORMAT size mismatches and missing-RLSB cases identified by
Python static-analysis scripts that cross-reference SFORMAT
entries against variable declarations.
* iNES 2.0 exponent fix verified by hand-tracing what byte 0xFF
produces post-fix: exp=30 (capped), mult=7, size=3 GiB nominal,
capped to 1 GiB, capped to 2 GiB by uppow2, FCEU_malloc returns
NULL on most systems, loader returns 0. No heap overflow for
any input byte.
* Savestate-loaded array index audit: built a Python scanner that
extracts each AddExState/SFORMAT entry and cross-references the
variable name against array-index uses in the same file. All
flagged sites covered.
* A libFuzzer harness and seed corpus generator (fuzz_main.c,
gen_seed_corpus.py) accompany this submission for ongoing
regression testing.
2026-05-04 02:15:40 +02:00
|
|
|
if (!tmp_buffer)
|
|
|
|
|
{
|
|
|
|
|
/* On allocation failure, drop this batch rather than dereferencing
|
|
|
|
|
* a NULL buffer in the memcpy below. The next batch will retry. */
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-06 13:36:22 +01:00
|
|
|
memcpy(tmp_buffer, stereo_filter_delay.samples,
|
|
|
|
|
stereo_filter_delay.samples_pos * sizeof(int32_t));
|
|
|
|
|
|
|
|
|
|
free(stereo_filter_delay.samples);
|
|
|
|
|
|
|
|
|
|
stereo_filter_delay.samples = tmp_buffer;
|
|
|
|
|
stereo_filter_delay.samples_size = tmp_buffer_size;
|
|
|
|
|
}
|
|
|
|
|
|
core: audio determinism fix and pipeline cleanup
Audit pass 6 - audio path. The libretro core only ever outputs 16-bit
stereo, so the question was whether anything in the audio pipeline is
either non-deterministic or pointlessly busy given that constraint.
DETERMINISM BUG FIX
src/filter.c's `sexyfilter_acc1` and `sexyfilter_acc2` are file-scope
int64_t IIR accumulators. They're saved/restored with the rest of the
sound state (sound.c "FAC1"/"FAC2" SFORMAT entries), but they were
NOT reset by FCEUSND_Power on cart load. So a second cart loaded in
the same process inherited the first cart's IIR state - audibly
minor (a few samples of transient before the IIR re-converges) but
non-deterministic across load orders.
`SexyFilter2`'s lowpass accumulator was even worse: a function-local
`static int64_t acc = 0` that no other code could touch. Its first-
load value was 0; from then on it just accumulated forever, with no
way to reset it. Same load-order-dependence problem, plus it wasn't
even savestate'd.
Fix:
- Lift SexyFilter2's local static to file scope as `sexyfilter2_acc`.
- Add `void SexyFilter_Reset(void)` that zeros all three accumulators.
- Call SexyFilter_Reset() from FCEUSND_Power.
- Add `sexyfilter2_acc` to the SFORMAT savestate list as "FAC3" so
runahead/replay/netplay restore it alongside FAC1/FAC2.
`mrindex` (filter.c file-scope, NeoFilterSound's input cursor) is
already reset in MakeFilters, which FCEUI_Sound calls on every cart
load, so it doesn't need explicit handling. `mrratio` is set in the
same path. Documented in the new SexyFilter_Reset comment.
PIPELINE CLEANUPS
(1) WaveHi memset bound. The HQ-mode flush was clearing the entire
WaveHi[40000] (160 KB) past `left` every frame, but channels only
write into [left, SOUNDTS) - everything past SOUNDTS is already
zero from the previous frame's clear (or FCEUSND_Power on the
first frame). Tightened the memset to (SOUNDTS - left) * 4 bytes
instead of sizeof(WaveHi) - left * 4. SOUNDTS is bounded by NES
cycles per frame (~30000 NTSC), so this saves ~40 KB of
pointless memset every HQ frame. The (SOUNDTS > left) guard
handles the degenerate case of a very short frame where SOUNDTS
might not have advanced past the coefficient history.
(2) RDoSQLQ silent-channel branch. When both square channels are
inactive, `amp[x]` is forced to 0 (line 634), which propagates
through `ttable[x]` to make `totalout = wlookup1[0] = 0`. The
previous code looped (end - start) iterations adding 0 to
Wave[V>>4] - genuinely no-op for ~30000 NES cycles per frame
when nothing is playing. Removed the loop body.
(3) LQ dispatch deduplication. `RDoSQLQ` mixes both squares in a
single call; `RDoTriangleNoisePCMLQ` mixes triangle/noise/PCM
in a single call. The previous code pointed DoSQ1 and DoSQ2
both at RDoSQLQ, and DoTriangle/DoNoise/DoPCM all at
RDoTriangleNoisePCMLQ. The first call did the work; the
subsequent calls entered the function only to be rejected by
the `if (end <= start) return;` guard at the top. Now point
DoSQ1/DoTriangle at the real workers and DoSQ2/DoNoise/DoPCM
at Dummyfunc - same behaviour, no redundant call+early-return
on every frame.
(4) stereo_filter_apply_delay copy loop -> memcpy. The element-by-
element copy `samples[i+pos] = sound_buffer[i]` is trivially
equivalent to memcpy for non-overlapping int32_t blocks; memcpy
lets the compiler/libc dispatch SIMD where available.
(5) Dead code in filter.c. Removed the `#ifdef moo` and `#if 0`
blocks (legacy commented-out lowpass formula attempts that
have been dead since the original blargg import) and the
`<math.h>` include, which is no longer needed once those
blocks are gone.
ARCHITECTURAL NOTES (not changed - documented for future passes)
- Per-frame sample count varies by +/-1 (e.g. 798/799 at 48000Hz
60Hz NTSC). This is required by libretro's variable-batch audio
model and is fundamental to the deterministic phase accumulator
(`soundtsoffs` carries the fractional remainder across frames).
Same input -> same count sequence. Replay/netplay-stable.
- The `stereo_filter_apply_null` post-pass exists to convert
in-place from int32 mono samples to int32 stereo-packed
((s<<16)|(s&0xFFFF)) format that, when the buffer is cast to
int16_t*, reads correctly as L,R,L,R pairs. Could be folded
into SexyFilter's clamp step to save one full-buffer pass
(~190 KB/s of redundant memory traffic) but requires sound.c
to know about the libretro frontend's stereo filter selection.
Too much frontend/core coupling to chase for a modest cache
win; left as-is.
- HQ path runs 5 in-place passes through WaveFinal[] per frame
(NeoFilterSound, SexyFilter, optional SexyFilter2,
stereo_filter_apply, audio_batch_cb read). LQ path runs 4.
Each pass is ~3.2 KB at 48000Hz - ~1 MB/s of buffer traffic
total. Modest; not worth restructuring.
- WaveHi[40000] is BSS-allocated (160 KB) even when in LQ mode
where it's never written. Cost is zero - BSS pages don't
materialise until first write - so making it conditional has
no runtime benefit, only adds complexity.
- stereo_filter_delay uses a linear buffer with memmove on each
frame to slide consumed samples back to position 0. A circular
buffer would eliminate the memmove but only matters when the
user opts into the delay filter (off by default).
Build clean under -std=gnu11 with -Wno-write-strings -Wsign-compare
-Wundef -Wmissing-prototypes; zero errors, zero warnings.
audit_determinism.py: no rand/time/long double/threads issues.
2026-05-04 05:08:11 +02:00
|
|
|
/* Copy current samples into the delay buffer's tail. The previous
|
|
|
|
|
* implementation walked the array element-by-element; memcpy is
|
|
|
|
|
* trivially equivalent for non-overlapping int32_t blocks and
|
|
|
|
|
* lets the compiler/libc dispatch SIMD where available. */
|
|
|
|
|
memcpy(stereo_filter_delay.samples + stereo_filter_delay.samples_pos,
|
|
|
|
|
sound_buffer, size * sizeof(int32_t));
|
2022-04-06 13:36:22 +01:00
|
|
|
|
|
|
|
|
stereo_filter_delay.samples_pos += size;
|
|
|
|
|
|
|
|
|
|
/* If we have enough samples in the delay
|
|
|
|
|
* buffer, mix them into the output */
|
|
|
|
|
if (stereo_filter_delay.samples_pos >
|
|
|
|
|
stereo_filter_delay.delay_count)
|
|
|
|
|
{
|
|
|
|
|
size_t delay_index = 0;
|
|
|
|
|
size_t samples_to_mix = stereo_filter_delay.samples_pos -
|
|
|
|
|
stereo_filter_delay.delay_count;
|
|
|
|
|
samples_to_mix = (samples_to_mix > size) ?
|
|
|
|
|
size : samples_to_mix;
|
|
|
|
|
|
|
|
|
|
/* Perform 'null' filtering for any samples for
|
|
|
|
|
* which a delay buffer entry is unavailable */
|
|
|
|
|
if (size > samples_to_mix)
|
|
|
|
|
for (i = 0; i < size - samples_to_mix; i++)
|
|
|
|
|
sound_buffer[i] = (sound_buffer[i] << 16) |
|
|
|
|
|
(sound_buffer[i] & 0xFFFF);
|
|
|
|
|
|
|
|
|
|
/* Each element of sound_buffer is a 16 bit mono sample
|
|
|
|
|
* stored in a 32 bit value. We convert this to stereo
|
|
|
|
|
* by copying the mono sample to the high (left channel)
|
|
|
|
|
* 16 bit region and the delayed sample to the low
|
|
|
|
|
* (right channel) region, casting sound_buffer
|
|
|
|
|
* to int16_t when uploading to the frontend */
|
|
|
|
|
for (i = size - samples_to_mix; i < size; i++)
|
|
|
|
|
sound_buffer[i] = (sound_buffer[i] << 16) |
|
|
|
|
|
(stereo_filter_delay.samples[delay_index++] & 0xFFFF);
|
|
|
|
|
|
|
|
|
|
/* Remove the mixed samples from the delay buffer */
|
|
|
|
|
memmove(stereo_filter_delay.samples,
|
|
|
|
|
stereo_filter_delay.samples + samples_to_mix,
|
|
|
|
|
(stereo_filter_delay.samples_pos - samples_to_mix) *
|
|
|
|
|
sizeof(int32_t));
|
|
|
|
|
stereo_filter_delay.samples_pos -= samples_to_mix;
|
|
|
|
|
}
|
|
|
|
|
/* Otherwise apply the regular 'null' filter */
|
|
|
|
|
else
|
|
|
|
|
for (i = 0; i < size; i++)
|
|
|
|
|
sound_buffer[i] = (sound_buffer[i] << 16) |
|
|
|
|
|
(sound_buffer[i] & 0xFFFF);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void (*stereo_filter_apply)(int32_t *sound_buffer, size_t size) = stereo_filter_apply_null;
|
|
|
|
|
|
|
|
|
|
static void stereo_filter_deinit_delay(void)
|
|
|
|
|
{
|
|
|
|
|
if (stereo_filter_delay.samples)
|
|
|
|
|
free(stereo_filter_delay.samples);
|
|
|
|
|
|
|
|
|
|
stereo_filter_delay.samples = NULL;
|
|
|
|
|
stereo_filter_delay.samples_size = 0;
|
|
|
|
|
stereo_filter_delay.samples_pos = 0;
|
|
|
|
|
stereo_filter_delay.delay_count = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void stereo_filter_init_delay(void)
|
|
|
|
|
{
|
|
|
|
|
size_t initial_samples_size;
|
|
|
|
|
|
|
|
|
|
/* Convert delay (ms) to number of samples */
|
|
|
|
|
stereo_filter_delay.delay_count = (size_t)(
|
|
|
|
|
(stereo_filter_delay_ms / 1000.0f) *
|
|
|
|
|
(float)sndsamplerate);
|
|
|
|
|
|
|
|
|
|
/* Preallocate delay_count + worst case expected
|
|
|
|
|
* samples per frame to minimise reallocation of
|
|
|
|
|
* the samples buffer during runtime */
|
|
|
|
|
initial_samples_size = stereo_filter_delay.delay_count +
|
|
|
|
|
(size_t)((float)sndsamplerate / NES_PAL_FPS) + 1;
|
|
|
|
|
|
|
|
|
|
stereo_filter_delay.samples = (int32_t *)malloc(
|
|
|
|
|
initial_samples_size * sizeof(int32_t));
|
core: memory-safety, leak, and savestate-portability audit fixes
Squashed series of ~50 distinct bugs found during a multi-day
security/correctness audit, ranging from ROM-triggerable heap
corruption and savestate-triggered OOB read primitives down to
obscure cross-platform savestate breakage.
Build is clean on `make platform=unix` with zero new warnings.
CRITICAL (ROM-triggerable, exploitable on the user's machine)
=============================================================
* ines.c iNES 2.0 PRG/CHR exponent overflow leading to undersized
allocation followed by heap buffer overflow on fread (pow() with
attacker-chosen exponent up to 63). Cap exponent at 30 and
compute size in uint32 with explicit cap below 2 GiB before
storing in int.
* ines.c miscROMSize int wraparound from attacker-controlled PRG/
CHR sizes; previous '& 0x8000000' check missed common underflow
cases. Compute in int64 and reject <= 0 or > 128 MiB.
* unif.c MAPR chunk OOB heap read on chunks of size < 4. Validate
chunk size before allocating board-name buffer.
* unif.c chunk size truncation: int conversion + missing cap allowed
a 4 GiB chunk size to wrap. Cap at 16 MiB.
* unif.c FixRomSize infinite loop on size > 0x80000000. Cap input.
* unif.c DINF chunk: months[(m - 1) % 12] is UB for m==0; m comes
from the .unf file. Clamp m into [1..12] before subtracting.
* unif.c NAME chunk: GameInfo->name = malloc(...) was unchecked.
* nsf.c size underflow: FCEU_fgetsize() - 0x80 wraps to ~UINT64_MAX
on tiny files, propagating a huge size into uppow2/FCEU_malloc.
Validate size > 0x80 before subtracting.
* nsf.c NSFMaxBank * 4096 cap tightened from 1<<20 (UB on signed-int
overflow) to 1<<19 (fits in int).
* general.c uppow2 had signed-int UB (1 << 32) and wrapped to 0 for
inputs near uint32 max. Cap at 0x80000000.
* cheat.c SubCheats[256] BSS overflow when more than 256 GG/PAR
substitution cheats are active. The array is adjacent to MMapPtrs[64]
in BSS which is exposed via retro_get_memory_data, making this
overflow visible from outside the core.
* libretro.c retro_cheat_set strcpy stack overflow with arbitrary-
length cheat strings from the frontend. Use strlcpy.
CRITICAL (savestate-triggerable on a malicious .fcs file)
=========================================================
* fds.c FDS savestate OOB read primitive: InDisk loaded from
savestate is used as index into 8-element diskdata[] static
pointer array. A value 8..254 dereferences arbitrary memory as
a pointer (heap-read primitive). Also bounds-checked SelectDisk,
mapperFDS_block, mapperFDS_blockstart, mapperFDS_diskaddr,
mapperFDS_blocklen so blockstart+diskaddr stays within the 65500-
byte disk buffer.
* 11 mappers had savestate-loaded variables masked at write time
but not at restore time, used as indices into fixed arrays:
- 88.c, KS7037.c, 112.c cmd indexes reg[8]
- sachen.c (S8259, S74LS374N) cmd indexes latch[8]
- 357.c dipswitch indexes outer_bank[4]
- unrom512.c flash_state indexes erase_a/d/b[5]
- 368.c preg indexes banks[8]
- 69.c sndcmd indexes sreg[14]
- mmc2and4.c latch0/latch1 index creg[4]
None individually exploitable into RCE - the array writes corrupt
adjacent BSS with constrained data flowing in - but each is an
out-of-bounds read or write from attacker-controllable input.
HIGH (memory-safety, reachable on any load)
===========================================
* fceu-memory.c FCEU_malloc deref-of-NULL on allocation failure
('ret = 0; memset(ret, 0, size);').
* file.c multiple FCEUFILE/MakeMemWrap/MakeMemWrapBuffer unchecked
allocations and unchecked filestream_tell return.
* libretro.c GameInfo NULL-derefs in three entry points
(retro_set_controller_port_device, retro_get_memory_data,
retro_get_memory_size) reachable on operations called before a
successful load.
* libretro.c framebuffer leak ~256 KB per failed FCEUI_LoadGame
(the libretro frontend doesn't call retro_unload_game on a
failed load).
* libretro.c 3DS retro_deinit unchecked linearFree.
* libretro.c stereo / NTSC filter unchecked malloc returns.
* fceu.c FCEUI_LoadGame unchecked GameInfo malloc.
* fds.c SubLoad and FDSLoad unchecked FCEU_malloc on diskdatao
backup buffers (NULL-deref in subsequent memcpy).
* fceu.c AllocGenieRW partial-failure leak: AReadG allocated but
BWriteG malloc fails -> 256 KB leak per retry.
* input/bworld.c Update(): unbounded strcpy from attacker-supplied
data into 20-byte bdata. Replaced with length-bounded copy.
* cart.c setprg2r/4r and setchr1r/2r/4r/8r mask-underflow guards.
SetupCartPRGMapping/SetupCartCHRMapping compute (size >> N) - 1
which underflows to 0xFFFFFFFF for chips smaller than the unit.
setprg8r/16r/32r and setchr8r already had defensive size checks;
the smaller units did not. malee.c (2 KB chip) and mapper 218
(2 KB NTARAM-as-CHR) accidentally avoid OOB; the primitive is
unsafe for any future board.
* video.c FCEU_InitVirtualVideo XBuf/XDBuf partial-failure leak.
* video.c, fceu.c FCEU_DispMessage / FCEU_printf / FCEU_PrintError:
vsprintf into fixed stack buffers replaced with vsnprintf.
* core: validate magic-string read length on FDS/UNIF/NSF load
(reject files too short to contain the magic so previous static
buffer / stack garbage doesn't spuriously match).
LEAKS (every cart load/unload cycle)
====================================
* mmc5.c MMC5 cart-side: WRAM (up to 64K) + MMC5fill (1K) + ExRAM
(1K) leaked per cycle. NSFMMC5_Close existed but was only used
for NSF code path.
* onebus.c Mapper 270/436: 8 KB CHRRAM leaked per cycle.
* 330.c, 375.c, 528.c: 8 KB WRAM each, no Close at all.
CORRECTNESS (UB / ABI / endianness)
===================================
* fceu-endian.c FlipByteOrder loop bound was 'count' instead of
'count/2', making it a no-op for every even count and silently
breaking savestate portability for every FCEUSTATE_RLSB-marked
field. The '#ifndef GEKKO' workarounds scattered through the
codebase (sound.c, vrc7.c, vrc6.c) were symptoms of this root
cause.
* state.c AddExState bounds check ran AFTER writing the entry, so
when SFEXINDEX hit the 63-entry cap the next call would write
the entry then immediately overwrite it with the terminator.
* state.c FCEUSS_Save_Mem: post-save callback was guarded by
'if (SPreSave)' instead of 'if (SPostSave)'.
* Sequence-point UB in counter expressions across 4 board files,
e.g. 'x = !x ? a : --x' and 'x = ++x % n'. GCC -Wsequence-point
flags all five sites (285.c, 413.c, asic_mmc3.c, jyasic.c).
* SFORMAT size mismatches between declared variable type and
AddExState size argument:
- asic_vrc3.c VRC3_count/VRC3_reload (uint16, saved as 1 byte)
- mmc3.c m555_count (uint32, saved as 2 bytes), m555_count_-
expired (uint8, saved as 2 bytes - OOB write into adjacent
BSS).
* unrom512.c UNROM-512 mapper 30 .srm save format: host-endian
uint32 flash write counters. Now stored as LE on disk regardless
of host (Battle Kid 2, Twin Dragons, Lizard, Sole, etc.).
* ~70 multi-byte single-variable SFORMAT/AddExState entries across
36 board files were saved as host-byte-order. After the
FlipByteOrder fix, these are now byte-swapped on BE so cross-
platform savestates round-trip correctly.
* coolgirl.c new ExStateLE() macro added; 22 multi-byte sites.
* pic16c5x.c 11 multi-byte AddExState calls (m_PC, m_PREVPC,
m_CONFIG, m_WDT, m_prescaler, m_opcode, m_STACK[0/1], m_icount,
m_delay_timer, m_rtcc, m_inst_cycles, m_clock2cycle).
* onebus.c PowerJoy Supermax submapper detection used non-portable
*(uint32*)&info->MD5 cast that read different bytes on LE vs BE.
* fds.c clean up redundant FCEUSTATE_RLSB encoding in AddExState
calls that also passed type=1 (idempotent OR; readability fix).
DOCUMENTATION
=============
* input.c UpdateGP's *(uint32*)data cast looks like a typical
endian bug but is actually correct (the libretro frontend builds
JSReturn with matching host-uint32 shifts). Comment added to
prevent future "fixes" from breaking it.
Limitations not addressed
=========================
* Element-stride-aware byte swapping. The savestate byte-swap
mechanism (FlipByteOrder over the entire SFORMAT entry buffer)
is structurally wrong for arrays of multi-byte values: it
reverses the whole buffer end-to-end instead of byte-swapping
each element. Several places that need cross-platform-portable
arrays (VRC7 sound state, jyasic chr[8]) work around this by
either splitting arrays into per-element SFORMAT entries (n106
PlayIndex, bandai reg) or by skipping save entirely on BE via
#ifndef GEKKO. A proper fix would extend the size encoding with
an element-stride field. Left for a future change because it
would change the savestate format.
* Strict-aliasing UB in ppu.c (around 9 sites doing
*(uint32*)uint8_buf for fast 4-byte writes via FCEU_dwmemset).
Works in practice with all common compilers because the patterns
are byte-symmetric, but is formally UB.
* FCEU_gmalloc calls exit(1) on OOM. A libretro core should never
exit() because that takes down the whole frontend. Used by
100+ call sites; refactoring to return-NULL is out of scope here.
Testing
=======
* Build: clean on `make platform=unix` with -O2; no new warnings.
* FlipByteOrder fix verified by hand-trace and a standalone unit
test for counts 2, 4, 8.
* uppow2 fix verified by unit test across 13 boundary cases.
* SFORMAT size mismatches and missing-RLSB cases identified by
Python static-analysis scripts that cross-reference SFORMAT
entries against variable declarations.
* iNES 2.0 exponent fix verified by hand-tracing what byte 0xFF
produces post-fix: exp=30 (capped), mult=7, size=3 GiB nominal,
capped to 1 GiB, capped to 2 GiB by uppow2, FCEU_malloc returns
NULL on most systems, loader returns 0. No heap overflow for
any input byte.
* Savestate-loaded array index audit: built a Python scanner that
extracts each AddExState/SFORMAT entry and cross-references the
variable name against array-index uses in the same file. All
flagged sites covered.
* A libFuzzer harness and seed corpus generator (fuzz_main.c,
gen_seed_corpus.py) accompany this submission for ongoing
regression testing.
2026-05-04 02:15:40 +02:00
|
|
|
if (!stereo_filter_delay.samples)
|
|
|
|
|
{
|
|
|
|
|
/* Fall back to the null filter on allocation failure rather than
|
|
|
|
|
* leaving a NULL samples buffer that the apply path would deref. */
|
|
|
|
|
stereo_filter_delay.samples_size = 0;
|
|
|
|
|
stereo_filter_delay.samples_pos = 0;
|
|
|
|
|
stereo_filter_apply = stereo_filter_apply_null;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-04-06 13:36:22 +01:00
|
|
|
stereo_filter_delay.samples_size = initial_samples_size;
|
|
|
|
|
stereo_filter_delay.samples_pos = 0;
|
|
|
|
|
|
|
|
|
|
/* Assign function pointer */
|
|
|
|
|
stereo_filter_apply = stereo_filter_apply_delay;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void stereo_filter_deinit(void)
|
|
|
|
|
{
|
|
|
|
|
/* Clean up */
|
|
|
|
|
stereo_filter_deinit_delay();
|
|
|
|
|
/* Assign default function pointer */
|
|
|
|
|
stereo_filter_apply = stereo_filter_apply_null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void stereo_filter_init(void)
|
|
|
|
|
{
|
|
|
|
|
stereo_filter_deinit();
|
|
|
|
|
|
|
|
|
|
/* Use a case statement to simplify matters
|
|
|
|
|
* if more filter types are added in the
|
|
|
|
|
* future... */
|
|
|
|
|
switch (current_stereo_filter)
|
|
|
|
|
{
|
|
|
|
|
case STEREO_FILTER_DELAY:
|
|
|
|
|
stereo_filter_init_delay();
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ========================================
|
|
|
|
|
* Stereo Filter END
|
|
|
|
|
* ======================================== */
|
|
|
|
|
|
2020-02-25 09:08:44 +08:00
|
|
|
#ifdef HAVE_NTSC_FILTER
|
|
|
|
|
/* ntsc */
|
|
|
|
|
#include "nes_ntsc.h"
|
|
|
|
|
#define NTSC_NONE 0
|
|
|
|
|
#define NTSC_COMPOSITE 1
|
|
|
|
|
#define NTSC_SVIDEO 2
|
|
|
|
|
#define NTSC_RGB 3
|
|
|
|
|
#define NTSC_MONOCHROME 4
|
|
|
|
|
|
2020-10-02 19:17:55 +08:00
|
|
|
#define NES_NTSC_WIDTH (((NES_NTSC_OUT_WIDTH(256) + 3) >> 2) << 2)
|
2020-02-25 09:08:44 +08:00
|
|
|
|
|
|
|
|
static unsigned use_ntsc = 0;
|
2021-10-19 17:33:56 +01:00
|
|
|
static unsigned burst_phase = 0;
|
2020-02-25 09:08:44 +08:00
|
|
|
static nes_ntsc_t nes_ntsc;
|
|
|
|
|
static nes_ntsc_setup_t ntsc_setup;
|
2025-09-01 22:07:53 +08:00
|
|
|
static bpp_t *ntsc_video_out = NULL; /* for ntsc blit buffer */
|
2020-02-25 09:08:44 +08:00
|
|
|
|
|
|
|
|
static void NTSCFilter_Cleanup(void)
|
|
|
|
|
{
|
|
|
|
|
if (ntsc_video_out)
|
|
|
|
|
free(ntsc_video_out);
|
|
|
|
|
ntsc_video_out = NULL;
|
2021-10-19 17:33:56 +01:00
|
|
|
|
|
|
|
|
use_ntsc = 0;
|
|
|
|
|
burst_phase = 0;
|
2020-02-25 09:08:44 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void NTSCFilter_Init(void)
|
|
|
|
|
{
|
|
|
|
|
memset(&nes_ntsc, 0, sizeof(nes_ntsc));
|
|
|
|
|
memset(&ntsc_setup, 0, sizeof(ntsc_setup));
|
2025-09-01 22:07:53 +08:00
|
|
|
ntsc_video_out = (bpp_t *)malloc(NES_NTSC_WIDTH * NES_HEIGHT * sizeof(bpp_t));
|
2020-02-25 09:08:44 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void NTSCFilter_Setup(void)
|
|
|
|
|
{
|
|
|
|
|
if (ntsc_video_out == NULL)
|
|
|
|
|
NTSCFilter_Init();
|
|
|
|
|
|
|
|
|
|
switch (use_ntsc) {
|
|
|
|
|
case NTSC_COMPOSITE:
|
|
|
|
|
ntsc_setup = nes_ntsc_composite;
|
|
|
|
|
break;
|
|
|
|
|
case NTSC_SVIDEO:
|
|
|
|
|
ntsc_setup = nes_ntsc_svideo;
|
|
|
|
|
break;
|
|
|
|
|
case NTSC_RGB:
|
|
|
|
|
ntsc_setup = nes_ntsc_rgb;
|
|
|
|
|
break;
|
|
|
|
|
case NTSC_MONOCHROME:
|
|
|
|
|
ntsc_setup = nes_ntsc_monochrome;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-25 11:51:41 +08:00
|
|
|
ntsc_setup.merge_fields = 0;
|
2024-10-15 22:35:46 +08:00
|
|
|
if (GameInfo && (GameInfo->type != GIT_VSUNI) && (current_palette == PAL_DEFAULT || current_palette == PAL_RAW))
|
2020-02-25 11:51:41 +08:00
|
|
|
/* use ntsc default palette instead of internal default palette for that "identity" effect */
|
2024-10-15 22:35:46 +08:00
|
|
|
ntsc_setup.palette = NULL;
|
2020-02-25 11:51:41 +08:00
|
|
|
else
|
|
|
|
|
/* use internal palette, this includes palette presets, external palette and custom palettes
|
2020-02-25 09:08:44 +08:00
|
|
|
* for VS. System games */
|
2024-10-15 22:35:46 +08:00
|
|
|
ntsc_setup.palette = (unsigned char const *)palo;
|
2020-02-25 09:08:44 +08:00
|
|
|
|
|
|
|
|
nes_ntsc_init(&nes_ntsc, &ntsc_setup);
|
|
|
|
|
}
|
|
|
|
|
#endif /* HAVE_NTSC_FILTER */
|
|
|
|
|
|
|
|
|
|
static void ResetPalette(void);
|
|
|
|
|
static void retro_set_custom_palette(void);
|
|
|
|
|
|
|
|
|
|
static void ResetPalette(void)
|
|
|
|
|
{
|
|
|
|
|
retro_set_custom_palette();
|
|
|
|
|
#ifdef HAVE_NTSC_FILTER
|
|
|
|
|
NTSCFilter_Setup();
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-30 22:35:00 +02:00
|
|
|
unsigned retro_api_version(void)
|
|
|
|
|
{
|
|
|
|
|
return RETRO_API_VERSION;
|
2014-03-30 22:15:17 +02:00
|
|
|
}
|
|
|
|
|
|
2014-03-30 22:35:00 +02:00
|
|
|
void retro_set_video_refresh(retro_video_refresh_t cb)
|
|
|
|
|
{
|
|
|
|
|
video_cb = cb;
|
2014-03-30 22:15:17 +02:00
|
|
|
}
|
|
|
|
|
|
2014-03-30 22:35:00 +02:00
|
|
|
void retro_set_audio_sample(retro_audio_sample_t cb)
|
|
|
|
|
{ }
|
2014-03-30 22:15:17 +02:00
|
|
|
|
2014-03-30 22:35:00 +02:00
|
|
|
void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb)
|
|
|
|
|
{
|
|
|
|
|
audio_batch_cb = cb;
|
2014-03-30 22:15:17 +02:00
|
|
|
}
|
|
|
|
|
|
2014-03-30 22:35:00 +02:00
|
|
|
void retro_set_input_poll(retro_input_poll_t cb)
|
|
|
|
|
{
|
|
|
|
|
poll_cb = cb;
|
2014-03-30 22:15:17 +02:00
|
|
|
}
|
|
|
|
|
|
2014-03-30 22:35:00 +02:00
|
|
|
void retro_set_input_state(retro_input_state_t cb)
|
|
|
|
|
{
|
|
|
|
|
input_cb = cb;
|
2014-03-30 22:15:17 +02:00
|
|
|
}
|
|
|
|
|
|
2018-11-23 01:59:20 +08:00
|
|
|
static void update_nes_controllers(unsigned port, unsigned device)
|
|
|
|
|
{
|
2020-05-05 13:35:30 +08:00
|
|
|
nes_input.type[port] = device;
|
2018-11-23 01:59:20 +08:00
|
|
|
|
|
|
|
|
if (port < 4)
|
|
|
|
|
{
|
|
|
|
|
switch (device)
|
|
|
|
|
{
|
|
|
|
|
case RETRO_DEVICE_NONE:
|
|
|
|
|
FCEUI_SetInput(port, SI_NONE, &Dummy, 0);
|
|
|
|
|
FCEU_printf(" Player %u: None Connected\n", port + 1);
|
|
|
|
|
break;
|
|
|
|
|
case RETRO_DEVICE_ZAPPER:
|
2020-05-05 13:35:30 +08:00
|
|
|
FCEUI_SetInput(port, SI_ZAPPER, nes_input.MouseData[port], 1);
|
2018-11-23 01:59:20 +08:00
|
|
|
FCEU_printf(" Player %u: Zapper\n", port + 1);
|
|
|
|
|
break;
|
|
|
|
|
case RETRO_DEVICE_ARKANOID:
|
2020-05-05 13:35:30 +08:00
|
|
|
FCEUI_SetInput(port, SI_ARKANOID, nes_input.MouseData[port], 0);
|
2018-11-23 01:59:20 +08:00
|
|
|
FCEU_printf(" Player %u: Arkanoid\n", port + 1);
|
|
|
|
|
break;
|
2022-12-31 21:27:46 -06:00
|
|
|
case RETRO_DEVICE_POWERPADA:
|
|
|
|
|
nes_input.type[port] = RETRO_DEVICE_POWERPADA;
|
|
|
|
|
FCEUI_SetInput(port, SI_POWERPADA, &nes_input.PowerPadData, 0);
|
2023-01-01 08:09:19 -06:00
|
|
|
FCEU_printf(" Player %u: Power Pad\n", port + 1);
|
2022-12-31 21:27:46 -06:00
|
|
|
break;
|
|
|
|
|
case RETRO_DEVICE_POWERPADB:
|
|
|
|
|
nes_input.type[port] = RETRO_DEVICE_POWERPADB;
|
|
|
|
|
FCEUI_SetInput(port, SI_POWERPADB, &nes_input.PowerPadData, 0);
|
2023-01-01 08:09:19 -06:00
|
|
|
FCEU_printf(" Player %u: Power Pad\n", port + 1);
|
2022-12-31 21:27:46 -06:00
|
|
|
break;
|
2018-11-23 01:59:20 +08:00
|
|
|
case RETRO_DEVICE_GAMEPAD:
|
|
|
|
|
default:
|
2020-05-05 13:35:30 +08:00
|
|
|
nes_input.type[port] = RETRO_DEVICE_GAMEPAD;
|
2020-06-04 13:26:02 +08:00
|
|
|
FCEUI_SetInput(port, SI_GAMEPAD, &nes_input.JSReturn, 0);
|
2018-11-23 01:59:20 +08:00
|
|
|
FCEU_printf(" Player %u: Gamepad\n", port + 1);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (port == 4)
|
|
|
|
|
{
|
|
|
|
|
switch (device)
|
|
|
|
|
{
|
|
|
|
|
case RETRO_DEVICE_FC_ARKANOID:
|
2020-10-05 10:38:44 +08:00
|
|
|
FCEUI_SetInputFC(SIFC_ARKANOID, nes_input.FamicomData, 0);
|
2018-11-23 01:59:20 +08:00
|
|
|
FCEU_printf(" Famicom Expansion: Arkanoid\n");
|
|
|
|
|
break;
|
|
|
|
|
case RETRO_DEVICE_FC_SHADOW:
|
2020-10-05 10:38:44 +08:00
|
|
|
FCEUI_SetInputFC(SIFC_SHADOW, nes_input.FamicomData, 1);
|
2018-11-23 01:59:20 +08:00
|
|
|
FCEU_printf(" Famicom Expansion: (Bandai) Hyper Shot\n");
|
|
|
|
|
break;
|
|
|
|
|
case RETRO_DEVICE_FC_OEKAKIDS:
|
2020-10-05 10:38:44 +08:00
|
|
|
FCEUI_SetInputFC(SIFC_OEKAKIDS, nes_input.FamicomData, 1);
|
2018-11-23 01:59:20 +08:00
|
|
|
FCEU_printf(" Famicom Expansion: Oeka Kids Tablet\n");
|
|
|
|
|
break;
|
|
|
|
|
case RETRO_DEVICE_FC_4PLAYERS:
|
2020-06-04 13:26:02 +08:00
|
|
|
FCEUI_SetInputFC(SIFC_4PLAYER, &nes_input.JSReturn, 0);
|
2018-11-23 01:59:20 +08:00
|
|
|
FCEU_printf(" Famicom Expansion: Famicom 4-Player Adapter\n");
|
|
|
|
|
break;
|
2021-04-10 15:20:50 +08:00
|
|
|
case RETRO_DEVICE_FC_HYPERSHOT:
|
|
|
|
|
FCEUI_SetInputFC(SIFC_HYPERSHOT, nes_input.FamicomData, 0);
|
|
|
|
|
FCEU_printf(" Famicom Expansion: Konami Hyper Shot\n");
|
|
|
|
|
break;
|
2023-09-15 19:48:52 -04:00
|
|
|
case RETRO_DEVICE_FC_FTRAINERA:
|
|
|
|
|
FCEUI_SetInputFC(SIFC_FTRAINERA, &nes_input.PowerPadData, 0);
|
|
|
|
|
FCEU_printf(" Famicom Expansion: Family Trainer A\n");
|
|
|
|
|
break;
|
|
|
|
|
case RETRO_DEVICE_FC_FTRAINERB:
|
|
|
|
|
FCEUI_SetInputFC(SIFC_FTRAINERB, &nes_input.PowerPadData, 0);
|
|
|
|
|
FCEU_printf(" Famicom Expansion: Family Trainer B\n");
|
|
|
|
|
break;
|
2025-09-13 11:27:38 +02:00
|
|
|
case RETRO_DEVICE_FC_FKB:
|
|
|
|
|
FCEUI_SetInputFC(SIFC_FKB, &nes_input.FamilyKeyboardData, 0);
|
|
|
|
|
FCEU_printf(" Famicom Expansion: Family BASIC Keyboard\n");
|
|
|
|
|
break;
|
|
|
|
|
case RETRO_DEVICE_FC_SUBORKB:
|
|
|
|
|
FCEUI_SetInputFC(SIFC_SUBORKB, &nes_input.SuborKeyboardData, 0);
|
|
|
|
|
FCEU_printf(" Famicom Expansion: Subor Keyboard\n");
|
|
|
|
|
break;
|
|
|
|
|
case RETRO_DEVICE_FC_PEC586KB:
|
|
|
|
|
FCEUI_SetInputFC(SIFC_PEC586KB, &nes_input.SuborKeyboardData, 0);
|
|
|
|
|
FCEU_printf(" Famicom Expansion: Dongda Keyboard\n");
|
|
|
|
|
break;
|
2018-11-23 01:59:20 +08:00
|
|
|
case RETRO_DEVICE_NONE:
|
|
|
|
|
default:
|
|
|
|
|
FCEUI_SetInputFC(SIFC_NONE, &Dummy, 0);
|
|
|
|
|
FCEU_printf(" Famicom Expansion: None Connected\n");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static unsigned nes_to_libretro(int d)
|
|
|
|
|
{
|
|
|
|
|
switch(d)
|
|
|
|
|
{
|
|
|
|
|
case SI_UNSET:
|
|
|
|
|
case SI_GAMEPAD:
|
|
|
|
|
return RETRO_DEVICE_GAMEPAD;
|
|
|
|
|
case SI_NONE:
|
|
|
|
|
return RETRO_DEVICE_NONE;
|
|
|
|
|
case SI_ZAPPER:
|
|
|
|
|
return RETRO_DEVICE_ZAPPER;
|
|
|
|
|
case SI_ARKANOID:
|
|
|
|
|
return RETRO_DEVICE_ARKANOID;
|
2022-12-31 21:27:46 -06:00
|
|
|
case SI_POWERPADB:
|
|
|
|
|
return RETRO_DEVICE_POWERPADB;
|
2018-11-23 01:59:20 +08:00
|
|
|
}
|
2018-12-23 22:46:17 +08:00
|
|
|
|
|
|
|
|
return (RETRO_DEVICE_GAMEPAD);
|
2018-11-23 01:59:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static unsigned fc_to_libretro(int d)
|
|
|
|
|
{
|
|
|
|
|
switch(d)
|
|
|
|
|
{
|
|
|
|
|
case SIFC_UNSET:
|
|
|
|
|
case SIFC_NONE:
|
|
|
|
|
return RETRO_DEVICE_NONE;
|
|
|
|
|
case SIFC_ARKANOID:
|
|
|
|
|
return RETRO_DEVICE_FC_ARKANOID;
|
|
|
|
|
case SIFC_SHADOW:
|
|
|
|
|
return RETRO_DEVICE_FC_SHADOW;
|
|
|
|
|
case SIFC_OEKAKIDS:
|
|
|
|
|
return RETRO_DEVICE_FC_OEKAKIDS;
|
|
|
|
|
case SIFC_4PLAYER:
|
|
|
|
|
return RETRO_DEVICE_FC_4PLAYERS;
|
2021-04-10 15:20:50 +08:00
|
|
|
case SIFC_HYPERSHOT:
|
|
|
|
|
return RETRO_DEVICE_FC_HYPERSHOT;
|
2023-09-15 19:48:52 -04:00
|
|
|
case SIFC_FTRAINERA:
|
|
|
|
|
return RETRO_DEVICE_FC_FTRAINERA;
|
|
|
|
|
case SIFC_FTRAINERB:
|
|
|
|
|
return RETRO_DEVICE_FC_FTRAINERB;
|
2025-09-13 11:27:38 +02:00
|
|
|
case SIFC_FKB:
|
|
|
|
|
return RETRO_DEVICE_FC_FKB;
|
|
|
|
|
case SIFC_SUBORKB:
|
|
|
|
|
return RETRO_DEVICE_FC_SUBORKB;
|
|
|
|
|
case SIFC_PEC586KB:
|
|
|
|
|
return RETRO_DEVICE_FC_PEC586KB;
|
2018-11-23 01:59:20 +08:00
|
|
|
}
|
2018-12-23 22:46:17 +08:00
|
|
|
|
|
|
|
|
return (RETRO_DEVICE_NONE);
|
2018-11-23 01:59:20 +08:00
|
|
|
}
|
2017-11-25 11:16:26 +08:00
|
|
|
|
2017-09-09 16:16:12 +08:00
|
|
|
void retro_set_controller_port_device(unsigned port, unsigned device)
|
|
|
|
|
{
|
2019-05-18 22:39:23 +08:00
|
|
|
if (port < 5)
|
2017-09-09 16:16:12 +08:00
|
|
|
{
|
2018-11-23 01:59:20 +08:00
|
|
|
if (port < 2) /* player 1-2 */
|
2017-09-16 16:24:59 +08:00
|
|
|
{
|
2018-11-23 02:18:16 +08:00
|
|
|
if (device != RETRO_DEVICE_AUTO)
|
2018-11-23 01:59:20 +08:00
|
|
|
update_nes_controllers(port, device);
|
core: memory-safety, leak, and savestate-portability audit fixes
Squashed series of ~50 distinct bugs found during a multi-day
security/correctness audit, ranging from ROM-triggerable heap
corruption and savestate-triggered OOB read primitives down to
obscure cross-platform savestate breakage.
Build is clean on `make platform=unix` with zero new warnings.
CRITICAL (ROM-triggerable, exploitable on the user's machine)
=============================================================
* ines.c iNES 2.0 PRG/CHR exponent overflow leading to undersized
allocation followed by heap buffer overflow on fread (pow() with
attacker-chosen exponent up to 63). Cap exponent at 30 and
compute size in uint32 with explicit cap below 2 GiB before
storing in int.
* ines.c miscROMSize int wraparound from attacker-controlled PRG/
CHR sizes; previous '& 0x8000000' check missed common underflow
cases. Compute in int64 and reject <= 0 or > 128 MiB.
* unif.c MAPR chunk OOB heap read on chunks of size < 4. Validate
chunk size before allocating board-name buffer.
* unif.c chunk size truncation: int conversion + missing cap allowed
a 4 GiB chunk size to wrap. Cap at 16 MiB.
* unif.c FixRomSize infinite loop on size > 0x80000000. Cap input.
* unif.c DINF chunk: months[(m - 1) % 12] is UB for m==0; m comes
from the .unf file. Clamp m into [1..12] before subtracting.
* unif.c NAME chunk: GameInfo->name = malloc(...) was unchecked.
* nsf.c size underflow: FCEU_fgetsize() - 0x80 wraps to ~UINT64_MAX
on tiny files, propagating a huge size into uppow2/FCEU_malloc.
Validate size > 0x80 before subtracting.
* nsf.c NSFMaxBank * 4096 cap tightened from 1<<20 (UB on signed-int
overflow) to 1<<19 (fits in int).
* general.c uppow2 had signed-int UB (1 << 32) and wrapped to 0 for
inputs near uint32 max. Cap at 0x80000000.
* cheat.c SubCheats[256] BSS overflow when more than 256 GG/PAR
substitution cheats are active. The array is adjacent to MMapPtrs[64]
in BSS which is exposed via retro_get_memory_data, making this
overflow visible from outside the core.
* libretro.c retro_cheat_set strcpy stack overflow with arbitrary-
length cheat strings from the frontend. Use strlcpy.
CRITICAL (savestate-triggerable on a malicious .fcs file)
=========================================================
* fds.c FDS savestate OOB read primitive: InDisk loaded from
savestate is used as index into 8-element diskdata[] static
pointer array. A value 8..254 dereferences arbitrary memory as
a pointer (heap-read primitive). Also bounds-checked SelectDisk,
mapperFDS_block, mapperFDS_blockstart, mapperFDS_diskaddr,
mapperFDS_blocklen so blockstart+diskaddr stays within the 65500-
byte disk buffer.
* 11 mappers had savestate-loaded variables masked at write time
but not at restore time, used as indices into fixed arrays:
- 88.c, KS7037.c, 112.c cmd indexes reg[8]
- sachen.c (S8259, S74LS374N) cmd indexes latch[8]
- 357.c dipswitch indexes outer_bank[4]
- unrom512.c flash_state indexes erase_a/d/b[5]
- 368.c preg indexes banks[8]
- 69.c sndcmd indexes sreg[14]
- mmc2and4.c latch0/latch1 index creg[4]
None individually exploitable into RCE - the array writes corrupt
adjacent BSS with constrained data flowing in - but each is an
out-of-bounds read or write from attacker-controllable input.
HIGH (memory-safety, reachable on any load)
===========================================
* fceu-memory.c FCEU_malloc deref-of-NULL on allocation failure
('ret = 0; memset(ret, 0, size);').
* file.c multiple FCEUFILE/MakeMemWrap/MakeMemWrapBuffer unchecked
allocations and unchecked filestream_tell return.
* libretro.c GameInfo NULL-derefs in three entry points
(retro_set_controller_port_device, retro_get_memory_data,
retro_get_memory_size) reachable on operations called before a
successful load.
* libretro.c framebuffer leak ~256 KB per failed FCEUI_LoadGame
(the libretro frontend doesn't call retro_unload_game on a
failed load).
* libretro.c 3DS retro_deinit unchecked linearFree.
* libretro.c stereo / NTSC filter unchecked malloc returns.
* fceu.c FCEUI_LoadGame unchecked GameInfo malloc.
* fds.c SubLoad and FDSLoad unchecked FCEU_malloc on diskdatao
backup buffers (NULL-deref in subsequent memcpy).
* fceu.c AllocGenieRW partial-failure leak: AReadG allocated but
BWriteG malloc fails -> 256 KB leak per retry.
* input/bworld.c Update(): unbounded strcpy from attacker-supplied
data into 20-byte bdata. Replaced with length-bounded copy.
* cart.c setprg2r/4r and setchr1r/2r/4r/8r mask-underflow guards.
SetupCartPRGMapping/SetupCartCHRMapping compute (size >> N) - 1
which underflows to 0xFFFFFFFF for chips smaller than the unit.
setprg8r/16r/32r and setchr8r already had defensive size checks;
the smaller units did not. malee.c (2 KB chip) and mapper 218
(2 KB NTARAM-as-CHR) accidentally avoid OOB; the primitive is
unsafe for any future board.
* video.c FCEU_InitVirtualVideo XBuf/XDBuf partial-failure leak.
* video.c, fceu.c FCEU_DispMessage / FCEU_printf / FCEU_PrintError:
vsprintf into fixed stack buffers replaced with vsnprintf.
* core: validate magic-string read length on FDS/UNIF/NSF load
(reject files too short to contain the magic so previous static
buffer / stack garbage doesn't spuriously match).
LEAKS (every cart load/unload cycle)
====================================
* mmc5.c MMC5 cart-side: WRAM (up to 64K) + MMC5fill (1K) + ExRAM
(1K) leaked per cycle. NSFMMC5_Close existed but was only used
for NSF code path.
* onebus.c Mapper 270/436: 8 KB CHRRAM leaked per cycle.
* 330.c, 375.c, 528.c: 8 KB WRAM each, no Close at all.
CORRECTNESS (UB / ABI / endianness)
===================================
* fceu-endian.c FlipByteOrder loop bound was 'count' instead of
'count/2', making it a no-op for every even count and silently
breaking savestate portability for every FCEUSTATE_RLSB-marked
field. The '#ifndef GEKKO' workarounds scattered through the
codebase (sound.c, vrc7.c, vrc6.c) were symptoms of this root
cause.
* state.c AddExState bounds check ran AFTER writing the entry, so
when SFEXINDEX hit the 63-entry cap the next call would write
the entry then immediately overwrite it with the terminator.
* state.c FCEUSS_Save_Mem: post-save callback was guarded by
'if (SPreSave)' instead of 'if (SPostSave)'.
* Sequence-point UB in counter expressions across 4 board files,
e.g. 'x = !x ? a : --x' and 'x = ++x % n'. GCC -Wsequence-point
flags all five sites (285.c, 413.c, asic_mmc3.c, jyasic.c).
* SFORMAT size mismatches between declared variable type and
AddExState size argument:
- asic_vrc3.c VRC3_count/VRC3_reload (uint16, saved as 1 byte)
- mmc3.c m555_count (uint32, saved as 2 bytes), m555_count_-
expired (uint8, saved as 2 bytes - OOB write into adjacent
BSS).
* unrom512.c UNROM-512 mapper 30 .srm save format: host-endian
uint32 flash write counters. Now stored as LE on disk regardless
of host (Battle Kid 2, Twin Dragons, Lizard, Sole, etc.).
* ~70 multi-byte single-variable SFORMAT/AddExState entries across
36 board files were saved as host-byte-order. After the
FlipByteOrder fix, these are now byte-swapped on BE so cross-
platform savestates round-trip correctly.
* coolgirl.c new ExStateLE() macro added; 22 multi-byte sites.
* pic16c5x.c 11 multi-byte AddExState calls (m_PC, m_PREVPC,
m_CONFIG, m_WDT, m_prescaler, m_opcode, m_STACK[0/1], m_icount,
m_delay_timer, m_rtcc, m_inst_cycles, m_clock2cycle).
* onebus.c PowerJoy Supermax submapper detection used non-portable
*(uint32*)&info->MD5 cast that read different bytes on LE vs BE.
* fds.c clean up redundant FCEUSTATE_RLSB encoding in AddExState
calls that also passed type=1 (idempotent OR; readability fix).
DOCUMENTATION
=============
* input.c UpdateGP's *(uint32*)data cast looks like a typical
endian bug but is actually correct (the libretro frontend builds
JSReturn with matching host-uint32 shifts). Comment added to
prevent future "fixes" from breaking it.
Limitations not addressed
=========================
* Element-stride-aware byte swapping. The savestate byte-swap
mechanism (FlipByteOrder over the entire SFORMAT entry buffer)
is structurally wrong for arrays of multi-byte values: it
reverses the whole buffer end-to-end instead of byte-swapping
each element. Several places that need cross-platform-portable
arrays (VRC7 sound state, jyasic chr[8]) work around this by
either splitting arrays into per-element SFORMAT entries (n106
PlayIndex, bandai reg) or by skipping save entirely on BE via
#ifndef GEKKO. A proper fix would extend the size encoding with
an element-stride field. Left for a future change because it
would change the savestate format.
* Strict-aliasing UB in ppu.c (around 9 sites doing
*(uint32*)uint8_buf for fast 4-byte writes via FCEU_dwmemset).
Works in practice with all common compilers because the patterns
are byte-symmetric, but is formally UB.
* FCEU_gmalloc calls exit(1) on OOM. A libretro core should never
exit() because that takes down the whole frontend. Used by
100+ call sites; refactoring to return-NULL is out of scope here.
Testing
=======
* Build: clean on `make platform=unix` with -O2; no new warnings.
* FlipByteOrder fix verified by hand-trace and a standalone unit
test for counts 2, 4, 8.
* uppow2 fix verified by unit test across 13 boundary cases.
* SFORMAT size mismatches and missing-RLSB cases identified by
Python static-analysis scripts that cross-reference SFORMAT
entries against variable declarations.
* iNES 2.0 exponent fix verified by hand-tracing what byte 0xFF
produces post-fix: exp=30 (capped), mult=7, size=3 GiB nominal,
capped to 1 GiB, capped to 2 GiB by uppow2, FCEU_malloc returns
NULL on most systems, loader returns 0. No heap overflow for
any input byte.
* Savestate-loaded array index audit: built a Python scanner that
extracts each AddExState/SFORMAT entry and cross-references the
variable name against array-index uses in the same file. All
flagged sites covered.
* A libFuzzer harness and seed corpus generator (fuzz_main.c,
gen_seed_corpus.py) accompany this submission for ongoing
regression testing.
2026-05-04 02:15:40 +02:00
|
|
|
else if (GameInfo) /* GameInfo may be NULL pre-load */
|
2018-11-23 01:59:20 +08:00
|
|
|
update_nes_controllers(port, nes_to_libretro(GameInfo->input[port]));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (port < 4) /* player 3-4 */
|
|
|
|
|
{
|
|
|
|
|
/* This section automatically enables 4players support
|
|
|
|
|
* when player 3 or 4 used */
|
|
|
|
|
|
2020-05-05 13:35:30 +08:00
|
|
|
nes_input.type[port] = RETRO_DEVICE_NONE;
|
2018-11-23 01:59:20 +08:00
|
|
|
|
|
|
|
|
if (device == RETRO_DEVICE_AUTO)
|
|
|
|
|
{
|
2020-05-05 13:35:30 +08:00
|
|
|
if (nes_input.enable_4player)
|
|
|
|
|
nes_input.type[port] = RETRO_DEVICE_GAMEPAD;
|
2018-11-23 01:59:20 +08:00
|
|
|
}
|
|
|
|
|
else if (device == RETRO_DEVICE_GAMEPAD)
|
2020-05-05 13:35:30 +08:00
|
|
|
nes_input.type[port] = RETRO_DEVICE_GAMEPAD;
|
2018-11-23 01:59:20 +08:00
|
|
|
|
|
|
|
|
FCEU_printf(" Player %u: %s\n", port + 1,
|
2020-05-05 13:35:30 +08:00
|
|
|
(nes_input.type[port] == RETRO_DEVICE_NONE) ? "None Connected" : "Gamepad");
|
2018-11-23 01:59:20 +08:00
|
|
|
}
|
|
|
|
|
else /* do famicom controllers here */
|
|
|
|
|
{
|
|
|
|
|
if (device != RETRO_DEVICE_FC_AUTO)
|
|
|
|
|
update_nes_controllers(4, device);
|
core: memory-safety, leak, and savestate-portability audit fixes
Squashed series of ~50 distinct bugs found during a multi-day
security/correctness audit, ranging from ROM-triggerable heap
corruption and savestate-triggered OOB read primitives down to
obscure cross-platform savestate breakage.
Build is clean on `make platform=unix` with zero new warnings.
CRITICAL (ROM-triggerable, exploitable on the user's machine)
=============================================================
* ines.c iNES 2.0 PRG/CHR exponent overflow leading to undersized
allocation followed by heap buffer overflow on fread (pow() with
attacker-chosen exponent up to 63). Cap exponent at 30 and
compute size in uint32 with explicit cap below 2 GiB before
storing in int.
* ines.c miscROMSize int wraparound from attacker-controlled PRG/
CHR sizes; previous '& 0x8000000' check missed common underflow
cases. Compute in int64 and reject <= 0 or > 128 MiB.
* unif.c MAPR chunk OOB heap read on chunks of size < 4. Validate
chunk size before allocating board-name buffer.
* unif.c chunk size truncation: int conversion + missing cap allowed
a 4 GiB chunk size to wrap. Cap at 16 MiB.
* unif.c FixRomSize infinite loop on size > 0x80000000. Cap input.
* unif.c DINF chunk: months[(m - 1) % 12] is UB for m==0; m comes
from the .unf file. Clamp m into [1..12] before subtracting.
* unif.c NAME chunk: GameInfo->name = malloc(...) was unchecked.
* nsf.c size underflow: FCEU_fgetsize() - 0x80 wraps to ~UINT64_MAX
on tiny files, propagating a huge size into uppow2/FCEU_malloc.
Validate size > 0x80 before subtracting.
* nsf.c NSFMaxBank * 4096 cap tightened from 1<<20 (UB on signed-int
overflow) to 1<<19 (fits in int).
* general.c uppow2 had signed-int UB (1 << 32) and wrapped to 0 for
inputs near uint32 max. Cap at 0x80000000.
* cheat.c SubCheats[256] BSS overflow when more than 256 GG/PAR
substitution cheats are active. The array is adjacent to MMapPtrs[64]
in BSS which is exposed via retro_get_memory_data, making this
overflow visible from outside the core.
* libretro.c retro_cheat_set strcpy stack overflow with arbitrary-
length cheat strings from the frontend. Use strlcpy.
CRITICAL (savestate-triggerable on a malicious .fcs file)
=========================================================
* fds.c FDS savestate OOB read primitive: InDisk loaded from
savestate is used as index into 8-element diskdata[] static
pointer array. A value 8..254 dereferences arbitrary memory as
a pointer (heap-read primitive). Also bounds-checked SelectDisk,
mapperFDS_block, mapperFDS_blockstart, mapperFDS_diskaddr,
mapperFDS_blocklen so blockstart+diskaddr stays within the 65500-
byte disk buffer.
* 11 mappers had savestate-loaded variables masked at write time
but not at restore time, used as indices into fixed arrays:
- 88.c, KS7037.c, 112.c cmd indexes reg[8]
- sachen.c (S8259, S74LS374N) cmd indexes latch[8]
- 357.c dipswitch indexes outer_bank[4]
- unrom512.c flash_state indexes erase_a/d/b[5]
- 368.c preg indexes banks[8]
- 69.c sndcmd indexes sreg[14]
- mmc2and4.c latch0/latch1 index creg[4]
None individually exploitable into RCE - the array writes corrupt
adjacent BSS with constrained data flowing in - but each is an
out-of-bounds read or write from attacker-controllable input.
HIGH (memory-safety, reachable on any load)
===========================================
* fceu-memory.c FCEU_malloc deref-of-NULL on allocation failure
('ret = 0; memset(ret, 0, size);').
* file.c multiple FCEUFILE/MakeMemWrap/MakeMemWrapBuffer unchecked
allocations and unchecked filestream_tell return.
* libretro.c GameInfo NULL-derefs in three entry points
(retro_set_controller_port_device, retro_get_memory_data,
retro_get_memory_size) reachable on operations called before a
successful load.
* libretro.c framebuffer leak ~256 KB per failed FCEUI_LoadGame
(the libretro frontend doesn't call retro_unload_game on a
failed load).
* libretro.c 3DS retro_deinit unchecked linearFree.
* libretro.c stereo / NTSC filter unchecked malloc returns.
* fceu.c FCEUI_LoadGame unchecked GameInfo malloc.
* fds.c SubLoad and FDSLoad unchecked FCEU_malloc on diskdatao
backup buffers (NULL-deref in subsequent memcpy).
* fceu.c AllocGenieRW partial-failure leak: AReadG allocated but
BWriteG malloc fails -> 256 KB leak per retry.
* input/bworld.c Update(): unbounded strcpy from attacker-supplied
data into 20-byte bdata. Replaced with length-bounded copy.
* cart.c setprg2r/4r and setchr1r/2r/4r/8r mask-underflow guards.
SetupCartPRGMapping/SetupCartCHRMapping compute (size >> N) - 1
which underflows to 0xFFFFFFFF for chips smaller than the unit.
setprg8r/16r/32r and setchr8r already had defensive size checks;
the smaller units did not. malee.c (2 KB chip) and mapper 218
(2 KB NTARAM-as-CHR) accidentally avoid OOB; the primitive is
unsafe for any future board.
* video.c FCEU_InitVirtualVideo XBuf/XDBuf partial-failure leak.
* video.c, fceu.c FCEU_DispMessage / FCEU_printf / FCEU_PrintError:
vsprintf into fixed stack buffers replaced with vsnprintf.
* core: validate magic-string read length on FDS/UNIF/NSF load
(reject files too short to contain the magic so previous static
buffer / stack garbage doesn't spuriously match).
LEAKS (every cart load/unload cycle)
====================================
* mmc5.c MMC5 cart-side: WRAM (up to 64K) + MMC5fill (1K) + ExRAM
(1K) leaked per cycle. NSFMMC5_Close existed but was only used
for NSF code path.
* onebus.c Mapper 270/436: 8 KB CHRRAM leaked per cycle.
* 330.c, 375.c, 528.c: 8 KB WRAM each, no Close at all.
CORRECTNESS (UB / ABI / endianness)
===================================
* fceu-endian.c FlipByteOrder loop bound was 'count' instead of
'count/2', making it a no-op for every even count and silently
breaking savestate portability for every FCEUSTATE_RLSB-marked
field. The '#ifndef GEKKO' workarounds scattered through the
codebase (sound.c, vrc7.c, vrc6.c) were symptoms of this root
cause.
* state.c AddExState bounds check ran AFTER writing the entry, so
when SFEXINDEX hit the 63-entry cap the next call would write
the entry then immediately overwrite it with the terminator.
* state.c FCEUSS_Save_Mem: post-save callback was guarded by
'if (SPreSave)' instead of 'if (SPostSave)'.
* Sequence-point UB in counter expressions across 4 board files,
e.g. 'x = !x ? a : --x' and 'x = ++x % n'. GCC -Wsequence-point
flags all five sites (285.c, 413.c, asic_mmc3.c, jyasic.c).
* SFORMAT size mismatches between declared variable type and
AddExState size argument:
- asic_vrc3.c VRC3_count/VRC3_reload (uint16, saved as 1 byte)
- mmc3.c m555_count (uint32, saved as 2 bytes), m555_count_-
expired (uint8, saved as 2 bytes - OOB write into adjacent
BSS).
* unrom512.c UNROM-512 mapper 30 .srm save format: host-endian
uint32 flash write counters. Now stored as LE on disk regardless
of host (Battle Kid 2, Twin Dragons, Lizard, Sole, etc.).
* ~70 multi-byte single-variable SFORMAT/AddExState entries across
36 board files were saved as host-byte-order. After the
FlipByteOrder fix, these are now byte-swapped on BE so cross-
platform savestates round-trip correctly.
* coolgirl.c new ExStateLE() macro added; 22 multi-byte sites.
* pic16c5x.c 11 multi-byte AddExState calls (m_PC, m_PREVPC,
m_CONFIG, m_WDT, m_prescaler, m_opcode, m_STACK[0/1], m_icount,
m_delay_timer, m_rtcc, m_inst_cycles, m_clock2cycle).
* onebus.c PowerJoy Supermax submapper detection used non-portable
*(uint32*)&info->MD5 cast that read different bytes on LE vs BE.
* fds.c clean up redundant FCEUSTATE_RLSB encoding in AddExState
calls that also passed type=1 (idempotent OR; readability fix).
DOCUMENTATION
=============
* input.c UpdateGP's *(uint32*)data cast looks like a typical
endian bug but is actually correct (the libretro frontend builds
JSReturn with matching host-uint32 shifts). Comment added to
prevent future "fixes" from breaking it.
Limitations not addressed
=========================
* Element-stride-aware byte swapping. The savestate byte-swap
mechanism (FlipByteOrder over the entire SFORMAT entry buffer)
is structurally wrong for arrays of multi-byte values: it
reverses the whole buffer end-to-end instead of byte-swapping
each element. Several places that need cross-platform-portable
arrays (VRC7 sound state, jyasic chr[8]) work around this by
either splitting arrays into per-element SFORMAT entries (n106
PlayIndex, bandai reg) or by skipping save entirely on BE via
#ifndef GEKKO. A proper fix would extend the size encoding with
an element-stride field. Left for a future change because it
would change the savestate format.
* Strict-aliasing UB in ppu.c (around 9 sites doing
*(uint32*)uint8_buf for fast 4-byte writes via FCEU_dwmemset).
Works in practice with all common compilers because the patterns
are byte-symmetric, but is formally UB.
* FCEU_gmalloc calls exit(1) on OOM. A libretro core should never
exit() because that takes down the whole frontend. Used by
100+ call sites; refactoring to return-NULL is out of scope here.
Testing
=======
* Build: clean on `make platform=unix` with -O2; no new warnings.
* FlipByteOrder fix verified by hand-trace and a standalone unit
test for counts 2, 4, 8.
* uppow2 fix verified by unit test across 13 boundary cases.
* SFORMAT size mismatches and missing-RLSB cases identified by
Python static-analysis scripts that cross-reference SFORMAT
entries against variable declarations.
* iNES 2.0 exponent fix verified by hand-tracing what byte 0xFF
produces post-fix: exp=30 (capped), mult=7, size=3 GiB nominal,
capped to 1 GiB, capped to 2 GiB by uppow2, FCEU_malloc returns
NULL on most systems, loader returns 0. No heap overflow for
any input byte.
* Savestate-loaded array index audit: built a Python scanner that
extracts each AddExState/SFORMAT entry and cross-references the
variable name against array-index uses in the same file. All
flagged sites covered.
* A libFuzzer harness and seed corpus generator (fuzz_main.c,
gen_seed_corpus.py) accompany this submission for ongoing
regression testing.
2026-05-04 02:15:40 +02:00
|
|
|
else if (GameInfo) /* GameInfo may be NULL pre-load */
|
2018-11-23 01:59:20 +08:00
|
|
|
update_nes_controllers(4, fc_to_libretro(GameInfo->inputfc));
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-05 13:35:30 +08:00
|
|
|
if (nes_input.type[2] == RETRO_DEVICE_GAMEPAD
|
|
|
|
|
|| nes_input.type[3] == RETRO_DEVICE_GAMEPAD)
|
2018-11-23 01:59:20 +08:00
|
|
|
FCEUI_DisableFourScore(0);
|
|
|
|
|
else
|
|
|
|
|
FCEUI_DisableFourScore(1);
|
|
|
|
|
|
|
|
|
|
/* check if famicom 4player adapter is used */
|
2020-05-05 13:35:30 +08:00
|
|
|
if (nes_input.type[4] == RETRO_DEVICE_FC_4PLAYERS)
|
2018-11-23 01:59:20 +08:00
|
|
|
FCEUI_DisableFourScore(1);
|
2017-11-25 11:16:26 +08:00
|
|
|
}
|
2017-09-09 16:16:12 +08:00
|
|
|
}
|
|
|
|
|
}
|
2014-03-30 22:15:17 +02:00
|
|
|
|
2021-10-19 17:33:56 +01:00
|
|
|
/* Core options 'update display' callback */
|
|
|
|
|
static bool update_option_visibility(void)
|
|
|
|
|
{
|
|
|
|
|
struct retro_variable var = {0};
|
|
|
|
|
bool updated = false;
|
|
|
|
|
|
|
|
|
|
/* If frontend supports core option categories,
|
|
|
|
|
* then fceumm_show_adv_system_options and
|
|
|
|
|
* fceumm_show_adv_sound_options are ignored
|
|
|
|
|
* and no options should be hidden */
|
|
|
|
|
if (libretro_supports_option_categories)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
var.key = "fceumm_show_adv_system_options";
|
|
|
|
|
var.value = NULL;
|
|
|
|
|
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
|
|
|
{
|
|
|
|
|
bool opt_showAdvSystemOptions_prev = opt_showAdvSystemOptions;
|
|
|
|
|
|
|
|
|
|
opt_showAdvSystemOptions = true;
|
|
|
|
|
if (strcmp(var.value, "disabled") == 0)
|
|
|
|
|
opt_showAdvSystemOptions = false;
|
|
|
|
|
|
|
|
|
|
if (opt_showAdvSystemOptions != opt_showAdvSystemOptions_prev)
|
|
|
|
|
{
|
|
|
|
|
struct retro_core_option_display option_display;
|
|
|
|
|
unsigned i;
|
|
|
|
|
unsigned size;
|
|
|
|
|
char options_list[][25] = {
|
|
|
|
|
"fceumm_overclocking",
|
|
|
|
|
"fceumm_ramstate",
|
|
|
|
|
"fceumm_nospritelimit",
|
|
|
|
|
"fceumm_up_down_allowed",
|
|
|
|
|
"fceumm_show_crosshair"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
option_display.visible = opt_showAdvSystemOptions;
|
|
|
|
|
size = sizeof(options_list) / sizeof(options_list[0]);
|
|
|
|
|
for (i = 0; i < size; i++)
|
|
|
|
|
{
|
|
|
|
|
option_display.key = options_list[i];
|
|
|
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY,
|
|
|
|
|
&option_display);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updated = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var.key = "fceumm_show_adv_sound_options";
|
|
|
|
|
var.value = NULL;
|
|
|
|
|
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
|
|
|
{
|
|
|
|
|
bool opt_showAdvSoundOptions_prev = opt_showAdvSoundOptions;
|
|
|
|
|
|
|
|
|
|
opt_showAdvSoundOptions = true;
|
|
|
|
|
if (strcmp(var.value, "disabled") == 0)
|
|
|
|
|
opt_showAdvSoundOptions = false;
|
|
|
|
|
|
|
|
|
|
if (opt_showAdvSoundOptions != opt_showAdvSoundOptions_prev)
|
|
|
|
|
{
|
|
|
|
|
struct retro_core_option_display option_display;
|
|
|
|
|
unsigned i;
|
|
|
|
|
unsigned size;
|
sound: add core option to mute ultrasonic triangle in HQ (fixes #638)
Castlevania II, Jackal, and several other titles repeatedly drop the
triangle channel into a period range that produces only ultrasonic
output (raw period <= 3, > ~12 kHz at NTSC). The hardware itself
plays these tones, but the DAC reconstruction filter in HQ mode
folds the ultrasonic energy back into the audible range as popping.
The LQ tri/noise/PCM mixer (RDoTriangleNoisePCMLQ) already gates the
triangle channel with `freq[0] <= 4` (i.e. raw period <= 3) for
exactly this reason -- the popping has been absent in LQ since
forever. The HQ path (RDoTriangle) never had the equivalent gate,
so users on Sound Quality = High/Very High hear the popping that LQ
users do not.
Mirror the gate in RDoTriangle, controlled by a new core option
`fceumm_removetrianglenoise` (default disabled). When the option
is off the codegen is identical to before -- gcc folds the
`FSettings.RemoveTriangleNoise && (...)` check via short-circuit
evaluation, the per-sample inner loop is untouched. When on, HQ
behaves like LQ for the triangle ultrasonic case and the popping
disappears.
LQ is intentionally left untouched -- it has always silenced these
tones unconditionally and changing that would be an audible behavior
shift for existing LQ users. The new option is HQ-only.
Backport reference: negativeExponent's libretro-fceumm_next fork
(commit afaa8f5) added the same option, gating both LQ and HQ paths
on it. This patch is the more conservative subset: only adds the
HQ gate, leaves LQ at its current (always-silencing) behaviour.
Build / codegen verified:
- sound.c -O2 / -O3 clean, +15 lines in RDoTriangle (.text),
inner per-sample for-loop unchanged.
- libretro.c clean (options_list[] widened from [25] to [32] to
fit the new key name).
- aarch64 cross-build clean.
2026-06-14 15:02:53 +00:00
|
|
|
char options_list[][32] = {
|
2021-10-19 17:33:56 +01:00
|
|
|
"fceumm_sndvolume",
|
|
|
|
|
"fceumm_sndquality",
|
2022-04-05 13:45:20 +01:00
|
|
|
"fceumm_sndlowpass",
|
sound: add core option to mute ultrasonic triangle in HQ (fixes #638)
Castlevania II, Jackal, and several other titles repeatedly drop the
triangle channel into a period range that produces only ultrasonic
output (raw period <= 3, > ~12 kHz at NTSC). The hardware itself
plays these tones, but the DAC reconstruction filter in HQ mode
folds the ultrasonic energy back into the audible range as popping.
The LQ tri/noise/PCM mixer (RDoTriangleNoisePCMLQ) already gates the
triangle channel with `freq[0] <= 4` (i.e. raw period <= 3) for
exactly this reason -- the popping has been absent in LQ since
forever. The HQ path (RDoTriangle) never had the equivalent gate,
so users on Sound Quality = High/Very High hear the popping that LQ
users do not.
Mirror the gate in RDoTriangle, controlled by a new core option
`fceumm_removetrianglenoise` (default disabled). When the option
is off the codegen is identical to before -- gcc folds the
`FSettings.RemoveTriangleNoise && (...)` check via short-circuit
evaluation, the per-sample inner loop is untouched. When on, HQ
behaves like LQ for the triangle ultrasonic case and the popping
disappears.
LQ is intentionally left untouched -- it has always silenced these
tones unconditionally and changing that would be an audible behavior
shift for existing LQ users. The new option is HQ-only.
Backport reference: negativeExponent's libretro-fceumm_next fork
(commit afaa8f5) added the same option, gating both LQ and HQ paths
on it. This patch is the more conservative subset: only adds the
HQ gate, leaves LQ at its current (always-silencing) behaviour.
Build / codegen verified:
- sound.c -O2 / -O3 clean, +15 lines in RDoTriangle (.text),
inner per-sample for-loop unchanged.
- libretro.c clean (options_list[] widened from [25] to [32] to
fit the new key name).
- aarch64 cross-build clean.
2026-06-14 15:02:53 +00:00
|
|
|
"fceumm_removetrianglenoise",
|
sound: add core option to reduce DMC channel popping (fixes #638)
Games like Castlevania II: Simon's Quest drive the DMC DAC directly
via $4011 writes to produce sample-style audio (separate from the
DPCM bit-stream path). The DAC jumps straight to the newly-written
7-bit value on every write, and those abrupt steps are audible as
clicks/pops when the writes form a low-frequency envelope.
Backport reference: negativeExponent's libretro-fceumm_next applies
a one-step midpoint smoother at the $4011 write site - take the
old DAC value and the new requested value, and store only the
average (rounded toward the new value). Successive writes still
converge on the target, so percussive samples retain their attack;
they just lose half of the per-write step amplitude, which is the
component that produces the click.
Backported as-is, gated on a new core option
`fceumm_reducedmcpopping` (default disabled, so the DAC
trajectory is bit-exact with the original code when the option is
not set). Verified to match _next's algebra exactly across the
boundary cases (full-range jumps in both directions, single-step
deltas, idle no-ops).
The DPCM bit-decode playback path (the +/-2 per bit RawDALatch
update further down in the DMC channel inner loop) is intentionally
NOT touched - that path is the well-behaved bit-stream output, no
popping, and smoothing it would attenuate normal samples.
This addresses the second half of issue #638 (the first being the
triangle ultrasonic gate, already landed as d3cd26e). The
follow-up comment on that issue asked specifically for DMC popping
reduction to land in libretro-fceumm since _next has no Android
port.
Build verified: sound.c -O2 / -O3 / aarch64 -O2 clean, libretro.c
clean.
2026-06-14 15:09:04 +00:00
|
|
|
"fceumm_reducedmcpopping",
|
2022-04-06 13:36:22 +01:00
|
|
|
"fceumm_sndstereodelay",
|
2021-10-19 17:33:56 +01:00
|
|
|
"fceumm_swapduty",
|
|
|
|
|
"fceumm_apu_1",
|
|
|
|
|
"fceumm_apu_2",
|
|
|
|
|
"fceumm_apu_3",
|
|
|
|
|
"fceumm_apu_4",
|
|
|
|
|
"fceumm_apu_5"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
option_display.visible = opt_showAdvSoundOptions;
|
|
|
|
|
size = sizeof(options_list) / sizeof(options_list[0]);
|
|
|
|
|
for (i = 0; i < size; i++)
|
|
|
|
|
{
|
|
|
|
|
option_display.key = options_list[i];
|
|
|
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY,
|
|
|
|
|
&option_display);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updated = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return updated;
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-11 23:11:55 +08:00
|
|
|
static void set_variables(void)
|
2014-03-30 22:35:00 +02:00
|
|
|
{
|
2021-10-19 17:33:56 +01:00
|
|
|
struct retro_core_option_display option_display;
|
2022-09-05 01:27:12 +02:00
|
|
|
unsigned index = 0;
|
2014-03-30 22:15:17 +02:00
|
|
|
|
2021-10-19 17:33:56 +01:00
|
|
|
option_display.visible = false;
|
|
|
|
|
|
2019-07-20 00:12:02 +08:00
|
|
|
/* Initialize main core option struct */
|
2021-10-19 17:33:56 +01:00
|
|
|
memset(&option_defs_us, 0, sizeof(option_defs_us));
|
2019-05-11 23:11:55 +08:00
|
|
|
|
2019-07-20 00:12:02 +08:00
|
|
|
/* Write common core options to main struct */
|
2021-12-04 14:41:07 +01:00
|
|
|
while (option_defs[index].key) {
|
|
|
|
|
memcpy(&option_defs_us[index], &option_defs[index],
|
2021-10-19 17:33:56 +01:00
|
|
|
sizeof(struct retro_core_option_v2_definition));
|
2019-07-20 00:12:02 +08:00
|
|
|
index++;
|
2019-05-11 23:11:55 +08:00
|
|
|
}
|
|
|
|
|
|
2019-07-20 00:12:02 +08:00
|
|
|
/* Append dipswitch settings to core options if available */
|
2021-10-19 17:33:56 +01:00
|
|
|
set_dipswitch_variables(index, option_defs_us);
|
|
|
|
|
|
|
|
|
|
libretro_supports_option_categories = false;
|
|
|
|
|
libretro_set_core_options(environ_cb,
|
|
|
|
|
&libretro_supports_option_categories);
|
|
|
|
|
|
|
|
|
|
/* If frontend supports core option categories,
|
|
|
|
|
* fceumm_show_adv_system_options and
|
|
|
|
|
* fceumm_show_adv_sound_options are unused
|
|
|
|
|
* and should be hidden */
|
|
|
|
|
if (libretro_supports_option_categories)
|
|
|
|
|
{
|
|
|
|
|
option_display.key = "fceumm_show_adv_system_options";
|
|
|
|
|
|
|
|
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY,
|
|
|
|
|
&option_display);
|
|
|
|
|
|
|
|
|
|
option_display.key = "fceumm_show_adv_sound_options";
|
|
|
|
|
|
|
|
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY,
|
|
|
|
|
&option_display);
|
|
|
|
|
}
|
|
|
|
|
/* If frontend does not support core option
|
|
|
|
|
* categories, core options may be shown/hidden
|
|
|
|
|
* at runtime. In this case, register 'update
|
|
|
|
|
* display' callback, so frontend can update
|
|
|
|
|
* core options menu without calling retro_run() */
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
struct retro_core_options_update_display_callback update_display_cb;
|
|
|
|
|
update_display_cb.callback = update_option_visibility;
|
|
|
|
|
|
|
|
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_UPDATE_DISPLAY_CALLBACK,
|
|
|
|
|
&update_display_cb);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* VS UNISystem games use internal palette regardless
|
|
|
|
|
* of user setting, so hide fceumm_palette option */
|
|
|
|
|
if (GameInfo && (GameInfo->type == GIT_VSUNI))
|
|
|
|
|
{
|
|
|
|
|
option_display.key = "fceumm_palette";
|
2019-05-11 23:11:55 +08:00
|
|
|
|
2021-10-19 17:33:56 +01:00
|
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY,
|
|
|
|
|
&option_display);
|
2021-10-22 13:30:27 +01:00
|
|
|
|
|
|
|
|
/* Additionally disable gamepad palette
|
|
|
|
|
* switching */
|
|
|
|
|
palette_switch_enabled = false;
|
2021-10-19 17:33:56 +01:00
|
|
|
}
|
2019-05-11 23:11:55 +08:00
|
|
|
}
|
|
|
|
|
|
2021-10-21 15:52:40 +01:00
|
|
|
/* Game Genie add-on must be enabled before
|
|
|
|
|
* loading content, so we cannot parse this
|
|
|
|
|
* option inside check_variables() */
|
|
|
|
|
static void check_game_genie_variable(void)
|
|
|
|
|
{
|
|
|
|
|
struct retro_variable var = {0};
|
|
|
|
|
int game_genie_enabled = 0;
|
|
|
|
|
|
|
|
|
|
var.key = "fceumm_game_genie";
|
|
|
|
|
|
|
|
|
|
/* Game Genie is only enabled for regular
|
|
|
|
|
* cartridges (excludes arcade content,
|
|
|
|
|
* FDS games, etc.) */
|
|
|
|
|
if ((GameInfo->type == GIT_CART) &&
|
|
|
|
|
(iNESCart.mapper != 105) && /* Nintendo World Championship cart (Mapper 105)*/
|
|
|
|
|
environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) &&
|
|
|
|
|
var.value &&
|
|
|
|
|
!strcmp(var.value, "enabled"))
|
|
|
|
|
game_genie_enabled = 1;
|
|
|
|
|
|
|
|
|
|
FCEUI_SetGameGenie(game_genie_enabled);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Callback passed to FCEUI_LoadGame()
|
|
|
|
|
* > Required since we must set and check
|
|
|
|
|
* core options immediately after ROM
|
|
|
|
|
* is loaded, before FCEUI_LoadGame()
|
|
|
|
|
* returns */
|
|
|
|
|
static void frontend_post_load_init()
|
|
|
|
|
{
|
|
|
|
|
set_variables();
|
|
|
|
|
check_game_genie_variable();
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-11 23:11:55 +08:00
|
|
|
void retro_set_environment(retro_environment_t cb)
|
|
|
|
|
{
|
2021-10-20 16:31:45 +01:00
|
|
|
struct retro_vfs_interface_info vfs_iface_info;
|
|
|
|
|
|
2017-11-25 11:16:26 +08:00
|
|
|
static const struct retro_controller_description pads1[] = {
|
2018-11-23 01:59:20 +08:00
|
|
|
{ "Auto", RETRO_DEVICE_AUTO },
|
2017-10-02 01:18:06 +08:00
|
|
|
{ "Gamepad", RETRO_DEVICE_GAMEPAD },
|
2017-11-25 11:16:26 +08:00
|
|
|
{ "Zapper", RETRO_DEVICE_ZAPPER },
|
2018-11-23 01:59:20 +08:00
|
|
|
{ 0, 0 },
|
2017-11-25 11:16:26 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const struct retro_controller_description pads2[] = {
|
2018-11-23 01:59:20 +08:00
|
|
|
{ "Auto", RETRO_DEVICE_AUTO },
|
2017-11-25 11:16:26 +08:00
|
|
|
{ "Gamepad", RETRO_DEVICE_GAMEPAD },
|
|
|
|
|
{ "Arkanoid", RETRO_DEVICE_ARKANOID },
|
|
|
|
|
{ "Zapper", RETRO_DEVICE_ZAPPER },
|
2023-01-01 08:09:19 -06:00
|
|
|
{ "Power Pad A", RETRO_DEVICE_POWERPADA },
|
|
|
|
|
{ "Power Pad B", RETRO_DEVICE_POWERPADB },
|
2018-11-23 01:59:20 +08:00
|
|
|
{ 0, 0 },
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const struct retro_controller_description pads3[] = {
|
|
|
|
|
{ "Auto", RETRO_DEVICE_AUTO },
|
|
|
|
|
{ "Gamepad", RETRO_DEVICE_GAMEPAD },
|
2023-01-01 08:09:19 -06:00
|
|
|
{ "Power Pad A", RETRO_DEVICE_POWERPADA },
|
|
|
|
|
{ "Power Pad B", RETRO_DEVICE_POWERPADB },
|
2018-11-23 01:59:20 +08:00
|
|
|
{ 0, 0 },
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const struct retro_controller_description pads4[] = {
|
|
|
|
|
{ "Auto", RETRO_DEVICE_AUTO },
|
|
|
|
|
{ "Gamepad", RETRO_DEVICE_GAMEPAD },
|
|
|
|
|
{ 0, 0 },
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const struct retro_controller_description pads5[] = {
|
|
|
|
|
{ "Auto", RETRO_DEVICE_FC_AUTO },
|
|
|
|
|
{ "Arkanoid", RETRO_DEVICE_FC_ARKANOID },
|
|
|
|
|
{ "(Bandai) Hyper Shot", RETRO_DEVICE_FC_SHADOW },
|
2021-04-10 15:20:50 +08:00
|
|
|
{ "(Konami) Hyper Shot", RETRO_DEVICE_FC_HYPERSHOT },
|
2018-11-23 01:59:20 +08:00
|
|
|
{ "Oeka Kids Tablet", RETRO_DEVICE_FC_OEKAKIDS },
|
|
|
|
|
{ "4-Player Adapter", RETRO_DEVICE_FC_4PLAYERS },
|
2023-09-15 19:48:52 -04:00
|
|
|
{ "Family Trainer A", RETRO_DEVICE_FC_FTRAINERA },
|
|
|
|
|
{ "Family Trainer B", RETRO_DEVICE_FC_FTRAINERB },
|
2018-11-23 01:59:20 +08:00
|
|
|
{ 0, 0 },
|
2017-09-09 16:16:12 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const struct retro_controller_info ports[] = {
|
2017-11-25 11:16:26 +08:00
|
|
|
{ pads1, 3 },
|
2022-12-31 21:27:46 -06:00
|
|
|
{ pads2, 6 },
|
2023-01-01 07:30:56 -06:00
|
|
|
{ pads3, 4 },
|
2018-11-23 01:59:20 +08:00
|
|
|
{ pads4, 2 },
|
2023-09-15 19:48:52 -04:00
|
|
|
{ pads5, 8 },
|
2018-11-23 01:59:20 +08:00
|
|
|
{ 0, 0 },
|
2017-09-09 16:16:12 +08:00
|
|
|
};
|
|
|
|
|
|
2021-10-20 16:31:45 +01:00
|
|
|
static const struct retro_system_content_info_override content_overrides[] = {
|
|
|
|
|
{
|
|
|
|
|
"fds|nes|unf|unif", /* extensions */
|
|
|
|
|
false, /* need_fullpath */
|
|
|
|
|
false /* persistent_data */
|
|
|
|
|
},
|
|
|
|
|
{ NULL, false, false }
|
|
|
|
|
};
|
|
|
|
|
|
2014-03-30 22:35:00 +02:00
|
|
|
environ_cb = cb;
|
2021-10-20 16:31:45 +01:00
|
|
|
|
2019-05-11 23:11:55 +08:00
|
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports);
|
2021-10-20 16:31:45 +01:00
|
|
|
|
|
|
|
|
vfs_iface_info.required_interface_version = 1;
|
|
|
|
|
vfs_iface_info.iface = NULL;
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VFS_INTERFACE, &vfs_iface_info))
|
|
|
|
|
filestream_vfs_init(&vfs_iface_info);
|
|
|
|
|
|
|
|
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE,
|
|
|
|
|
(void*)content_overrides);
|
2014-03-30 22:15:17 +02:00
|
|
|
}
|
|
|
|
|
|
2014-03-30 22:35:00 +02:00
|
|
|
void retro_get_system_info(struct retro_system_info *info)
|
|
|
|
|
{
|
2021-10-20 16:31:45 +01:00
|
|
|
info->need_fullpath = true;
|
2016-09-07 18:51:12 +04:00
|
|
|
info->valid_extensions = "fds|nes|unf|unif";
|
2016-12-09 17:38:47 -05:00
|
|
|
#ifdef GIT_VERSION
|
2017-09-09 16:16:12 +08:00
|
|
|
info->library_version = "(SVN)" GIT_VERSION;
|
2016-12-09 17:38:47 -05:00
|
|
|
#else
|
2017-03-24 02:05:55 +01:00
|
|
|
info->library_version = "(SVN)";
|
2016-12-09 17:38:47 -05:00
|
|
|
#endif
|
2017-03-24 02:05:55 +01:00
|
|
|
info->library_name = "FCEUmm";
|
|
|
|
|
info->block_extract = false;
|
2014-03-30 22:15:17 +02:00
|
|
|
}
|
|
|
|
|
|
2021-04-28 18:15:10 -05:00
|
|
|
static float get_aspect_ratio(unsigned width, unsigned height)
|
|
|
|
|
{
|
|
|
|
|
if (aspect_ratio_par == 2)
|
|
|
|
|
return NES_4_3;
|
|
|
|
|
else if (aspect_ratio_par == 3)
|
|
|
|
|
return NES_PP;
|
|
|
|
|
else
|
|
|
|
|
return NES_8_7_PAR;
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-30 22:35:00 +02:00
|
|
|
void retro_get_system_av_info(struct retro_system_av_info *info)
|
|
|
|
|
{
|
2023-03-10 21:47:35 -05:00
|
|
|
unsigned width = NES_WIDTH - crop_overscan_h_left - crop_overscan_h_right;
|
|
|
|
|
unsigned height = NES_HEIGHT - crop_overscan_v_top - crop_overscan_v_bottom;
|
2020-05-06 05:13:47 +08:00
|
|
|
#ifdef HAVE_NTSC_FILTER
|
2020-05-06 05:45:24 +08:00
|
|
|
info->geometry.base_width = (use_ntsc ? NES_NTSC_OUT_WIDTH(width) : width);
|
2020-10-02 19:17:55 +08:00
|
|
|
info->geometry.max_width = (use_ntsc ? NES_NTSC_WIDTH : NES_WIDTH);
|
2020-05-06 05:13:47 +08:00
|
|
|
#else
|
2020-05-06 05:45:24 +08:00
|
|
|
info->geometry.base_width = width;
|
2020-05-06 05:13:47 +08:00
|
|
|
info->geometry.max_width = NES_WIDTH;
|
|
|
|
|
#endif
|
2020-05-06 05:45:24 +08:00
|
|
|
info->geometry.base_height = height;
|
2020-05-05 09:07:55 +08:00
|
|
|
info->geometry.max_height = NES_HEIGHT;
|
2021-04-28 18:15:10 -05:00
|
|
|
info->geometry.aspect_ratio = get_aspect_ratio(width, height);
|
2017-09-09 14:54:02 +08:00
|
|
|
info->timing.sample_rate = (float)sndsamplerate;
|
2021-06-05 15:05:07 +02:00
|
|
|
if (FSettings.PAL || dendy)
|
2021-10-21 15:52:40 +01:00
|
|
|
info->timing.fps = NES_PAL_FPS;
|
2014-03-30 22:35:00 +02:00
|
|
|
else
|
2021-10-21 15:52:40 +01:00
|
|
|
info->timing.fps = NES_NTSC_FPS;
|
2014-03-30 22:15:17 +02:00
|
|
|
}
|
|
|
|
|
|
2014-06-23 15:10:26 +02:00
|
|
|
static void check_system_specs(void)
|
|
|
|
|
{
|
2017-09-08 02:22:16 +08:00
|
|
|
/* TODO - when we get it running at fullspeed on PSP, set to 4 */
|
2014-06-23 15:10:26 +02:00
|
|
|
unsigned level = 5;
|
|
|
|
|
environ_cb(RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL, &level);
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-30 22:35:00 +02:00
|
|
|
void retro_init(void)
|
|
|
|
|
{
|
2017-09-06 23:13:45 +02:00
|
|
|
bool achievements = true;
|
2014-04-18 19:16:52 +02:00
|
|
|
log_cb.log=default_logger;
|
|
|
|
|
environ_cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log_cb);
|
2017-08-29 19:19:36 +08:00
|
|
|
|
2017-08-29 19:15:36 +08:00
|
|
|
environ_cb(RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS, &achievements);
|
2017-08-29 19:19:36 +08:00
|
|
|
|
2019-06-21 11:21:46 +02:00
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_INPUT_BITMASKS, NULL))
|
|
|
|
|
libretro_supports_bitmasks = true;
|
2021-10-21 15:52:40 +01:00
|
|
|
|
|
|
|
|
environ_cb(RETRO_ENVIRONMENT_GET_MESSAGE_INTERFACE_VERSION,
|
|
|
|
|
&libretro_msg_interface_version);
|
2021-10-22 13:30:27 +01:00
|
|
|
|
|
|
|
|
palette_switch_init();
|
2014-03-30 22:15:17 +02:00
|
|
|
}
|
|
|
|
|
|
2017-09-05 01:15:54 +08:00
|
|
|
static void retro_set_custom_palette(void)
|
2014-03-30 22:35:00 +02:00
|
|
|
{
|
2020-02-25 09:08:44 +08:00
|
|
|
unsigned i;
|
2014-03-30 22:35:00 +02:00
|
|
|
|
2024-10-15 22:35:46 +08:00
|
|
|
palette_game_available = 0;
|
2015-03-19 23:16:52 +01:00
|
|
|
use_raw_palette = false;
|
|
|
|
|
|
2020-03-21 08:49:45 +08:00
|
|
|
/* VS UNISystem uses internal palette presets regardless of options */
|
|
|
|
|
if (GameInfo->type == GIT_VSUNI)
|
|
|
|
|
FCEU_ResetPalette();
|
|
|
|
|
|
|
|
|
|
/* Reset and choose between default internal or external custom palette */
|
|
|
|
|
else if (current_palette == PAL_DEFAULT || current_palette == PAL_CUSTOM)
|
2014-03-30 22:35:00 +02:00
|
|
|
{
|
2024-10-15 22:35:46 +08:00
|
|
|
palette_game_available = external_palette_exist && (current_palette == PAL_CUSTOM);
|
2017-09-04 21:03:48 +08:00
|
|
|
|
2024-10-15 22:35:46 +08:00
|
|
|
/* if palette_game_available is set to 1, external palette
|
2020-03-21 08:49:45 +08:00
|
|
|
* is loaded, else it will load default NES palette.
|
|
|
|
|
* FCEUI_SetPaletteArray() both resets the palette array to
|
|
|
|
|
* internal default palette and then chooses which one to use. */
|
2024-10-15 22:35:46 +08:00
|
|
|
FCEUI_SetPaletteArray( NULL, 0 );
|
2014-03-30 22:35:00 +02:00
|
|
|
}
|
2014-03-30 22:15:17 +02:00
|
|
|
|
2020-01-26 10:22:42 +08:00
|
|
|
/* setup raw palette */
|
|
|
|
|
else if (current_palette == PAL_RAW)
|
2015-03-19 23:16:52 +01:00
|
|
|
{
|
2020-02-25 07:13:42 +08:00
|
|
|
pal color;
|
2015-03-19 23:16:52 +01:00
|
|
|
use_raw_palette = true;
|
|
|
|
|
for (i = 0; i < 64; i++)
|
|
|
|
|
{
|
2020-01-26 10:22:42 +08:00
|
|
|
color.r = (((i >> 0) & 0xF) * 255) / 15;
|
|
|
|
|
color.g = (((i >> 4) & 0x3) * 255) / 3;
|
|
|
|
|
color.b = 0;
|
|
|
|
|
FCEUD_SetPalette( i, color.r, color.g, color.b);
|
2015-03-19 23:16:52 +01:00
|
|
|
}
|
2024-10-15 17:02:00 +08:00
|
|
|
#if !defined(PSP) || !defined(PS2)
|
|
|
|
|
for (i = 0; i < 512; i++)
|
|
|
|
|
{
|
|
|
|
|
color.r = (((i >> 0) & 0xF) * 255) / 15;
|
|
|
|
|
color.g = (((i >> 4) & 0x3) * 255) / 3;
|
|
|
|
|
color.b = (((i >> 6) & 0x7) * 255 / 7);
|
|
|
|
|
FCEUD_SetPalette( 256 + i, color.r, color.g, color.b);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
2015-03-19 23:16:52 +01:00
|
|
|
}
|
|
|
|
|
|
2020-03-21 08:49:45 +08:00
|
|
|
/* setup palette presets */
|
2020-01-26 10:22:42 +08:00
|
|
|
else
|
2014-03-30 22:35:00 +02:00
|
|
|
{
|
2020-01-26 10:22:42 +08:00
|
|
|
unsigned *palette_data = palettes[current_palette].data;
|
|
|
|
|
for ( i = 0; i < 64; i++ )
|
|
|
|
|
{
|
|
|
|
|
unsigned data = palette_data[i];
|
2020-02-25 07:13:42 +08:00
|
|
|
base_palette[ i * 3 + 0 ] = ( data >> 16 ) & 0xff; /* red */
|
|
|
|
|
base_palette[ i * 3 + 1 ] = ( data >> 8 ) & 0xff; /* green */
|
|
|
|
|
base_palette[ i * 3 + 2 ] = ( data >> 0 ) & 0xff; /* blue */
|
2020-01-26 10:22:42 +08:00
|
|
|
}
|
2024-10-15 22:35:46 +08:00
|
|
|
FCEUI_SetPaletteArray( base_palette, 64 );
|
2014-03-30 22:35:00 +02:00
|
|
|
}
|
2014-03-30 22:15:17 +02:00
|
|
|
}
|
|
|
|
|
|
2017-03-11 03:04:38 +08:00
|
|
|
/* Set variables for NTSC(1) / PAL(2) / Dendy(3)
|
|
|
|
|
* Dendy has PAL framerate and resolution, but ~NTSC timings,
|
2017-09-03 01:20:35 +08:00
|
|
|
* and has 50 dummy scanlines to force 50 fps.
|
|
|
|
|
*/
|
2020-10-25 19:13:45 +08:00
|
|
|
static void FCEUD_RegionOverride(unsigned region)
|
2017-03-11 03:04:38 +08:00
|
|
|
{
|
core: stride-aware savestate, iNES2 helpers, -Wundef, -Wmissing-prototypes
Audit pass 5 - five distinct cleanups bundled into one omnibus.
1. Element-stride byte-swapping for savestate fields (state.h, state.c,
fceu-endian.{h,c})
The SFORMAT 's' field was previously {bit 31 = RLSB, bits 0..30 =
byte size}. RLSB triggers FlipByteOrder() on MSB hosts, which
reverses the entire entry buffer end-to-end. That is correct for a
single primitive (size <= 8 bytes) but wrong for an array of
multi-byte primitives - reversing the whole buffer would swap
element 0 with element N-1 and reverse their bytes too, scrambling
the data.
The previous workaround was either splitting an N-element array
into N separate single-primitive entries with distinct chunk IDs
(n106 PlayIndex split into IDX0..IDX7) or skipping the entry
entirely on big-endian hosts (the GEKKO #ifndef in vrc6.c / vrc7.c).
Both approaches mean BE saves are not portable to LE and vice
versa, and force the same workaround at every new array site.
This pass adds proper stride support:
* SFORMAT 's' encoding is now {bit 31 = RLSB, bits 24..30 =
stride in bytes (0 = legacy/unset), bits 0..23 = byte size}.
16 MiB max size, well above any actual savestate field.
* FCEUSTATE_RLSB_ARRAY(stride) macro for the new pattern.
* FlipByteOrderStrided() byte-swaps each element of an array
independently. Round-trip identity verified: [01 00 00 00 ...]
-> [00 00 00 01 ...] -> [01 00 00 00 ...].
* state.c's SubWrite / ReadStateChunk / CheckS use new helpers
sf_size() / sf_stride() / sf_flip() that mask the size out of
the new bit layout and dispatch to the strided variant when
stride < size.
Backwards compatible: legacy single-primitive entries (size == 1,
2, 4, 8) leave the stride bits at zero, which sf_stride() reads
as "stride equals size" and falls through to FlipByteOrder() as
before. No on-disk format change. Existing single-primitive RLSB
sites are unchanged.
The infrastructure is now in place so any future SFORMAT entry
that is an array of multi-byte primitives can be expressed as a
single entry (e.g. "{ buf, sizeof(buf) | FCEUSTATE_RLSB_ARRAY(4),
"BUF." }") without splitting or skipping. The existing PlayIndex
split and GEKKO #ifndefs are intentionally left untouched -
migrating them would alter the on-disk savestate format and is a
separate decision.
2. iNES1-vs-iNES2 sizing helpers (cart.h)
Twelve sites across the codebase encoded the same conditional:
info->iNES2 ? (info->PRGRamSize + info->PRGRamSaveSize) : default
Sometimes for PRGRAM, sometimes for CHRRAM, sometimes in bytes,
sometimes after dividing by 1024. The pattern is verbose and easy
to write inconsistently.
Added two inline helpers in cart.h:
- CartInfo_PRGRAM_bytes(info, default_bytes)
- CartInfo_CHRRAM_bytes(info, default_bytes)
Migrated 9 of the 13 sites: cartram.c (2), 162.c, 163.c, 134.c,
399.c, 478.c, 480.c, 484.c. The remaining 4 are non-helper-fitting
variants (164.c special masking, 2 cartram SaveGameLen sites with
different fallback semantics, mmc3.c Boogerman submapper detection).
3. -Wundef enabled permanently (Makefile.libretro)
Zero warnings out of the box - no #if-on-undefined-macro footguns
in the codebase. Now part of WARNING_DEFINES alongside the existing
-Wsign-compare.
4. -Wmissing-prototypes enabled permanently (Makefile.libretro)
Started at 198 warnings, cleared all of them:
* Mass-static-ified ~96 functions across 75 files that were
defined non-static but only used within their own translation
unit. (See static_prototype_fixer.py in the development notes.)
* K&R-style empty-parens prototypes "()" replaced with explicit
"(void)" across all asic_*.{c,h} files - GCC treats "()" as
"any args" and refuses to match it against a separate K&R
definition.
* Added missing forward declarations to public headers:
- fds.h (FDSLoad)
- nsf.h (NSFLoad)
- ines.h (iNESLoad)
- unif.h (UNIFLoad)
- latch.h (LatchHardReset, K&R fix)
- eeprom_93Cx6.h (eeprom_93Cx6_read, K&R fix)
Each header gained an "#include "file.h"" where needed.
* fds_apu.c now includes its own fds_apu.h header (was missing).
* fds_apu.h: removed unused FDSSoundRead declaration (the function
is internal-static).
* cartram.h: removed unused CartRAM_close declaration (the function
is internal-static).
* input.h: added a centralised block of FCEU_Init* prototypes
(Zapper, Mouse, Powerpad, Arkanoid, VirtualBoy, FKB, SuborKB,
PEC586KB, HS, Mahjong, FamilyTrainerA/B, OekaKids, TopRider,
BarcodeWorld, BattleBox, QuizKing, FTrainerA/B, SpaceShadow,
LCDCompZapper, ArkanoidFC) plus FCEU_ZapperSetTolerance. These
were previously declared as "extern" inside src/input.c.
* Static-ified FP_FASTAPASS callbacks in 106.c, 65.c, 67.c,
asic_h3001.c, asic_vrc3.c (those with no external callers);
left non-static for those that have header decls or are
referenced from sibling .c files (asic_mmc1, asic_vrc6,
asic_vrc7, flashrom).
* For a small set of cross-file functions where adding a header
was disproportionate to the value (MMC5_hb, NSFMMC5_Close,
GetKeyboard, FCEU_GetJoyJoy), placed a forward declaration
immediately above the definition. This satisfies
-Wmissing-prototypes (which checks for any prior declaration
in scope) without churning the public-header layout.
5. -Wshadow partial cleanup (not enabled permanently)
Fixed five real shadows that were either bugs or actively
misleading:
* src/boards/476.c: removed an inner "int i" that shadowed the
outer loop counter.
* src/boards/mmc5.c MMC5_hb: parameter "scanline" renamed to
"sl_param" (was shadowing the global "scanline").
* src/boards/n106.c DoNamcoSound: parameter "Wave" renamed to
"WaveBuf" (was shadowing the global Wave audio buffer); also
updated the forward declaration and the matching parameter
on sound.h's NeoFill function pointer typedef.
* src/boards/vrc7.c UpdateOPLNEO: same Wave -> WaveBuf rename.
* src/ntsc/nes_ntsc_impl.h: renamed an inner loop counter "n"
that shadowed an outer "n".
* src/drivers/libretro/libretro.c FCEUD_RegionOverride: local
"pal" renamed to "is_pal" (was shadowing the typedef "pal" from
palette.h).
* src/palette.c FCEUI_SetPaletteArray: parameter "pal" renamed
to "data" (same shadow); driver.h declaration updated to match.
-Wshadow itself is NOT enabled permanently because the remaining
warnings are deliberate parameter naming conventions (XBuf in
draw functions, X in cpu hooks) and third-party blargg ntsc code.
In addition, four files were touched as part of an MSVC-build fix
that came up mid-pass: src/fds.c, src/nsf.c, src/ines.c, and
src/drivers/libretro/libretro_dipswitch.c had snprintf() calls
introduced in pass 4 that fail to link on pre-MSVC2015 toolchains
when STATIC_LINKING=1 (the libretro-common compat_snprintf.c shim
isn't compiled in those configurations). Replaced each snprintf with
either sprintf-into-bounded-buffer (the format strings have known
maximum output) or strlcpy/strlcat for the dipswitch key-build case.
All output is still bounded; truncation happens via strl*'s normal
truncation semantics where applicable.
All added code is C89-clean (top-of-block declarations only, no
mixed decls, no // comments, INLINE macro from fceu-types.h instead
of bare "inline"). Builds clean under -std=gnu11 with -Wno-write-
strings -Wsign-compare -Wundef -Wmissing-prototypes; zero errors,
zero warnings.
Determinism audit (audit_determinism.py): no rand/time/long
double/threads issues introduced.
2026-05-04 04:44:52 +02:00
|
|
|
unsigned is_pal = 0;
|
2020-02-07 14:53:14 +08:00
|
|
|
unsigned d = 0;
|
2017-07-01 11:01:46 +02:00
|
|
|
|
2017-03-11 03:04:38 +08:00
|
|
|
switch (region)
|
|
|
|
|
{
|
|
|
|
|
case 0: /* auto */
|
2020-02-07 14:53:14 +08:00
|
|
|
d = (systemRegion >> 1) & 1;
|
core: stride-aware savestate, iNES2 helpers, -Wundef, -Wmissing-prototypes
Audit pass 5 - five distinct cleanups bundled into one omnibus.
1. Element-stride byte-swapping for savestate fields (state.h, state.c,
fceu-endian.{h,c})
The SFORMAT 's' field was previously {bit 31 = RLSB, bits 0..30 =
byte size}. RLSB triggers FlipByteOrder() on MSB hosts, which
reverses the entire entry buffer end-to-end. That is correct for a
single primitive (size <= 8 bytes) but wrong for an array of
multi-byte primitives - reversing the whole buffer would swap
element 0 with element N-1 and reverse their bytes too, scrambling
the data.
The previous workaround was either splitting an N-element array
into N separate single-primitive entries with distinct chunk IDs
(n106 PlayIndex split into IDX0..IDX7) or skipping the entry
entirely on big-endian hosts (the GEKKO #ifndef in vrc6.c / vrc7.c).
Both approaches mean BE saves are not portable to LE and vice
versa, and force the same workaround at every new array site.
This pass adds proper stride support:
* SFORMAT 's' encoding is now {bit 31 = RLSB, bits 24..30 =
stride in bytes (0 = legacy/unset), bits 0..23 = byte size}.
16 MiB max size, well above any actual savestate field.
* FCEUSTATE_RLSB_ARRAY(stride) macro for the new pattern.
* FlipByteOrderStrided() byte-swaps each element of an array
independently. Round-trip identity verified: [01 00 00 00 ...]
-> [00 00 00 01 ...] -> [01 00 00 00 ...].
* state.c's SubWrite / ReadStateChunk / CheckS use new helpers
sf_size() / sf_stride() / sf_flip() that mask the size out of
the new bit layout and dispatch to the strided variant when
stride < size.
Backwards compatible: legacy single-primitive entries (size == 1,
2, 4, 8) leave the stride bits at zero, which sf_stride() reads
as "stride equals size" and falls through to FlipByteOrder() as
before. No on-disk format change. Existing single-primitive RLSB
sites are unchanged.
The infrastructure is now in place so any future SFORMAT entry
that is an array of multi-byte primitives can be expressed as a
single entry (e.g. "{ buf, sizeof(buf) | FCEUSTATE_RLSB_ARRAY(4),
"BUF." }") without splitting or skipping. The existing PlayIndex
split and GEKKO #ifndefs are intentionally left untouched -
migrating them would alter the on-disk savestate format and is a
separate decision.
2. iNES1-vs-iNES2 sizing helpers (cart.h)
Twelve sites across the codebase encoded the same conditional:
info->iNES2 ? (info->PRGRamSize + info->PRGRamSaveSize) : default
Sometimes for PRGRAM, sometimes for CHRRAM, sometimes in bytes,
sometimes after dividing by 1024. The pattern is verbose and easy
to write inconsistently.
Added two inline helpers in cart.h:
- CartInfo_PRGRAM_bytes(info, default_bytes)
- CartInfo_CHRRAM_bytes(info, default_bytes)
Migrated 9 of the 13 sites: cartram.c (2), 162.c, 163.c, 134.c,
399.c, 478.c, 480.c, 484.c. The remaining 4 are non-helper-fitting
variants (164.c special masking, 2 cartram SaveGameLen sites with
different fallback semantics, mmc3.c Boogerman submapper detection).
3. -Wundef enabled permanently (Makefile.libretro)
Zero warnings out of the box - no #if-on-undefined-macro footguns
in the codebase. Now part of WARNING_DEFINES alongside the existing
-Wsign-compare.
4. -Wmissing-prototypes enabled permanently (Makefile.libretro)
Started at 198 warnings, cleared all of them:
* Mass-static-ified ~96 functions across 75 files that were
defined non-static but only used within their own translation
unit. (See static_prototype_fixer.py in the development notes.)
* K&R-style empty-parens prototypes "()" replaced with explicit
"(void)" across all asic_*.{c,h} files - GCC treats "()" as
"any args" and refuses to match it against a separate K&R
definition.
* Added missing forward declarations to public headers:
- fds.h (FDSLoad)
- nsf.h (NSFLoad)
- ines.h (iNESLoad)
- unif.h (UNIFLoad)
- latch.h (LatchHardReset, K&R fix)
- eeprom_93Cx6.h (eeprom_93Cx6_read, K&R fix)
Each header gained an "#include "file.h"" where needed.
* fds_apu.c now includes its own fds_apu.h header (was missing).
* fds_apu.h: removed unused FDSSoundRead declaration (the function
is internal-static).
* cartram.h: removed unused CartRAM_close declaration (the function
is internal-static).
* input.h: added a centralised block of FCEU_Init* prototypes
(Zapper, Mouse, Powerpad, Arkanoid, VirtualBoy, FKB, SuborKB,
PEC586KB, HS, Mahjong, FamilyTrainerA/B, OekaKids, TopRider,
BarcodeWorld, BattleBox, QuizKing, FTrainerA/B, SpaceShadow,
LCDCompZapper, ArkanoidFC) plus FCEU_ZapperSetTolerance. These
were previously declared as "extern" inside src/input.c.
* Static-ified FP_FASTAPASS callbacks in 106.c, 65.c, 67.c,
asic_h3001.c, asic_vrc3.c (those with no external callers);
left non-static for those that have header decls or are
referenced from sibling .c files (asic_mmc1, asic_vrc6,
asic_vrc7, flashrom).
* For a small set of cross-file functions where adding a header
was disproportionate to the value (MMC5_hb, NSFMMC5_Close,
GetKeyboard, FCEU_GetJoyJoy), placed a forward declaration
immediately above the definition. This satisfies
-Wmissing-prototypes (which checks for any prior declaration
in scope) without churning the public-header layout.
5. -Wshadow partial cleanup (not enabled permanently)
Fixed five real shadows that were either bugs or actively
misleading:
* src/boards/476.c: removed an inner "int i" that shadowed the
outer loop counter.
* src/boards/mmc5.c MMC5_hb: parameter "scanline" renamed to
"sl_param" (was shadowing the global "scanline").
* src/boards/n106.c DoNamcoSound: parameter "Wave" renamed to
"WaveBuf" (was shadowing the global Wave audio buffer); also
updated the forward declaration and the matching parameter
on sound.h's NeoFill function pointer typedef.
* src/boards/vrc7.c UpdateOPLNEO: same Wave -> WaveBuf rename.
* src/ntsc/nes_ntsc_impl.h: renamed an inner loop counter "n"
that shadowed an outer "n".
* src/drivers/libretro/libretro.c FCEUD_RegionOverride: local
"pal" renamed to "is_pal" (was shadowing the typedef "pal" from
palette.h).
* src/palette.c FCEUI_SetPaletteArray: parameter "pal" renamed
to "data" (same shadow); driver.h declaration updated to match.
-Wshadow itself is NOT enabled permanently because the remaining
warnings are deliberate parameter naming conventions (XBuf in
draw functions, X in cpu hooks) and third-party blargg ntsc code.
In addition, four files were touched as part of an MSVC-build fix
that came up mid-pass: src/fds.c, src/nsf.c, src/ines.c, and
src/drivers/libretro/libretro_dipswitch.c had snprintf() calls
introduced in pass 4 that fail to link on pre-MSVC2015 toolchains
when STATIC_LINKING=1 (the libretro-common compat_snprintf.c shim
isn't compiled in those configurations). Replaced each snprintf with
either sprintf-into-bounded-buffer (the format strings have known
maximum output) or strlcpy/strlcat for the dipswitch key-build case.
All output is still bounded; truncation happens via strl*'s normal
truncation semantics where applicable.
All added code is C89-clean (top-of-block declarations only, no
mixed decls, no // comments, INLINE macro from fceu-types.h instead
of bare "inline"). Builds clean under -std=gnu11 with -Wno-write-
strings -Wsign-compare -Wundef -Wmissing-prototypes; zero errors,
zero warnings.
Determinism audit (audit_determinism.py): no rand/time/long
double/threads issues introduced.
2026-05-04 04:44:52 +02:00
|
|
|
is_pal = systemRegion & 1;
|
2017-03-11 03:04:38 +08:00
|
|
|
break;
|
|
|
|
|
case 1: /* ntsc */
|
2021-10-21 15:52:40 +01:00
|
|
|
FCEUD_DispMessage(RETRO_LOG_INFO, 2000, "System: NTSC");
|
2017-03-11 03:04:38 +08:00
|
|
|
break;
|
|
|
|
|
case 2: /* pal */
|
core: stride-aware savestate, iNES2 helpers, -Wundef, -Wmissing-prototypes
Audit pass 5 - five distinct cleanups bundled into one omnibus.
1. Element-stride byte-swapping for savestate fields (state.h, state.c,
fceu-endian.{h,c})
The SFORMAT 's' field was previously {bit 31 = RLSB, bits 0..30 =
byte size}. RLSB triggers FlipByteOrder() on MSB hosts, which
reverses the entire entry buffer end-to-end. That is correct for a
single primitive (size <= 8 bytes) but wrong for an array of
multi-byte primitives - reversing the whole buffer would swap
element 0 with element N-1 and reverse their bytes too, scrambling
the data.
The previous workaround was either splitting an N-element array
into N separate single-primitive entries with distinct chunk IDs
(n106 PlayIndex split into IDX0..IDX7) or skipping the entry
entirely on big-endian hosts (the GEKKO #ifndef in vrc6.c / vrc7.c).
Both approaches mean BE saves are not portable to LE and vice
versa, and force the same workaround at every new array site.
This pass adds proper stride support:
* SFORMAT 's' encoding is now {bit 31 = RLSB, bits 24..30 =
stride in bytes (0 = legacy/unset), bits 0..23 = byte size}.
16 MiB max size, well above any actual savestate field.
* FCEUSTATE_RLSB_ARRAY(stride) macro for the new pattern.
* FlipByteOrderStrided() byte-swaps each element of an array
independently. Round-trip identity verified: [01 00 00 00 ...]
-> [00 00 00 01 ...] -> [01 00 00 00 ...].
* state.c's SubWrite / ReadStateChunk / CheckS use new helpers
sf_size() / sf_stride() / sf_flip() that mask the size out of
the new bit layout and dispatch to the strided variant when
stride < size.
Backwards compatible: legacy single-primitive entries (size == 1,
2, 4, 8) leave the stride bits at zero, which sf_stride() reads
as "stride equals size" and falls through to FlipByteOrder() as
before. No on-disk format change. Existing single-primitive RLSB
sites are unchanged.
The infrastructure is now in place so any future SFORMAT entry
that is an array of multi-byte primitives can be expressed as a
single entry (e.g. "{ buf, sizeof(buf) | FCEUSTATE_RLSB_ARRAY(4),
"BUF." }") without splitting or skipping. The existing PlayIndex
split and GEKKO #ifndefs are intentionally left untouched -
migrating them would alter the on-disk savestate format and is a
separate decision.
2. iNES1-vs-iNES2 sizing helpers (cart.h)
Twelve sites across the codebase encoded the same conditional:
info->iNES2 ? (info->PRGRamSize + info->PRGRamSaveSize) : default
Sometimes for PRGRAM, sometimes for CHRRAM, sometimes in bytes,
sometimes after dividing by 1024. The pattern is verbose and easy
to write inconsistently.
Added two inline helpers in cart.h:
- CartInfo_PRGRAM_bytes(info, default_bytes)
- CartInfo_CHRRAM_bytes(info, default_bytes)
Migrated 9 of the 13 sites: cartram.c (2), 162.c, 163.c, 134.c,
399.c, 478.c, 480.c, 484.c. The remaining 4 are non-helper-fitting
variants (164.c special masking, 2 cartram SaveGameLen sites with
different fallback semantics, mmc3.c Boogerman submapper detection).
3. -Wundef enabled permanently (Makefile.libretro)
Zero warnings out of the box - no #if-on-undefined-macro footguns
in the codebase. Now part of WARNING_DEFINES alongside the existing
-Wsign-compare.
4. -Wmissing-prototypes enabled permanently (Makefile.libretro)
Started at 198 warnings, cleared all of them:
* Mass-static-ified ~96 functions across 75 files that were
defined non-static but only used within their own translation
unit. (See static_prototype_fixer.py in the development notes.)
* K&R-style empty-parens prototypes "()" replaced with explicit
"(void)" across all asic_*.{c,h} files - GCC treats "()" as
"any args" and refuses to match it against a separate K&R
definition.
* Added missing forward declarations to public headers:
- fds.h (FDSLoad)
- nsf.h (NSFLoad)
- ines.h (iNESLoad)
- unif.h (UNIFLoad)
- latch.h (LatchHardReset, K&R fix)
- eeprom_93Cx6.h (eeprom_93Cx6_read, K&R fix)
Each header gained an "#include "file.h"" where needed.
* fds_apu.c now includes its own fds_apu.h header (was missing).
* fds_apu.h: removed unused FDSSoundRead declaration (the function
is internal-static).
* cartram.h: removed unused CartRAM_close declaration (the function
is internal-static).
* input.h: added a centralised block of FCEU_Init* prototypes
(Zapper, Mouse, Powerpad, Arkanoid, VirtualBoy, FKB, SuborKB,
PEC586KB, HS, Mahjong, FamilyTrainerA/B, OekaKids, TopRider,
BarcodeWorld, BattleBox, QuizKing, FTrainerA/B, SpaceShadow,
LCDCompZapper, ArkanoidFC) plus FCEU_ZapperSetTolerance. These
were previously declared as "extern" inside src/input.c.
* Static-ified FP_FASTAPASS callbacks in 106.c, 65.c, 67.c,
asic_h3001.c, asic_vrc3.c (those with no external callers);
left non-static for those that have header decls or are
referenced from sibling .c files (asic_mmc1, asic_vrc6,
asic_vrc7, flashrom).
* For a small set of cross-file functions where adding a header
was disproportionate to the value (MMC5_hb, NSFMMC5_Close,
GetKeyboard, FCEU_GetJoyJoy), placed a forward declaration
immediately above the definition. This satisfies
-Wmissing-prototypes (which checks for any prior declaration
in scope) without churning the public-header layout.
5. -Wshadow partial cleanup (not enabled permanently)
Fixed five real shadows that were either bugs or actively
misleading:
* src/boards/476.c: removed an inner "int i" that shadowed the
outer loop counter.
* src/boards/mmc5.c MMC5_hb: parameter "scanline" renamed to
"sl_param" (was shadowing the global "scanline").
* src/boards/n106.c DoNamcoSound: parameter "Wave" renamed to
"WaveBuf" (was shadowing the global Wave audio buffer); also
updated the forward declaration and the matching parameter
on sound.h's NeoFill function pointer typedef.
* src/boards/vrc7.c UpdateOPLNEO: same Wave -> WaveBuf rename.
* src/ntsc/nes_ntsc_impl.h: renamed an inner loop counter "n"
that shadowed an outer "n".
* src/drivers/libretro/libretro.c FCEUD_RegionOverride: local
"pal" renamed to "is_pal" (was shadowing the typedef "pal" from
palette.h).
* src/palette.c FCEUI_SetPaletteArray: parameter "pal" renamed
to "data" (same shadow); driver.h declaration updated to match.
-Wshadow itself is NOT enabled permanently because the remaining
warnings are deliberate parameter naming conventions (XBuf in
draw functions, X in cpu hooks) and third-party blargg ntsc code.
In addition, four files were touched as part of an MSVC-build fix
that came up mid-pass: src/fds.c, src/nsf.c, src/ines.c, and
src/drivers/libretro/libretro_dipswitch.c had snprintf() calls
introduced in pass 4 that fail to link on pre-MSVC2015 toolchains
when STATIC_LINKING=1 (the libretro-common compat_snprintf.c shim
isn't compiled in those configurations). Replaced each snprintf with
either sprintf-into-bounded-buffer (the format strings have known
maximum output) or strlcpy/strlcat for the dipswitch key-build case.
All output is still bounded; truncation happens via strl*'s normal
truncation semantics where applicable.
All added code is C89-clean (top-of-block declarations only, no
mixed decls, no // comments, INLINE macro from fceu-types.h instead
of bare "inline"). Builds clean under -std=gnu11 with -Wno-write-
strings -Wsign-compare -Wundef -Wmissing-prototypes; zero errors,
zero warnings.
Determinism audit (audit_determinism.py): no rand/time/long
double/threads issues introduced.
2026-05-04 04:44:52 +02:00
|
|
|
is_pal = 1;
|
2021-10-21 15:52:40 +01:00
|
|
|
FCEUD_DispMessage(RETRO_LOG_INFO, 2000, "System: PAL");
|
2017-03-11 03:04:38 +08:00
|
|
|
break;
|
|
|
|
|
case 3: /* dendy */
|
2020-02-07 14:53:14 +08:00
|
|
|
d = 1;
|
2021-10-21 15:52:40 +01:00
|
|
|
FCEUD_DispMessage(RETRO_LOG_INFO, 2000, "System: Dendy");
|
2017-03-11 03:04:38 +08:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-05 15:05:07 +02:00
|
|
|
dendy = d;
|
core: stride-aware savestate, iNES2 helpers, -Wundef, -Wmissing-prototypes
Audit pass 5 - five distinct cleanups bundled into one omnibus.
1. Element-stride byte-swapping for savestate fields (state.h, state.c,
fceu-endian.{h,c})
The SFORMAT 's' field was previously {bit 31 = RLSB, bits 0..30 =
byte size}. RLSB triggers FlipByteOrder() on MSB hosts, which
reverses the entire entry buffer end-to-end. That is correct for a
single primitive (size <= 8 bytes) but wrong for an array of
multi-byte primitives - reversing the whole buffer would swap
element 0 with element N-1 and reverse their bytes too, scrambling
the data.
The previous workaround was either splitting an N-element array
into N separate single-primitive entries with distinct chunk IDs
(n106 PlayIndex split into IDX0..IDX7) or skipping the entry
entirely on big-endian hosts (the GEKKO #ifndef in vrc6.c / vrc7.c).
Both approaches mean BE saves are not portable to LE and vice
versa, and force the same workaround at every new array site.
This pass adds proper stride support:
* SFORMAT 's' encoding is now {bit 31 = RLSB, bits 24..30 =
stride in bytes (0 = legacy/unset), bits 0..23 = byte size}.
16 MiB max size, well above any actual savestate field.
* FCEUSTATE_RLSB_ARRAY(stride) macro for the new pattern.
* FlipByteOrderStrided() byte-swaps each element of an array
independently. Round-trip identity verified: [01 00 00 00 ...]
-> [00 00 00 01 ...] -> [01 00 00 00 ...].
* state.c's SubWrite / ReadStateChunk / CheckS use new helpers
sf_size() / sf_stride() / sf_flip() that mask the size out of
the new bit layout and dispatch to the strided variant when
stride < size.
Backwards compatible: legacy single-primitive entries (size == 1,
2, 4, 8) leave the stride bits at zero, which sf_stride() reads
as "stride equals size" and falls through to FlipByteOrder() as
before. No on-disk format change. Existing single-primitive RLSB
sites are unchanged.
The infrastructure is now in place so any future SFORMAT entry
that is an array of multi-byte primitives can be expressed as a
single entry (e.g. "{ buf, sizeof(buf) | FCEUSTATE_RLSB_ARRAY(4),
"BUF." }") without splitting or skipping. The existing PlayIndex
split and GEKKO #ifndefs are intentionally left untouched -
migrating them would alter the on-disk savestate format and is a
separate decision.
2. iNES1-vs-iNES2 sizing helpers (cart.h)
Twelve sites across the codebase encoded the same conditional:
info->iNES2 ? (info->PRGRamSize + info->PRGRamSaveSize) : default
Sometimes for PRGRAM, sometimes for CHRRAM, sometimes in bytes,
sometimes after dividing by 1024. The pattern is verbose and easy
to write inconsistently.
Added two inline helpers in cart.h:
- CartInfo_PRGRAM_bytes(info, default_bytes)
- CartInfo_CHRRAM_bytes(info, default_bytes)
Migrated 9 of the 13 sites: cartram.c (2), 162.c, 163.c, 134.c,
399.c, 478.c, 480.c, 484.c. The remaining 4 are non-helper-fitting
variants (164.c special masking, 2 cartram SaveGameLen sites with
different fallback semantics, mmc3.c Boogerman submapper detection).
3. -Wundef enabled permanently (Makefile.libretro)
Zero warnings out of the box - no #if-on-undefined-macro footguns
in the codebase. Now part of WARNING_DEFINES alongside the existing
-Wsign-compare.
4. -Wmissing-prototypes enabled permanently (Makefile.libretro)
Started at 198 warnings, cleared all of them:
* Mass-static-ified ~96 functions across 75 files that were
defined non-static but only used within their own translation
unit. (See static_prototype_fixer.py in the development notes.)
* K&R-style empty-parens prototypes "()" replaced with explicit
"(void)" across all asic_*.{c,h} files - GCC treats "()" as
"any args" and refuses to match it against a separate K&R
definition.
* Added missing forward declarations to public headers:
- fds.h (FDSLoad)
- nsf.h (NSFLoad)
- ines.h (iNESLoad)
- unif.h (UNIFLoad)
- latch.h (LatchHardReset, K&R fix)
- eeprom_93Cx6.h (eeprom_93Cx6_read, K&R fix)
Each header gained an "#include "file.h"" where needed.
* fds_apu.c now includes its own fds_apu.h header (was missing).
* fds_apu.h: removed unused FDSSoundRead declaration (the function
is internal-static).
* cartram.h: removed unused CartRAM_close declaration (the function
is internal-static).
* input.h: added a centralised block of FCEU_Init* prototypes
(Zapper, Mouse, Powerpad, Arkanoid, VirtualBoy, FKB, SuborKB,
PEC586KB, HS, Mahjong, FamilyTrainerA/B, OekaKids, TopRider,
BarcodeWorld, BattleBox, QuizKing, FTrainerA/B, SpaceShadow,
LCDCompZapper, ArkanoidFC) plus FCEU_ZapperSetTolerance. These
were previously declared as "extern" inside src/input.c.
* Static-ified FP_FASTAPASS callbacks in 106.c, 65.c, 67.c,
asic_h3001.c, asic_vrc3.c (those with no external callers);
left non-static for those that have header decls or are
referenced from sibling .c files (asic_mmc1, asic_vrc6,
asic_vrc7, flashrom).
* For a small set of cross-file functions where adding a header
was disproportionate to the value (MMC5_hb, NSFMMC5_Close,
GetKeyboard, FCEU_GetJoyJoy), placed a forward declaration
immediately above the definition. This satisfies
-Wmissing-prototypes (which checks for any prior declaration
in scope) without churning the public-header layout.
5. -Wshadow partial cleanup (not enabled permanently)
Fixed five real shadows that were either bugs or actively
misleading:
* src/boards/476.c: removed an inner "int i" that shadowed the
outer loop counter.
* src/boards/mmc5.c MMC5_hb: parameter "scanline" renamed to
"sl_param" (was shadowing the global "scanline").
* src/boards/n106.c DoNamcoSound: parameter "Wave" renamed to
"WaveBuf" (was shadowing the global Wave audio buffer); also
updated the forward declaration and the matching parameter
on sound.h's NeoFill function pointer typedef.
* src/boards/vrc7.c UpdateOPLNEO: same Wave -> WaveBuf rename.
* src/ntsc/nes_ntsc_impl.h: renamed an inner loop counter "n"
that shadowed an outer "n".
* src/drivers/libretro/libretro.c FCEUD_RegionOverride: local
"pal" renamed to "is_pal" (was shadowing the typedef "pal" from
palette.h).
* src/palette.c FCEUI_SetPaletteArray: parameter "pal" renamed
to "data" (same shadow); driver.h declaration updated to match.
-Wshadow itself is NOT enabled permanently because the remaining
warnings are deliberate parameter naming conventions (XBuf in
draw functions, X in cpu hooks) and third-party blargg ntsc code.
In addition, four files were touched as part of an MSVC-build fix
that came up mid-pass: src/fds.c, src/nsf.c, src/ines.c, and
src/drivers/libretro/libretro_dipswitch.c had snprintf() calls
introduced in pass 4 that fail to link on pre-MSVC2015 toolchains
when STATIC_LINKING=1 (the libretro-common compat_snprintf.c shim
isn't compiled in those configurations). Replaced each snprintf with
either sprintf-into-bounded-buffer (the format strings have known
maximum output) or strlcpy/strlcat for the dipswitch key-build case.
All output is still bounded; truncation happens via strl*'s normal
truncation semantics where applicable.
All added code is C89-clean (top-of-block declarations only, no
mixed decls, no // comments, INLINE macro from fceu-types.h instead
of bare "inline"). Builds clean under -std=gnu11 with -Wno-write-
strings -Wsign-compare -Wundef -Wmissing-prototypes; zero errors,
zero warnings.
Determinism audit (audit_determinism.py): no rand/time/long
double/threads issues introduced.
2026-05-04 04:44:52 +02:00
|
|
|
FCEUI_SetVidSystem(is_pal);
|
2020-02-25 09:08:44 +08:00
|
|
|
ResetPalette();
|
2017-03-11 03:04:38 +08:00
|
|
|
}
|
|
|
|
|
|
2014-03-30 22:35:00 +02:00
|
|
|
void retro_deinit (void)
|
|
|
|
|
{
|
|
|
|
|
FCEUI_CloseGame();
|
2014-04-17 21:32:53 +02:00
|
|
|
FCEUI_Sound(0);
|
2014-03-30 22:35:00 +02:00
|
|
|
FCEUI_Kill();
|
2015-04-17 22:15:28 +01:00
|
|
|
#if defined(_3DS)
|
core: memory-safety, leak, and savestate-portability audit fixes
Squashed series of ~50 distinct bugs found during a multi-day
security/correctness audit, ranging from ROM-triggerable heap
corruption and savestate-triggered OOB read primitives down to
obscure cross-platform savestate breakage.
Build is clean on `make platform=unix` with zero new warnings.
CRITICAL (ROM-triggerable, exploitable on the user's machine)
=============================================================
* ines.c iNES 2.0 PRG/CHR exponent overflow leading to undersized
allocation followed by heap buffer overflow on fread (pow() with
attacker-chosen exponent up to 63). Cap exponent at 30 and
compute size in uint32 with explicit cap below 2 GiB before
storing in int.
* ines.c miscROMSize int wraparound from attacker-controlled PRG/
CHR sizes; previous '& 0x8000000' check missed common underflow
cases. Compute in int64 and reject <= 0 or > 128 MiB.
* unif.c MAPR chunk OOB heap read on chunks of size < 4. Validate
chunk size before allocating board-name buffer.
* unif.c chunk size truncation: int conversion + missing cap allowed
a 4 GiB chunk size to wrap. Cap at 16 MiB.
* unif.c FixRomSize infinite loop on size > 0x80000000. Cap input.
* unif.c DINF chunk: months[(m - 1) % 12] is UB for m==0; m comes
from the .unf file. Clamp m into [1..12] before subtracting.
* unif.c NAME chunk: GameInfo->name = malloc(...) was unchecked.
* nsf.c size underflow: FCEU_fgetsize() - 0x80 wraps to ~UINT64_MAX
on tiny files, propagating a huge size into uppow2/FCEU_malloc.
Validate size > 0x80 before subtracting.
* nsf.c NSFMaxBank * 4096 cap tightened from 1<<20 (UB on signed-int
overflow) to 1<<19 (fits in int).
* general.c uppow2 had signed-int UB (1 << 32) and wrapped to 0 for
inputs near uint32 max. Cap at 0x80000000.
* cheat.c SubCheats[256] BSS overflow when more than 256 GG/PAR
substitution cheats are active. The array is adjacent to MMapPtrs[64]
in BSS which is exposed via retro_get_memory_data, making this
overflow visible from outside the core.
* libretro.c retro_cheat_set strcpy stack overflow with arbitrary-
length cheat strings from the frontend. Use strlcpy.
CRITICAL (savestate-triggerable on a malicious .fcs file)
=========================================================
* fds.c FDS savestate OOB read primitive: InDisk loaded from
savestate is used as index into 8-element diskdata[] static
pointer array. A value 8..254 dereferences arbitrary memory as
a pointer (heap-read primitive). Also bounds-checked SelectDisk,
mapperFDS_block, mapperFDS_blockstart, mapperFDS_diskaddr,
mapperFDS_blocklen so blockstart+diskaddr stays within the 65500-
byte disk buffer.
* 11 mappers had savestate-loaded variables masked at write time
but not at restore time, used as indices into fixed arrays:
- 88.c, KS7037.c, 112.c cmd indexes reg[8]
- sachen.c (S8259, S74LS374N) cmd indexes latch[8]
- 357.c dipswitch indexes outer_bank[4]
- unrom512.c flash_state indexes erase_a/d/b[5]
- 368.c preg indexes banks[8]
- 69.c sndcmd indexes sreg[14]
- mmc2and4.c latch0/latch1 index creg[4]
None individually exploitable into RCE - the array writes corrupt
adjacent BSS with constrained data flowing in - but each is an
out-of-bounds read or write from attacker-controllable input.
HIGH (memory-safety, reachable on any load)
===========================================
* fceu-memory.c FCEU_malloc deref-of-NULL on allocation failure
('ret = 0; memset(ret, 0, size);').
* file.c multiple FCEUFILE/MakeMemWrap/MakeMemWrapBuffer unchecked
allocations and unchecked filestream_tell return.
* libretro.c GameInfo NULL-derefs in three entry points
(retro_set_controller_port_device, retro_get_memory_data,
retro_get_memory_size) reachable on operations called before a
successful load.
* libretro.c framebuffer leak ~256 KB per failed FCEUI_LoadGame
(the libretro frontend doesn't call retro_unload_game on a
failed load).
* libretro.c 3DS retro_deinit unchecked linearFree.
* libretro.c stereo / NTSC filter unchecked malloc returns.
* fceu.c FCEUI_LoadGame unchecked GameInfo malloc.
* fds.c SubLoad and FDSLoad unchecked FCEU_malloc on diskdatao
backup buffers (NULL-deref in subsequent memcpy).
* fceu.c AllocGenieRW partial-failure leak: AReadG allocated but
BWriteG malloc fails -> 256 KB leak per retry.
* input/bworld.c Update(): unbounded strcpy from attacker-supplied
data into 20-byte bdata. Replaced with length-bounded copy.
* cart.c setprg2r/4r and setchr1r/2r/4r/8r mask-underflow guards.
SetupCartPRGMapping/SetupCartCHRMapping compute (size >> N) - 1
which underflows to 0xFFFFFFFF for chips smaller than the unit.
setprg8r/16r/32r and setchr8r already had defensive size checks;
the smaller units did not. malee.c (2 KB chip) and mapper 218
(2 KB NTARAM-as-CHR) accidentally avoid OOB; the primitive is
unsafe for any future board.
* video.c FCEU_InitVirtualVideo XBuf/XDBuf partial-failure leak.
* video.c, fceu.c FCEU_DispMessage / FCEU_printf / FCEU_PrintError:
vsprintf into fixed stack buffers replaced with vsnprintf.
* core: validate magic-string read length on FDS/UNIF/NSF load
(reject files too short to contain the magic so previous static
buffer / stack garbage doesn't spuriously match).
LEAKS (every cart load/unload cycle)
====================================
* mmc5.c MMC5 cart-side: WRAM (up to 64K) + MMC5fill (1K) + ExRAM
(1K) leaked per cycle. NSFMMC5_Close existed but was only used
for NSF code path.
* onebus.c Mapper 270/436: 8 KB CHRRAM leaked per cycle.
* 330.c, 375.c, 528.c: 8 KB WRAM each, no Close at all.
CORRECTNESS (UB / ABI / endianness)
===================================
* fceu-endian.c FlipByteOrder loop bound was 'count' instead of
'count/2', making it a no-op for every even count and silently
breaking savestate portability for every FCEUSTATE_RLSB-marked
field. The '#ifndef GEKKO' workarounds scattered through the
codebase (sound.c, vrc7.c, vrc6.c) were symptoms of this root
cause.
* state.c AddExState bounds check ran AFTER writing the entry, so
when SFEXINDEX hit the 63-entry cap the next call would write
the entry then immediately overwrite it with the terminator.
* state.c FCEUSS_Save_Mem: post-save callback was guarded by
'if (SPreSave)' instead of 'if (SPostSave)'.
* Sequence-point UB in counter expressions across 4 board files,
e.g. 'x = !x ? a : --x' and 'x = ++x % n'. GCC -Wsequence-point
flags all five sites (285.c, 413.c, asic_mmc3.c, jyasic.c).
* SFORMAT size mismatches between declared variable type and
AddExState size argument:
- asic_vrc3.c VRC3_count/VRC3_reload (uint16, saved as 1 byte)
- mmc3.c m555_count (uint32, saved as 2 bytes), m555_count_-
expired (uint8, saved as 2 bytes - OOB write into adjacent
BSS).
* unrom512.c UNROM-512 mapper 30 .srm save format: host-endian
uint32 flash write counters. Now stored as LE on disk regardless
of host (Battle Kid 2, Twin Dragons, Lizard, Sole, etc.).
* ~70 multi-byte single-variable SFORMAT/AddExState entries across
36 board files were saved as host-byte-order. After the
FlipByteOrder fix, these are now byte-swapped on BE so cross-
platform savestates round-trip correctly.
* coolgirl.c new ExStateLE() macro added; 22 multi-byte sites.
* pic16c5x.c 11 multi-byte AddExState calls (m_PC, m_PREVPC,
m_CONFIG, m_WDT, m_prescaler, m_opcode, m_STACK[0/1], m_icount,
m_delay_timer, m_rtcc, m_inst_cycles, m_clock2cycle).
* onebus.c PowerJoy Supermax submapper detection used non-portable
*(uint32*)&info->MD5 cast that read different bytes on LE vs BE.
* fds.c clean up redundant FCEUSTATE_RLSB encoding in AddExState
calls that also passed type=1 (idempotent OR; readability fix).
DOCUMENTATION
=============
* input.c UpdateGP's *(uint32*)data cast looks like a typical
endian bug but is actually correct (the libretro frontend builds
JSReturn with matching host-uint32 shifts). Comment added to
prevent future "fixes" from breaking it.
Limitations not addressed
=========================
* Element-stride-aware byte swapping. The savestate byte-swap
mechanism (FlipByteOrder over the entire SFORMAT entry buffer)
is structurally wrong for arrays of multi-byte values: it
reverses the whole buffer end-to-end instead of byte-swapping
each element. Several places that need cross-platform-portable
arrays (VRC7 sound state, jyasic chr[8]) work around this by
either splitting arrays into per-element SFORMAT entries (n106
PlayIndex, bandai reg) or by skipping save entirely on BE via
#ifndef GEKKO. A proper fix would extend the size encoding with
an element-stride field. Left for a future change because it
would change the savestate format.
* Strict-aliasing UB in ppu.c (around 9 sites doing
*(uint32*)uint8_buf for fast 4-byte writes via FCEU_dwmemset).
Works in practice with all common compilers because the patterns
are byte-symmetric, but is formally UB.
* FCEU_gmalloc calls exit(1) on OOM. A libretro core should never
exit() because that takes down the whole frontend. Used by
100+ call sites; refactoring to return-NULL is out of scope here.
Testing
=======
* Build: clean on `make platform=unix` with -O2; no new warnings.
* FlipByteOrder fix verified by hand-trace and a standalone unit
test for counts 2, 4, 8.
* uppow2 fix verified by unit test across 13 boundary cases.
* SFORMAT size mismatches and missing-RLSB cases identified by
Python static-analysis scripts that cross-reference SFORMAT
entries against variable declarations.
* iNES 2.0 exponent fix verified by hand-tracing what byte 0xFF
produces post-fix: exp=30 (capped), mult=7, size=3 GiB nominal,
capped to 1 GiB, capped to 2 GiB by uppow2, FCEU_malloc returns
NULL on most systems, loader returns 0. No heap overflow for
any input byte.
* Savestate-loaded array index audit: built a Python scanner that
extracts each AddExState/SFORMAT entry and cross-references the
variable name against array-index uses in the same file. All
flagged sites covered.
* A libFuzzer harness and seed corpus generator (fuzz_main.c,
gen_seed_corpus.py) accompany this submission for ongoing
regression testing.
2026-05-04 02:15:40 +02:00
|
|
|
if (fceu_video_out)
|
|
|
|
|
linearFree(fceu_video_out);
|
|
|
|
|
fceu_video_out = NULL;
|
2015-08-28 12:17:56 +02:00
|
|
|
#else
|
|
|
|
|
if (fceu_video_out)
|
|
|
|
|
free(fceu_video_out);
|
|
|
|
|
fceu_video_out = NULL;
|
2015-04-17 22:15:28 +01:00
|
|
|
#endif
|
2019-02-22 00:05:24 +01:00
|
|
|
#if defined(RENDER_GSKIT_PS2)
|
|
|
|
|
ps2 = NULL;
|
|
|
|
|
#endif
|
2019-06-21 11:21:46 +02:00
|
|
|
libretro_supports_bitmasks = false;
|
2021-10-21 15:52:40 +01:00
|
|
|
libretro_msg_interface_version = 0;
|
2019-07-21 09:57:03 +08:00
|
|
|
DPSW_Cleanup();
|
2020-01-26 10:22:42 +08:00
|
|
|
#ifdef HAVE_NTSC_FILTER
|
2020-02-25 09:08:44 +08:00
|
|
|
NTSCFilter_Cleanup();
|
2020-01-26 10:22:42 +08:00
|
|
|
#endif
|
2021-10-22 13:30:27 +01:00
|
|
|
palette_switch_deinit();
|
2022-04-06 13:36:22 +01:00
|
|
|
stereo_filter_deinit();
|
2014-03-30 22:15:17 +02:00
|
|
|
}
|
|
|
|
|
|
2014-03-30 22:35:00 +02:00
|
|
|
void retro_reset(void)
|
|
|
|
|
{
|
|
|
|
|
ResetNES();
|
2014-03-30 22:15:17 +02:00
|
|
|
}
|
|
|
|
|
|
2018-11-30 09:22:36 +08:00
|
|
|
static void set_apu_channels(int chan)
|
|
|
|
|
{
|
core: APU channel disable - fix bitmasks and LQ-mode volume application
Two related bugs in the user-facing fceumm_apu_1..5 channel disable
toggles. Pre-existing since commit 448a231 (set_apu_channels) and at
least as far back as the LQ rewrite for the second one.
set_apu_channels (libretro.c)
-----------------------------
The bitmask built by check_variables uses bit i for fceumm_apu_(i+1)
enabled, ordered SQ1, SQ2, Triangle, Noise, DMC. The old code:
FSettings.SquareVolume[1] = (chan & 1) ? 256 : 0;
FSettings.SquareVolume[0] = (chan & 2) ? 256 : 0;
FSettings.TriangleVolume = (chan & 3) ? 256 : 0;
FSettings.NoiseVolume = (chan & 4) ? 256 : 0;
FSettings.PCMVolume = (chan & 5) ? 256 : 0;
crossed SQ1/SQ2 and used non-power-of-two masks for the remaining
three. Practical effect: apu_1 muted SQ2 and apu_2 muted SQ1; apu_3
through apu_5 left their channels audible in any normal config (the
masks happened to be true any time another bit was set).
Fixed to read each channel's own bit:
FSettings.SquareVolume[0] = (chan & 0x01) ? 256 : 0; /* SQ1 */
FSettings.SquareVolume[1] = (chan & 0x02) ? 256 : 0; /* SQ2 */
FSettings.TriangleVolume = (chan & 0x04) ? 256 : 0; /* Tri */
FSettings.NoiseVolume = (chan & 0x08) ? 256 : 0; /* Nse */
FSettings.PCMVolume = (chan & 0x10) ? 256 : 0; /* DMC */
RDoTriangleNoisePCMLQ (sound.c)
-------------------------------
The combined Triangle/Noise/DMC LQ mix uses a non-linear lookup
table:
totalout = wlookup2[lq_tcout + noiseout + RawDALatch];
The old code took the noise envelope (EnvUnits[2], not Triangle -
Triangle has no envelope; EnvUnits[0]=SQ1, [1]=SQ2, [2]=Noise) and
scaled it by FSettings.TriangleVolume, despite the comment claiming
to modify Triangle. Meanwhile lq_tcout (Triangle) and RawDALatch
(DMC) went into wlookup2 unscaled, so neither TriangleVolume nor
PCMVolume had any effect in LQ mode.
Fixed:
* amptab[0] (noise envelope) is now scaled by FSettings.NoiseVolume.
* lq_tcout is scaled by FSettings.TriangleVolume into a local
scaled_tcout fed to wlookup2.
* RawDALatch is scaled by FSettings.PCMVolume into scaled_dmc.
The volume scalars are loop-invariant within this function, so the
Triangle volume is cached once in tri_vol at function entry to keep
the inner loops tight; the != 256 fast paths preserve byte-exact
behavior at full volume.
Verification
------------
Built per-channel-only test ROMs (test_sq1_only, test_sq2_only,
test_tri_only, test_nse_only, test_dmc_only) and an apu_test
runner that measures output RMS with each fceumm_apu_N toggle.
After the fix, in both LQ and HQ modes, each apu_N toggle mutes
exactly the channel whose ROM is the only sound source, and no
other toggle has any effect on it.
Default (all-on) audio output is bit-exact identical to the
baseline across the existing test_silent / test_idle / test_active /
test_stress ROMs (1500 frames each), so games and homebrew that
never touch the apu_N options see no change. No performance
regression in the default path; the != 256 guards skip the multiplies.
2026-05-04 07:40:08 +02:00
|
|
|
/* Bitmask layout (set up by check_variables): bit i = fceumm_apu_(i+1)
|
|
|
|
|
* is enabled. Channel order matches the libretro core options:
|
|
|
|
|
* bit 0 = Square 1 (apu_1)
|
|
|
|
|
* bit 1 = Square 2 (apu_2)
|
|
|
|
|
* bit 2 = Triangle (apu_3)
|
|
|
|
|
* bit 3 = Noise (apu_4)
|
|
|
|
|
* bit 4 = PCM / DMC (apu_5)
|
|
|
|
|
*
|
|
|
|
|
* The previous masks crossed the SQ1/SQ2 indices and used non-power-of-
|
|
|
|
|
* two values for the remaining channels, which silently broke the
|
|
|
|
|
* apu_3..apu_5 toggles entirely (any combination of bits 0/1/2 left
|
|
|
|
|
* those channels audible) and made apu_1 mute SQ2 and vice versa. */
|
|
|
|
|
FSettings.SquareVolume[0] = (chan & 0x01) ? 256 : 0;
|
|
|
|
|
FSettings.SquareVolume[1] = (chan & 0x02) ? 256 : 0;
|
|
|
|
|
FSettings.TriangleVolume = (chan & 0x04) ? 256 : 0;
|
|
|
|
|
FSettings.NoiseVolume = (chan & 0x08) ? 256 : 0;
|
|
|
|
|
FSettings.PCMVolume = (chan & 0x10) ? 256 : 0;
|
2018-11-30 09:22:36 +08:00
|
|
|
}
|
|
|
|
|
|
2017-01-02 03:36:39 +01:00
|
|
|
static void check_variables(bool startup)
|
2014-03-30 22:35:00 +02:00
|
|
|
{
|
|
|
|
|
struct retro_variable var = {0};
|
2018-11-30 09:22:36 +08:00
|
|
|
char key[256];
|
|
|
|
|
int i, enable_apu;
|
2022-04-06 13:36:22 +01:00
|
|
|
bool stereo_filter_updated = false;
|
2014-03-30 22:35:00 +02:00
|
|
|
|
2020-05-05 09:24:24 +08:00
|
|
|
/* 1 = Performs only geometry update: e.g. overscans */
|
|
|
|
|
/* 2 = Performs video/geometry update when needed and timing changes: e.g. region and filter change */
|
|
|
|
|
int audio_video_updated = 0;
|
|
|
|
|
|
2019-05-11 23:11:55 +08:00
|
|
|
var.key = "fceumm_ramstate";
|
|
|
|
|
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
|
|
|
{
|
|
|
|
|
if (!strcmp(var.value, "random"))
|
2021-06-05 15:05:07 +02:00
|
|
|
option_ramstate = 2;
|
2019-05-11 23:11:55 +08:00
|
|
|
else if (!strcmp(var.value, "fill $00"))
|
2021-06-05 15:05:07 +02:00
|
|
|
option_ramstate = 1;
|
2019-05-11 23:11:55 +08:00
|
|
|
else
|
2021-06-05 15:05:07 +02:00
|
|
|
option_ramstate = 0;
|
2019-05-11 23:11:55 +08:00
|
|
|
}
|
|
|
|
|
|
2020-01-26 10:22:42 +08:00
|
|
|
#ifdef HAVE_NTSC_FILTER
|
|
|
|
|
var.key = "fceumm_ntsc_filter";
|
|
|
|
|
|
2024-10-15 22:35:46 +08:00
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && GameInfo && GameInfo->type != GIT_NSF)
|
2020-01-26 10:22:42 +08:00
|
|
|
{
|
|
|
|
|
unsigned orig_value = use_ntsc;
|
|
|
|
|
if (strcmp(var.value, "disabled") == 0)
|
|
|
|
|
use_ntsc = NTSC_NONE;
|
|
|
|
|
else if (strcmp(var.value, "composite") == 0)
|
|
|
|
|
use_ntsc = NTSC_COMPOSITE;
|
|
|
|
|
else if (strcmp(var.value, "svideo") == 0)
|
|
|
|
|
use_ntsc = NTSC_SVIDEO;
|
|
|
|
|
else if (strcmp(var.value, "rgb") == 0)
|
|
|
|
|
use_ntsc = NTSC_RGB;
|
|
|
|
|
else if (strcmp(var.value, "monochrome") == 0)
|
|
|
|
|
use_ntsc = NTSC_MONOCHROME;
|
|
|
|
|
if (use_ntsc != orig_value)
|
2020-05-05 09:24:24 +08:00
|
|
|
{
|
|
|
|
|
ResetPalette();
|
2020-05-05 09:07:55 +08:00
|
|
|
audio_video_updated = 2;
|
2020-05-05 09:24:24 +08:00
|
|
|
}
|
2020-02-27 20:23:08 +08:00
|
|
|
}
|
|
|
|
|
#endif /* HAVE_NTSC_FILTER */
|
2020-01-26 10:22:42 +08:00
|
|
|
|
2016-02-22 18:36:09 +01:00
|
|
|
var.key = "fceumm_palette";
|
2014-03-30 22:35:00 +02:00
|
|
|
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
|
|
|
{
|
|
|
|
|
unsigned orig_value = current_palette;
|
|
|
|
|
|
2017-08-31 19:33:22 +08:00
|
|
|
if (!strcmp(var.value, "default"))
|
2018-11-25 09:01:21 +08:00
|
|
|
current_palette = PAL_DEFAULT;
|
|
|
|
|
else if (!strcmp(var.value, "raw"))
|
|
|
|
|
current_palette = PAL_RAW;
|
|
|
|
|
else if (!strcmp(var.value, "custom"))
|
|
|
|
|
current_palette = PAL_CUSTOM;
|
2021-06-05 15:05:07 +02:00
|
|
|
else if (!strcmp(var.value, "asqrealc"))
|
|
|
|
|
current_palette = 0;
|
2025-04-23 17:19:41 -05:00
|
|
|
else if (!strcmp(var.value, "restored-wii-vc"))
|
2021-06-05 15:05:07 +02:00
|
|
|
current_palette = 1;
|
2025-04-23 17:19:41 -05:00
|
|
|
else if (!strcmp(var.value, "wii-vc"))
|
2021-06-05 15:05:07 +02:00
|
|
|
current_palette = 2;
|
2025-04-23 17:19:41 -05:00
|
|
|
else if (!strcmp(var.value, "rgb"))
|
2021-06-05 15:05:07 +02:00
|
|
|
current_palette = 3;
|
2025-04-23 17:19:41 -05:00
|
|
|
else if (!strcmp(var.value, "yuv-v3"))
|
2021-06-05 15:05:07 +02:00
|
|
|
current_palette = 4;
|
2025-04-23 17:19:41 -05:00
|
|
|
else if (!strcmp(var.value, "unsaturated-final"))
|
2021-06-05 15:05:07 +02:00
|
|
|
current_palette = 5;
|
2025-04-23 17:19:41 -05:00
|
|
|
else if (!strcmp(var.value, "sony-cxa2025as-us"))
|
2021-06-05 15:05:07 +02:00
|
|
|
current_palette = 6;
|
2025-04-23 17:19:41 -05:00
|
|
|
else if (!strcmp(var.value, "pal"))
|
2021-06-05 15:05:07 +02:00
|
|
|
current_palette = 7;
|
2025-04-23 17:19:41 -05:00
|
|
|
else if (!strcmp(var.value, "bmf-final2"))
|
2021-06-05 15:05:07 +02:00
|
|
|
current_palette = 8;
|
2025-04-23 17:19:41 -05:00
|
|
|
else if (!strcmp(var.value, "bmf-final3"))
|
2021-06-05 15:05:07 +02:00
|
|
|
current_palette = 9;
|
2025-04-23 17:19:41 -05:00
|
|
|
else if (!strcmp(var.value, "smooth-fbx"))
|
2021-06-05 15:05:07 +02:00
|
|
|
current_palette = 10;
|
2025-04-23 17:19:41 -05:00
|
|
|
else if (!strcmp(var.value, "composite-direct-fbx"))
|
2021-06-05 15:05:07 +02:00
|
|
|
current_palette = 11;
|
2025-04-23 17:19:41 -05:00
|
|
|
else if (!strcmp(var.value, "pvm-style-d93-fbx"))
|
2021-06-05 15:05:07 +02:00
|
|
|
current_palette = 12;
|
2025-04-23 17:19:41 -05:00
|
|
|
else if (!strcmp(var.value, "ntsc-hardware-fbx"))
|
2021-06-05 15:05:07 +02:00
|
|
|
current_palette = 13;
|
2025-04-23 17:19:41 -05:00
|
|
|
else if (!strcmp(var.value, "nes-classic-fbx-fs"))
|
2021-06-05 15:05:07 +02:00
|
|
|
current_palette = 14;
|
2025-04-23 17:19:41 -05:00
|
|
|
else if (!strcmp(var.value, "nescap"))
|
2021-06-05 15:05:07 +02:00
|
|
|
current_palette = 15;
|
2025-04-23 17:19:41 -05:00
|
|
|
else if (!strcmp(var.value, "wavebeam"))
|
2022-06-09 13:58:15 +08:00
|
|
|
current_palette = 16;
|
2025-04-23 17:19:41 -05:00
|
|
|
else if (!strcmp(var.value, "digital-prime-fbx"))
|
2022-06-09 13:58:15 +08:00
|
|
|
current_palette = 17;
|
2025-04-23 17:19:41 -05:00
|
|
|
else if (!strcmp(var.value, "magnum-fbx"))
|
2022-06-09 13:58:15 +08:00
|
|
|
current_palette = 18;
|
2025-04-23 17:19:41 -05:00
|
|
|
else if (!strcmp(var.value, "smooth-v2-fbx"))
|
2022-06-09 13:58:15 +08:00
|
|
|
current_palette = 19;
|
2025-04-23 17:19:41 -05:00
|
|
|
else if (!strcmp(var.value, "nes-classic-fbx"))
|
2024-03-02 14:26:27 +01:00
|
|
|
current_palette = 20;
|
2025-04-23 17:19:41 -05:00
|
|
|
else if (!strcmp(var.value, "royaltea"))
|
2024-03-02 14:26:27 +01:00
|
|
|
current_palette = 21;
|
2025-04-23 17:19:41 -05:00
|
|
|
else if (!strcmp(var.value, "mugicha"))
|
|
|
|
|
current_palette = 22;
|
2014-03-30 22:35:00 +02:00
|
|
|
|
|
|
|
|
if (current_palette != orig_value)
|
2020-05-05 09:07:55 +08:00
|
|
|
{
|
|
|
|
|
audio_video_updated = 1;
|
|
|
|
|
ResetPalette();
|
2022-06-09 13:58:15 +08:00
|
|
|
}
|
2014-03-30 22:35:00 +02:00
|
|
|
}
|
2016-02-22 18:36:09 +01:00
|
|
|
|
2018-04-14 19:35:51 +02:00
|
|
|
var.key = "fceumm_up_down_allowed";
|
|
|
|
|
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
2020-05-05 13:35:30 +08:00
|
|
|
nes_input.up_down_allowed = (!strcmp(var.value, "enabled")) ? true : false;
|
2018-04-14 19:35:51 +02:00
|
|
|
else
|
2020-05-05 13:35:30 +08:00
|
|
|
nes_input.up_down_allowed = false;
|
2018-04-14 19:35:51 +02:00
|
|
|
|
2016-02-22 18:36:09 +01:00
|
|
|
var.key = "fceumm_nospritelimit";
|
2015-09-13 10:44:05 -05:00
|
|
|
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
|
|
|
{
|
2016-02-22 18:36:09 +01:00
|
|
|
int no_sprite_limit = (!strcmp(var.value, "enabled")) ? 1 : 0;
|
2015-09-13 10:44:05 -05:00
|
|
|
FCEUI_DisableSpriteLimitation(no_sprite_limit);
|
|
|
|
|
}
|
2016-02-22 18:36:09 +01:00
|
|
|
|
|
|
|
|
var.key = "fceumm_overclocking";
|
|
|
|
|
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
|
|
|
{
|
|
|
|
|
bool do_reinit = false;
|
|
|
|
|
|
|
|
|
|
if (!strcmp(var.value, "disabled")
|
2021-06-05 15:05:07 +02:00
|
|
|
&& overclock_enabled != 0)
|
2016-02-22 18:36:09 +01:00
|
|
|
{
|
2021-06-05 15:05:07 +02:00
|
|
|
skip_7bit_overclocking = 1;
|
|
|
|
|
extrascanlines = 0;
|
|
|
|
|
vblankscanlines = 0;
|
|
|
|
|
overclock_enabled = 0;
|
2016-02-22 18:36:09 +01:00
|
|
|
do_reinit = true;
|
|
|
|
|
}
|
2017-08-24 21:14:25 +08:00
|
|
|
else if (!strcmp(var.value, "2x-Postrender"))
|
2016-02-22 18:36:09 +01:00
|
|
|
{
|
2021-06-05 15:05:07 +02:00
|
|
|
skip_7bit_overclocking = 1;
|
|
|
|
|
extrascanlines = 266;
|
|
|
|
|
vblankscanlines = 0;
|
|
|
|
|
overclock_enabled = 1;
|
2017-08-24 20:58:04 +08:00
|
|
|
do_reinit = true;
|
|
|
|
|
}
|
2017-08-24 21:14:25 +08:00
|
|
|
else if (!strcmp(var.value, "2x-VBlank"))
|
2017-08-24 20:58:04 +08:00
|
|
|
{
|
2021-06-05 15:05:07 +02:00
|
|
|
skip_7bit_overclocking = 1;
|
|
|
|
|
extrascanlines = 0;
|
|
|
|
|
vblankscanlines = 266;
|
|
|
|
|
overclock_enabled = 1;
|
2016-02-22 18:36:09 +01:00
|
|
|
do_reinit = true;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-05 15:05:07 +02:00
|
|
|
normal_scanlines = dendy ? 290 : 240;
|
|
|
|
|
totalscanlines = normal_scanlines + (overclock_enabled ? extrascanlines : 0);
|
2020-02-07 14:53:14 +08:00
|
|
|
|
2017-01-02 03:36:39 +01:00
|
|
|
if (do_reinit && startup)
|
2016-02-22 18:36:09 +01:00
|
|
|
{
|
|
|
|
|
FCEU_KillVirtualVideo();
|
|
|
|
|
FCEU_InitVirtualVideo();
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-05-21 00:32:55 -05:00
|
|
|
|
2017-09-16 17:32:05 +08:00
|
|
|
var.key = "fceumm_zapper_mode";
|
|
|
|
|
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
|
|
|
{
|
2022-08-27 23:01:26 -05:00
|
|
|
if (!strcmp(var.value, "mouse")) {
|
|
|
|
|
zappermode = RetroMouse;
|
|
|
|
|
switchZapper = 0;
|
|
|
|
|
}
|
|
|
|
|
else if (!strcmp(var.value, "touchscreen")) {
|
|
|
|
|
zappermode = RetroPointer;
|
|
|
|
|
switchZapper = 0;
|
|
|
|
|
}
|
|
|
|
|
else if (!strcmp(var.value, "stlightgun")) {
|
|
|
|
|
zappermode = RetroSTLightgun;
|
|
|
|
|
switchZapper = 1;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
zappermode = RetroCLightgun; /*default setting*/
|
|
|
|
|
switchZapper = 0;
|
|
|
|
|
}
|
2017-09-16 17:32:05 +08:00
|
|
|
}
|
|
|
|
|
|
2023-03-11 11:40:58 -06:00
|
|
|
var.key = "fceumm_arkanoid_mode";
|
|
|
|
|
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
|
|
|
{
|
|
|
|
|
if (!strcmp(var.value, "touchscreen")) {
|
|
|
|
|
arkanoidmode = RetroArkanoidPointer;
|
|
|
|
|
}
|
|
|
|
|
else if (!strcmp(var.value, "abs_mouse")) {
|
|
|
|
|
arkanoidmode = RetroArkanoidAbsMouse;
|
|
|
|
|
}
|
|
|
|
|
else if (!strcmp(var.value, "stelladaptor")) {
|
|
|
|
|
arkanoidmode = RetroArkanoidStelladaptor;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
arkanoidmode = RetroArkanoidMouse; /*default setting*/
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-05 15:42:26 -05:00
|
|
|
var.key = "fceumm_zapper_tolerance";
|
|
|
|
|
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
|
|
|
{
|
|
|
|
|
FCEU_ZapperSetTolerance(atoi(var.value));
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-11 11:40:58 -06:00
|
|
|
var.key = "fceumm_mouse_sensitivity";
|
|
|
|
|
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
|
|
|
{
|
|
|
|
|
mouseSensitivity = atoi(var.value);
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-16 17:32:05 +08:00
|
|
|
var.key = "fceumm_show_crosshair";
|
|
|
|
|
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
|
|
|
{
|
2021-06-05 15:05:07 +02:00
|
|
|
if (!strcmp(var.value, "enabled")) show_crosshair = 1;
|
|
|
|
|
else if (!strcmp(var.value, "disabled")) show_crosshair = 0;
|
2017-09-16 17:32:05 +08:00
|
|
|
}
|
|
|
|
|
|
2022-08-27 23:01:26 -05:00
|
|
|
var.key = "fceumm_zapper_trigger";
|
|
|
|
|
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
|
|
|
{
|
|
|
|
|
if (!strcmp(var.value, "enabled")) zapper_trigger_invert_option = 1;
|
|
|
|
|
else if (!strcmp(var.value, "disabled")) zapper_trigger_invert_option = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var.key = "fceumm_zapper_sensor";
|
|
|
|
|
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
|
|
|
{
|
|
|
|
|
if (!strcmp(var.value, "enabled")) zapper_sensor_invert_option = 1;
|
|
|
|
|
else if (!strcmp(var.value, "disabled")) zapper_sensor_invert_option = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-02 02:40:28 +08:00
|
|
|
#ifdef PSP
|
2016-05-21 00:32:55 -05:00
|
|
|
var.key = "fceumm_overscan";
|
|
|
|
|
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
|
|
|
{
|
2020-10-02 19:24:00 +08:00
|
|
|
bool newval = (!strcmp(var.value, "enabled"));
|
|
|
|
|
if (newval != crop_overscan)
|
2016-08-11 20:16:13 -05:00
|
|
|
{
|
2023-03-10 21:47:35 -05:00
|
|
|
crop_overscan_h_left = (newval == true ? 8 : 0);
|
|
|
|
|
crop_overscan_h_right = (newval == true ? 8 : 0);
|
|
|
|
|
crop_overscan_v_top = (newval == true ? 8 : 0);
|
|
|
|
|
crop_overscan_v_bottom = (newval == true ? 8 : 0);
|
|
|
|
|
|
2020-10-02 19:24:00 +08:00
|
|
|
crop_overscan = newval;
|
2020-05-05 09:24:24 +08:00
|
|
|
audio_video_updated = 1;
|
2016-08-11 20:16:13 -05:00
|
|
|
}
|
2016-05-21 00:32:55 -05:00
|
|
|
}
|
2017-09-02 02:40:28 +08:00
|
|
|
#else
|
2023-03-10 21:47:35 -05:00
|
|
|
var.key = "fceumm_overscan_h_left";
|
2017-09-02 02:40:28 +08:00
|
|
|
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
|
|
|
{
|
2023-03-10 21:47:35 -05:00
|
|
|
int newval = atoi(var.value);
|
|
|
|
|
if (newval != crop_overscan_h_left)
|
2017-09-02 02:40:28 +08:00
|
|
|
{
|
2023-03-10 21:47:35 -05:00
|
|
|
crop_overscan_h_left = newval;
|
2020-05-05 09:07:55 +08:00
|
|
|
audio_video_updated = 1;
|
2017-09-02 02:40:28 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-10 21:47:35 -05:00
|
|
|
var.key = "fceumm_overscan_h_right";
|
2017-09-02 02:40:28 +08:00
|
|
|
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
|
|
|
{
|
2023-03-10 21:47:35 -05:00
|
|
|
int newval = atoi(var.value);
|
|
|
|
|
if (newval != crop_overscan_h_right)
|
|
|
|
|
{
|
|
|
|
|
crop_overscan_h_right = newval;
|
|
|
|
|
audio_video_updated = 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var.key = "fceumm_overscan_v_top";
|
|
|
|
|
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
|
|
|
{
|
|
|
|
|
int newval = atoi(var.value);
|
|
|
|
|
if (newval != crop_overscan_v_top)
|
2017-09-02 02:40:28 +08:00
|
|
|
{
|
2023-03-10 21:47:35 -05:00
|
|
|
crop_overscan_v_top = newval;
|
|
|
|
|
audio_video_updated = 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var.key = "fceumm_overscan_v_bottom";
|
|
|
|
|
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
|
|
|
{
|
|
|
|
|
int newval = atoi(var.value);
|
|
|
|
|
if (newval != crop_overscan_v_bottom)
|
|
|
|
|
{
|
|
|
|
|
crop_overscan_v_bottom = newval;
|
2020-05-05 09:07:55 +08:00
|
|
|
audio_video_updated = 1;
|
2017-09-02 02:40:28 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
2021-10-21 15:52:40 +01:00
|
|
|
|
2020-05-05 09:07:55 +08:00
|
|
|
var.key = "fceumm_aspect";
|
|
|
|
|
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
|
|
|
{
|
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
|
|
|
int oldval = aspect_ratio_par;
|
2021-04-28 18:15:10 -05:00
|
|
|
if (!strcmp(var.value, "8:7 PAR")) {
|
|
|
|
|
aspect_ratio_par = 1;
|
|
|
|
|
} else if (!strcmp(var.value, "4:3")) {
|
|
|
|
|
aspect_ratio_par = 2;
|
|
|
|
|
} else if (!strcmp(var.value, "PP")) {
|
|
|
|
|
aspect_ratio_par = 3;
|
2020-05-05 09:07:55 +08:00
|
|
|
}
|
2021-04-28 18:15:10 -05:00
|
|
|
if (aspect_ratio_par != oldval)
|
|
|
|
|
audio_video_updated = 1;
|
2020-05-05 09:07:55 +08:00
|
|
|
}
|
2017-09-02 02:40:28 +08:00
|
|
|
|
2017-02-16 20:30:24 -06:00
|
|
|
var.key = "fceumm_turbo_enable";
|
|
|
|
|
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
|
|
|
{
|
2020-05-05 13:35:30 +08:00
|
|
|
nes_input.turbo_enabler[0] = 0;
|
|
|
|
|
nes_input.turbo_enabler[1] = 0;
|
2017-11-12 15:38:04 +08:00
|
|
|
|
|
|
|
|
if (!strcmp(var.value, "Player 1"))
|
2020-05-05 13:35:30 +08:00
|
|
|
nes_input.turbo_enabler[0] = 1;
|
2017-02-16 20:30:24 -06:00
|
|
|
else if (!strcmp(var.value, "Player 2"))
|
2020-05-05 13:35:30 +08:00
|
|
|
nes_input.turbo_enabler[1] = 1;
|
2017-02-16 20:30:24 -06:00
|
|
|
else if (!strcmp(var.value, "Both"))
|
|
|
|
|
{
|
2020-05-05 13:35:30 +08:00
|
|
|
nes_input.turbo_enabler[0] = 1;
|
|
|
|
|
nes_input.turbo_enabler[1] = 1;
|
2017-02-16 20:30:24 -06:00
|
|
|
}
|
|
|
|
|
}
|
2017-02-19 14:56:53 +08:00
|
|
|
|
2017-02-17 15:30:32 -06:00
|
|
|
var.key = "fceumm_turbo_delay";
|
|
|
|
|
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
2020-05-05 13:35:30 +08:00
|
|
|
nes_input.turbo_delay = atoi(var.value);
|
2017-08-31 19:33:22 +08:00
|
|
|
|
2017-03-11 03:04:38 +08:00
|
|
|
var.key = "fceumm_region";
|
2021-10-21 15:52:40 +01:00
|
|
|
|
2017-03-11 03:04:38 +08:00
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
|
|
|
{
|
2020-02-07 14:53:14 +08:00
|
|
|
unsigned oldval = opt_region;
|
2017-03-11 03:04:38 +08:00
|
|
|
if (!strcmp(var.value, "Auto"))
|
2020-02-07 14:53:14 +08:00
|
|
|
opt_region = 0;
|
2017-03-11 03:04:38 +08:00
|
|
|
else if (!strcmp(var.value, "NTSC"))
|
2020-02-07 14:53:14 +08:00
|
|
|
opt_region = 1;
|
2017-03-11 03:04:38 +08:00
|
|
|
else if (!strcmp(var.value, "PAL"))
|
2020-02-07 14:53:14 +08:00
|
|
|
opt_region = 2;
|
2017-03-11 03:04:38 +08:00
|
|
|
else if (!strcmp(var.value, "Dendy"))
|
2020-02-07 14:53:14 +08:00
|
|
|
opt_region = 3;
|
|
|
|
|
if (opt_region != oldval)
|
2020-05-05 09:24:24 +08:00
|
|
|
{
|
2020-02-07 14:53:14 +08:00
|
|
|
FCEUD_RegionOverride(opt_region);
|
2020-05-05 09:24:24 +08:00
|
|
|
audio_video_updated = 2;
|
|
|
|
|
}
|
2017-03-11 03:04:38 +08:00
|
|
|
}
|
2017-10-07 13:35:00 +08:00
|
|
|
|
2017-09-09 14:54:02 +08:00
|
|
|
var.key = "fceumm_sndquality";
|
|
|
|
|
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
|
|
|
{
|
|
|
|
|
unsigned oldval = sndquality;
|
|
|
|
|
if (!strcmp(var.value, "Low"))
|
|
|
|
|
sndquality = 0;
|
|
|
|
|
else if (!strcmp(var.value, "High"))
|
|
|
|
|
sndquality = 1;
|
|
|
|
|
else if (!strcmp(var.value, "Very High"))
|
|
|
|
|
sndquality = 2;
|
|
|
|
|
if (sndquality != oldval)
|
|
|
|
|
FCEUI_SetSoundQuality(sndquality);
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-05 13:45:20 +01:00
|
|
|
var.key = "fceumm_sndlowpass";
|
|
|
|
|
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
|
|
|
{
|
|
|
|
|
int lowpass = (!strcmp(var.value, "enabled")) ? 1 : 0;
|
|
|
|
|
FCEUI_SetLowPass(lowpass);
|
|
|
|
|
}
|
|
|
|
|
|
sound: add core option to mute ultrasonic triangle in HQ (fixes #638)
Castlevania II, Jackal, and several other titles repeatedly drop the
triangle channel into a period range that produces only ultrasonic
output (raw period <= 3, > ~12 kHz at NTSC). The hardware itself
plays these tones, but the DAC reconstruction filter in HQ mode
folds the ultrasonic energy back into the audible range as popping.
The LQ tri/noise/PCM mixer (RDoTriangleNoisePCMLQ) already gates the
triangle channel with `freq[0] <= 4` (i.e. raw period <= 3) for
exactly this reason -- the popping has been absent in LQ since
forever. The HQ path (RDoTriangle) never had the equivalent gate,
so users on Sound Quality = High/Very High hear the popping that LQ
users do not.
Mirror the gate in RDoTriangle, controlled by a new core option
`fceumm_removetrianglenoise` (default disabled). When the option
is off the codegen is identical to before -- gcc folds the
`FSettings.RemoveTriangleNoise && (...)` check via short-circuit
evaluation, the per-sample inner loop is untouched. When on, HQ
behaves like LQ for the triangle ultrasonic case and the popping
disappears.
LQ is intentionally left untouched -- it has always silenced these
tones unconditionally and changing that would be an audible behavior
shift for existing LQ users. The new option is HQ-only.
Backport reference: negativeExponent's libretro-fceumm_next fork
(commit afaa8f5) added the same option, gating both LQ and HQ paths
on it. This patch is the more conservative subset: only adds the
HQ gate, leaves LQ at its current (always-silencing) behaviour.
Build / codegen verified:
- sound.c -O2 / -O3 clean, +15 lines in RDoTriangle (.text),
inner per-sample for-loop unchanged.
- libretro.c clean (options_list[] widened from [25] to [32] to
fit the new key name).
- aarch64 cross-build clean.
2026-06-14 15:02:53 +00:00
|
|
|
var.key = "fceumm_removetrianglenoise";
|
|
|
|
|
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
|
|
|
{
|
|
|
|
|
int newval = (!strcmp(var.value, "enabled")) ? 1 : 0;
|
|
|
|
|
FCEUI_RemoveTriangleNoise(newval);
|
|
|
|
|
}
|
|
|
|
|
|
sound: add core option to reduce DMC channel popping (fixes #638)
Games like Castlevania II: Simon's Quest drive the DMC DAC directly
via $4011 writes to produce sample-style audio (separate from the
DPCM bit-stream path). The DAC jumps straight to the newly-written
7-bit value on every write, and those abrupt steps are audible as
clicks/pops when the writes form a low-frequency envelope.
Backport reference: negativeExponent's libretro-fceumm_next applies
a one-step midpoint smoother at the $4011 write site - take the
old DAC value and the new requested value, and store only the
average (rounded toward the new value). Successive writes still
converge on the target, so percussive samples retain their attack;
they just lose half of the per-write step amplitude, which is the
component that produces the click.
Backported as-is, gated on a new core option
`fceumm_reducedmcpopping` (default disabled, so the DAC
trajectory is bit-exact with the original code when the option is
not set). Verified to match _next's algebra exactly across the
boundary cases (full-range jumps in both directions, single-step
deltas, idle no-ops).
The DPCM bit-decode playback path (the +/-2 per bit RawDALatch
update further down in the DMC channel inner loop) is intentionally
NOT touched - that path is the well-behaved bit-stream output, no
popping, and smoothing it would attenuate normal samples.
This addresses the second half of issue #638 (the first being the
triangle ultrasonic gate, already landed as d3cd26e). The
follow-up comment on that issue asked specifically for DMC popping
reduction to land in libretro-fceumm since _next has no Android
port.
Build verified: sound.c -O2 / -O3 / aarch64 -O2 clean, libretro.c
clean.
2026-06-14 15:09:04 +00:00
|
|
|
var.key = "fceumm_reducedmcpopping";
|
|
|
|
|
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
|
|
|
{
|
|
|
|
|
int newval = (!strcmp(var.value, "enabled")) ? 1 : 0;
|
|
|
|
|
FCEUI_ReduceDmcPopping(newval);
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-06 13:36:22 +01:00
|
|
|
var.key = "fceumm_sndstereodelay";
|
|
|
|
|
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
|
|
|
{
|
|
|
|
|
enum stereo_filter_type filter_type = STEREO_FILTER_NULL;
|
|
|
|
|
float filter_delay_ms = STEREO_FILTER_DELAY_MS_DEFAULT;
|
|
|
|
|
|
|
|
|
|
if (strcmp(var.value, "disabled") &&
|
|
|
|
|
(strlen(var.value) > 1))
|
|
|
|
|
{
|
|
|
|
|
char value_str[3];
|
|
|
|
|
|
|
|
|
|
value_str[0] = var.value[0];
|
|
|
|
|
value_str[1] = var.value[1];
|
|
|
|
|
value_str[2] = '\0';
|
|
|
|
|
|
|
|
|
|
filter_type = STEREO_FILTER_DELAY;
|
|
|
|
|
filter_delay_ms = (float)atoi(var.value);
|
|
|
|
|
|
|
|
|
|
filter_delay_ms = (filter_delay_ms < 1.0f) ?
|
|
|
|
|
1.0f : filter_delay_ms;
|
|
|
|
|
filter_delay_ms = (filter_delay_ms > 32.0f) ?
|
|
|
|
|
32.0f : filter_delay_ms;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((filter_type != current_stereo_filter) ||
|
|
|
|
|
((filter_type == STEREO_FILTER_DELAY) &&
|
|
|
|
|
(filter_delay_ms != stereo_filter_delay_ms)))
|
|
|
|
|
{
|
|
|
|
|
current_stereo_filter = filter_type;
|
|
|
|
|
stereo_filter_delay_ms = filter_delay_ms;
|
|
|
|
|
stereo_filter_updated = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((stereo_filter_updated ||
|
|
|
|
|
(audio_video_updated == 2)) && !startup)
|
|
|
|
|
stereo_filter_init();
|
|
|
|
|
|
2017-09-09 15:57:12 +02:00
|
|
|
var.key = "fceumm_sndvolume";
|
|
|
|
|
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
|
|
|
{
|
2019-07-20 00:12:02 +08:00
|
|
|
int val = (int)(atof(var.value) * 25.6);
|
|
|
|
|
sndvolume = val;
|
2017-09-09 15:57:12 +02:00
|
|
|
FCEUD_SoundToggle();
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-05 09:07:55 +08:00
|
|
|
if (audio_video_updated && !startup)
|
2016-08-11 20:16:13 -05:00
|
|
|
{
|
2020-05-05 09:07:55 +08:00
|
|
|
struct retro_system_av_info av_info;
|
2016-08-11 20:16:13 -05:00
|
|
|
retro_get_system_av_info(&av_info);
|
2022-06-09 13:58:15 +08:00
|
|
|
if (audio_video_updated == 2)
|
2020-05-05 09:07:55 +08:00
|
|
|
environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &av_info);
|
|
|
|
|
else
|
|
|
|
|
environ_cb(RETRO_ENVIRONMENT_SET_GEOMETRY, &av_info);
|
2016-08-11 20:16:13 -05:00
|
|
|
}
|
2017-10-05 14:43:20 +08:00
|
|
|
|
|
|
|
|
var.key = "fceumm_swapduty";
|
|
|
|
|
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
|
|
|
{
|
|
|
|
|
bool newval = (!strcmp(var.value, "enabled"));
|
2021-06-05 15:05:07 +02:00
|
|
|
if (newval != swapDuty)
|
|
|
|
|
swapDuty = newval;
|
2017-10-05 14:43:20 +08:00
|
|
|
}
|
2018-11-30 09:22:36 +08:00
|
|
|
|
|
|
|
|
var.key = key;
|
|
|
|
|
|
2020-02-15 11:23:07 +08:00
|
|
|
enable_apu = 0xff;
|
2018-11-30 09:22:36 +08:00
|
|
|
|
core: strict-aliasing fixes, bounded string ops, drop FCEUDEF_DEBUGGER
Three follow-up cleanups on top of the determinism / typedef / LE
work in f2a5be3.
================================================================
Pass 1: fix strict-aliasing UB in PPU sprite buffer and FCEU_dwmemset
================================================================
Three sites were doing type-punning through uint32_t* casts that
GCC flags with -Wstrict-aliasing=2:
1. ppu.c FetchSpriteData (two near-identical sites). After populating
a local SPRB struct (4 bytes: ca[2], atr, x), the prior code did
'*(uint32_t*)&SPRBUF[ns << 2] = *(uint32_t*)&dst' to store the
struct as one 32-bit word. This violates strict-aliasing (uint8_t
buffer accessed as uint32_t, struct accessed as uint32_t) and
silently assumes 4-byte alignment of SPRBUF + (ns << 2). Use
memcpy instead - GCC and Clang lower it to the same single
32-bit store.
2. fceu-memory.h FCEU_dwmemset macro. Same pattern every iteration:
'*(uint32_t*)& (d)[_x] = c'. Replaced with memcpy(... &v, 4) in
the macro body. Same generated code, now alias- and align-clean.
Added #include <string.h> to fceu-memory.h itself (4 callers
relied on transitive includes for memcpy).
================================================================
Pass 2: bounded string operations
================================================================
Sweep across the core and libretro driver to replace every unbounded
string function with its size-aware counterpart:
strcpy -> strlcpy (with explicit destination size)
strncpy -> strlcpy (silent truncation -> guaranteed NUL termination
and known-size semantics; behaviourally identical
at every call site here because callers either
pre-zeroed the buffer or treated truncation as
a guaranteed terminator)
strcat -> strlcpy at offset (track 'pos' across appends to avoid
rescanning the buffer with strlen and to keep
the bound explicit at every step)
sprintf -> snprintf (with sizeof(buf) as the bound)
The compat/strl.h header from libretro-common is on the include path
for every translation unit; the libretro driver files were already
using strlcpy via stdstring.h transitive include. Core files (ines.c,
unif.c, cheat.c, nsf.c, state.c, general.c) gain explicit
'#include <compat/strl.h>'.
Per-file changes:
general.c
- FCEUI_SetBaseDirectory: strncpy + manual NUL -> strlcpy
- FCEU_MakeFName: malloc(strlen+1) + strcpy -> sized strlcpy,
malloc NULL-check added (was unchecked).
ines.c
- 'gigastr' iNES-header-warning building.
Pre-existing bug fixed: every sprintf(gigastr + gigastr_len, ...)
wrote at the SAME offset captured once at the top of the block,
so when multiple 'tofix' bits were set, only the LAST fragment
survived (each sprintf clobbered the previous). Replaced with a
running 'pos' offset across snprintf and strlcpy-at-offset calls.
- 6 sprintf -> snprintf, 3 strcat -> strlcpy at offset, 1 strcpy
-> strlcpy.
unif.c
- GameInfo->name allocation: strcpy -> strlcpy with explicit size.
cheat.c
- FCEUI_AddCheat: malloc + strcpy -> sized strlcpy.
- FCEUI_SetCheat: realloc + strcpy -> sized strlcpy.
nsf.c
- FCEUI_NSFGetInfo: 3x strncpy -> strlcpy.
- Visualiser snbuf sprintf -> snprintf (the 16-byte buffer was
technically overflowable for very large song counts).
state.c
- AddExState description copy: strncpy -> strlcpy. Preserves
4-byte SFORMAT-tag semantics.
fds.c
- Disk-tag formatting: sprintf "DDT%d" -> snprintf.
boards/__serial.c
- 2 sprintf -> snprintf (Windows-only SerialOpen path).
drivers/libretro/libretro.c
- retro_cheat_set: sprintf "N/A" + strcpy literal -> strlcpy.
drivers/libretro/libretro_dipswitch.c
- VS-DIP key building: sprintf -> snprintf.
- core_key calloc + strcpy -> sized strlcpy. calloc NULL-check
added (was unchecked - dereferencing NULL on OOM).
drivers/libretro/libretro_core_options.h
- values_buf assembly: single strcpy + N strcat replaced with
strlcpy + strlcpy-at-offset using a running 'pos'. Each step
bounded by remaining buffer space.
================================================================
Pass 3: remove FCEUDEF_DEBUGGER and the unused debugger scaffolding
================================================================
The FCEUDEF_DEBUGGER macro was never defined for libretro builds, so
every block guarded by it was dead code. Removing the macro takes
out:
- src/debug.c (FCEUI_DumpMem, FCEUI_DumpVid, FCEUI_LoadMem,
FCEUI_Disassemble, FCEUI_MemDump, FCEUI_MemSafePeek,
FCEUI_MemPoke, breakpoint set/get/list, FCEUI_SetCPUCallback).
Not in Makefile.common SOURCES_C, never compiled. None of the
declared functions had any caller in any compiled .c file.
- src/debug.h (declarations for the above).
- x6502.c X6502_RunDebug. The dual-implementation pattern with a
function pointer that switched between RunNormal and RunDebug
is now a single direct X6502_Run.
- x6502.c X6502_Debug, FCEUI_NMI, FCEUI_IRQ, FCEUI_GetIVectors.
Set/get debugger hooks. No callers.
- x6502.c RdMemHook, WrMemHook, XSave, debugmode. Hook scaffolding
used only by RunDebug.
- x6502struct.h X6502 fields preexec, CPUHook, ReadHook, WriteHook.
Set only inside RunDebug, never read elsewhere.
- fceuindbg variable. Set to 1 only in FCEUI_GetIVectors and
X6502_RunDebug (both removed); was always 0 elsewhere, so every
'if (!fceuindbg)' check was a no-op. Removed both the variable
(defined in ppu.c, declared in fceu.h) and every check site
across sound.c, input.c, fds.c, nsf.c, ppu.c, mmc5.c, n106.c,
BMW8544.c, and the input drivers (arkanoid, mahjong, mouse,
pec586kb, powerpad, zapper).
- All FCEUDEF_DEBUGGER conditional declarations from driver.h.
The 'if (!fceuindbg)' checks gated side-effects (joypad-bit-counter
increments, PPU register reads, etc.) so a future debugger could peek
at memory without advancing the emulator state. With the debugger
gone, those side-effects are now unconditional - which is what they
should have been anyway in a libretro build.
If a future developer needs a debugger they should resurrect this
out of git history into a separate debugger-frontend project rather
than re-introducing a build-time toggle that nothing in the libretro
build can ever exercise.
================================================================
Build status
================================================================
Build clean on `make platform=unix` with zero errors and zero
warnings. Output binary 184 bytes smaller than upstream f2a5be3
(4,388,840 -> 4,388,656). 31 files changed, 127 insertions, 863
deletions.
2026-05-04 03:23:56 +02:00
|
|
|
strlcpy(key, "fceumm_apu_x", sizeof(key));
|
2018-11-30 09:22:36 +08:00
|
|
|
for (i = 0; i < 5; i++)
|
|
|
|
|
{
|
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
|
|
|
/* Replace the trailing 'x' with '1'..'5'. The literal length is
|
|
|
|
|
* known at compile time so use sizeof-1 to avoid the strlen call
|
|
|
|
|
* inside the loop. */
|
|
|
|
|
key[sizeof("fceumm_apu_") - 1] = '1' + i;
|
2018-11-30 09:22:36 +08:00
|
|
|
var.value = NULL;
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && !strcmp(var.value, "disabled"))
|
2020-02-15 11:23:07 +08:00
|
|
|
enable_apu &= ~(1 << i);
|
2018-11-30 09:22:36 +08:00
|
|
|
}
|
2020-02-15 11:23:07 +08:00
|
|
|
set_apu_channels(enable_apu);
|
2018-11-30 09:22:36 +08:00
|
|
|
|
2019-05-11 23:11:55 +08:00
|
|
|
update_dipswitch();
|
2020-02-15 11:23:07 +08:00
|
|
|
|
2021-10-19 17:33:56 +01:00
|
|
|
update_option_visibility();
|
2014-03-30 22:35:00 +02:00
|
|
|
}
|
|
|
|
|
|
core: stride-aware savestate, iNES2 helpers, -Wundef, -Wmissing-prototypes
Audit pass 5 - five distinct cleanups bundled into one omnibus.
1. Element-stride byte-swapping for savestate fields (state.h, state.c,
fceu-endian.{h,c})
The SFORMAT 's' field was previously {bit 31 = RLSB, bits 0..30 =
byte size}. RLSB triggers FlipByteOrder() on MSB hosts, which
reverses the entire entry buffer end-to-end. That is correct for a
single primitive (size <= 8 bytes) but wrong for an array of
multi-byte primitives - reversing the whole buffer would swap
element 0 with element N-1 and reverse their bytes too, scrambling
the data.
The previous workaround was either splitting an N-element array
into N separate single-primitive entries with distinct chunk IDs
(n106 PlayIndex split into IDX0..IDX7) or skipping the entry
entirely on big-endian hosts (the GEKKO #ifndef in vrc6.c / vrc7.c).
Both approaches mean BE saves are not portable to LE and vice
versa, and force the same workaround at every new array site.
This pass adds proper stride support:
* SFORMAT 's' encoding is now {bit 31 = RLSB, bits 24..30 =
stride in bytes (0 = legacy/unset), bits 0..23 = byte size}.
16 MiB max size, well above any actual savestate field.
* FCEUSTATE_RLSB_ARRAY(stride) macro for the new pattern.
* FlipByteOrderStrided() byte-swaps each element of an array
independently. Round-trip identity verified: [01 00 00 00 ...]
-> [00 00 00 01 ...] -> [01 00 00 00 ...].
* state.c's SubWrite / ReadStateChunk / CheckS use new helpers
sf_size() / sf_stride() / sf_flip() that mask the size out of
the new bit layout and dispatch to the strided variant when
stride < size.
Backwards compatible: legacy single-primitive entries (size == 1,
2, 4, 8) leave the stride bits at zero, which sf_stride() reads
as "stride equals size" and falls through to FlipByteOrder() as
before. No on-disk format change. Existing single-primitive RLSB
sites are unchanged.
The infrastructure is now in place so any future SFORMAT entry
that is an array of multi-byte primitives can be expressed as a
single entry (e.g. "{ buf, sizeof(buf) | FCEUSTATE_RLSB_ARRAY(4),
"BUF." }") without splitting or skipping. The existing PlayIndex
split and GEKKO #ifndefs are intentionally left untouched -
migrating them would alter the on-disk savestate format and is a
separate decision.
2. iNES1-vs-iNES2 sizing helpers (cart.h)
Twelve sites across the codebase encoded the same conditional:
info->iNES2 ? (info->PRGRamSize + info->PRGRamSaveSize) : default
Sometimes for PRGRAM, sometimes for CHRRAM, sometimes in bytes,
sometimes after dividing by 1024. The pattern is verbose and easy
to write inconsistently.
Added two inline helpers in cart.h:
- CartInfo_PRGRAM_bytes(info, default_bytes)
- CartInfo_CHRRAM_bytes(info, default_bytes)
Migrated 9 of the 13 sites: cartram.c (2), 162.c, 163.c, 134.c,
399.c, 478.c, 480.c, 484.c. The remaining 4 are non-helper-fitting
variants (164.c special masking, 2 cartram SaveGameLen sites with
different fallback semantics, mmc3.c Boogerman submapper detection).
3. -Wundef enabled permanently (Makefile.libretro)
Zero warnings out of the box - no #if-on-undefined-macro footguns
in the codebase. Now part of WARNING_DEFINES alongside the existing
-Wsign-compare.
4. -Wmissing-prototypes enabled permanently (Makefile.libretro)
Started at 198 warnings, cleared all of them:
* Mass-static-ified ~96 functions across 75 files that were
defined non-static but only used within their own translation
unit. (See static_prototype_fixer.py in the development notes.)
* K&R-style empty-parens prototypes "()" replaced with explicit
"(void)" across all asic_*.{c,h} files - GCC treats "()" as
"any args" and refuses to match it against a separate K&R
definition.
* Added missing forward declarations to public headers:
- fds.h (FDSLoad)
- nsf.h (NSFLoad)
- ines.h (iNESLoad)
- unif.h (UNIFLoad)
- latch.h (LatchHardReset, K&R fix)
- eeprom_93Cx6.h (eeprom_93Cx6_read, K&R fix)
Each header gained an "#include "file.h"" where needed.
* fds_apu.c now includes its own fds_apu.h header (was missing).
* fds_apu.h: removed unused FDSSoundRead declaration (the function
is internal-static).
* cartram.h: removed unused CartRAM_close declaration (the function
is internal-static).
* input.h: added a centralised block of FCEU_Init* prototypes
(Zapper, Mouse, Powerpad, Arkanoid, VirtualBoy, FKB, SuborKB,
PEC586KB, HS, Mahjong, FamilyTrainerA/B, OekaKids, TopRider,
BarcodeWorld, BattleBox, QuizKing, FTrainerA/B, SpaceShadow,
LCDCompZapper, ArkanoidFC) plus FCEU_ZapperSetTolerance. These
were previously declared as "extern" inside src/input.c.
* Static-ified FP_FASTAPASS callbacks in 106.c, 65.c, 67.c,
asic_h3001.c, asic_vrc3.c (those with no external callers);
left non-static for those that have header decls or are
referenced from sibling .c files (asic_mmc1, asic_vrc6,
asic_vrc7, flashrom).
* For a small set of cross-file functions where adding a header
was disproportionate to the value (MMC5_hb, NSFMMC5_Close,
GetKeyboard, FCEU_GetJoyJoy), placed a forward declaration
immediately above the definition. This satisfies
-Wmissing-prototypes (which checks for any prior declaration
in scope) without churning the public-header layout.
5. -Wshadow partial cleanup (not enabled permanently)
Fixed five real shadows that were either bugs or actively
misleading:
* src/boards/476.c: removed an inner "int i" that shadowed the
outer loop counter.
* src/boards/mmc5.c MMC5_hb: parameter "scanline" renamed to
"sl_param" (was shadowing the global "scanline").
* src/boards/n106.c DoNamcoSound: parameter "Wave" renamed to
"WaveBuf" (was shadowing the global Wave audio buffer); also
updated the forward declaration and the matching parameter
on sound.h's NeoFill function pointer typedef.
* src/boards/vrc7.c UpdateOPLNEO: same Wave -> WaveBuf rename.
* src/ntsc/nes_ntsc_impl.h: renamed an inner loop counter "n"
that shadowed an outer "n".
* src/drivers/libretro/libretro.c FCEUD_RegionOverride: local
"pal" renamed to "is_pal" (was shadowing the typedef "pal" from
palette.h).
* src/palette.c FCEUI_SetPaletteArray: parameter "pal" renamed
to "data" (same shadow); driver.h declaration updated to match.
-Wshadow itself is NOT enabled permanently because the remaining
warnings are deliberate parameter naming conventions (XBuf in
draw functions, X in cpu hooks) and third-party blargg ntsc code.
In addition, four files were touched as part of an MSVC-build fix
that came up mid-pass: src/fds.c, src/nsf.c, src/ines.c, and
src/drivers/libretro/libretro_dipswitch.c had snprintf() calls
introduced in pass 4 that fail to link on pre-MSVC2015 toolchains
when STATIC_LINKING=1 (the libretro-common compat_snprintf.c shim
isn't compiled in those configurations). Replaced each snprintf with
either sprintf-into-bounded-buffer (the format strings have known
maximum output) or strlcpy/strlcat for the dipswitch key-build case.
All output is still bounded; truncation happens via strl*'s normal
truncation semantics where applicable.
All added code is C89-clean (top-of-block declarations only, no
mixed decls, no // comments, INLINE macro from fceu-types.h instead
of bare "inline"). Builds clean under -std=gnu11 with -Wno-write-
strings -Wsign-compare -Wundef -Wmissing-prototypes; zero errors,
zero warnings.
Determinism audit (audit_determinism.py): no rand/time/long
double/threads issues introduced.
2026-05-04 04:44:52 +02:00
|
|
|
static void add_powerpad_input(unsigned port, uint32_t variant, uint32_t *ppdata) {
|
2023-01-05 02:10:01 +01:00
|
|
|
unsigned k;
|
2023-01-07 11:07:14 -06:00
|
|
|
const uint32_t* map = powerpadmap;
|
2023-01-05 02:10:01 +01:00
|
|
|
for (k = 0 ; k < 12 ; k++)
|
2022-12-31 21:27:46 -06:00
|
|
|
if (input_cb(0, RETRO_DEVICE_KEYBOARD, 0, map[k]))
|
|
|
|
|
*ppdata |= (1 << k);
|
|
|
|
|
}
|
|
|
|
|
|
core: stride-aware savestate, iNES2 helpers, -Wundef, -Wmissing-prototypes
Audit pass 5 - five distinct cleanups bundled into one omnibus.
1. Element-stride byte-swapping for savestate fields (state.h, state.c,
fceu-endian.{h,c})
The SFORMAT 's' field was previously {bit 31 = RLSB, bits 0..30 =
byte size}. RLSB triggers FlipByteOrder() on MSB hosts, which
reverses the entire entry buffer end-to-end. That is correct for a
single primitive (size <= 8 bytes) but wrong for an array of
multi-byte primitives - reversing the whole buffer would swap
element 0 with element N-1 and reverse their bytes too, scrambling
the data.
The previous workaround was either splitting an N-element array
into N separate single-primitive entries with distinct chunk IDs
(n106 PlayIndex split into IDX0..IDX7) or skipping the entry
entirely on big-endian hosts (the GEKKO #ifndef in vrc6.c / vrc7.c).
Both approaches mean BE saves are not portable to LE and vice
versa, and force the same workaround at every new array site.
This pass adds proper stride support:
* SFORMAT 's' encoding is now {bit 31 = RLSB, bits 24..30 =
stride in bytes (0 = legacy/unset), bits 0..23 = byte size}.
16 MiB max size, well above any actual savestate field.
* FCEUSTATE_RLSB_ARRAY(stride) macro for the new pattern.
* FlipByteOrderStrided() byte-swaps each element of an array
independently. Round-trip identity verified: [01 00 00 00 ...]
-> [00 00 00 01 ...] -> [01 00 00 00 ...].
* state.c's SubWrite / ReadStateChunk / CheckS use new helpers
sf_size() / sf_stride() / sf_flip() that mask the size out of
the new bit layout and dispatch to the strided variant when
stride < size.
Backwards compatible: legacy single-primitive entries (size == 1,
2, 4, 8) leave the stride bits at zero, which sf_stride() reads
as "stride equals size" and falls through to FlipByteOrder() as
before. No on-disk format change. Existing single-primitive RLSB
sites are unchanged.
The infrastructure is now in place so any future SFORMAT entry
that is an array of multi-byte primitives can be expressed as a
single entry (e.g. "{ buf, sizeof(buf) | FCEUSTATE_RLSB_ARRAY(4),
"BUF." }") without splitting or skipping. The existing PlayIndex
split and GEKKO #ifndefs are intentionally left untouched -
migrating them would alter the on-disk savestate format and is a
separate decision.
2. iNES1-vs-iNES2 sizing helpers (cart.h)
Twelve sites across the codebase encoded the same conditional:
info->iNES2 ? (info->PRGRamSize + info->PRGRamSaveSize) : default
Sometimes for PRGRAM, sometimes for CHRRAM, sometimes in bytes,
sometimes after dividing by 1024. The pattern is verbose and easy
to write inconsistently.
Added two inline helpers in cart.h:
- CartInfo_PRGRAM_bytes(info, default_bytes)
- CartInfo_CHRRAM_bytes(info, default_bytes)
Migrated 9 of the 13 sites: cartram.c (2), 162.c, 163.c, 134.c,
399.c, 478.c, 480.c, 484.c. The remaining 4 are non-helper-fitting
variants (164.c special masking, 2 cartram SaveGameLen sites with
different fallback semantics, mmc3.c Boogerman submapper detection).
3. -Wundef enabled permanently (Makefile.libretro)
Zero warnings out of the box - no #if-on-undefined-macro footguns
in the codebase. Now part of WARNING_DEFINES alongside the existing
-Wsign-compare.
4. -Wmissing-prototypes enabled permanently (Makefile.libretro)
Started at 198 warnings, cleared all of them:
* Mass-static-ified ~96 functions across 75 files that were
defined non-static but only used within their own translation
unit. (See static_prototype_fixer.py in the development notes.)
* K&R-style empty-parens prototypes "()" replaced with explicit
"(void)" across all asic_*.{c,h} files - GCC treats "()" as
"any args" and refuses to match it against a separate K&R
definition.
* Added missing forward declarations to public headers:
- fds.h (FDSLoad)
- nsf.h (NSFLoad)
- ines.h (iNESLoad)
- unif.h (UNIFLoad)
- latch.h (LatchHardReset, K&R fix)
- eeprom_93Cx6.h (eeprom_93Cx6_read, K&R fix)
Each header gained an "#include "file.h"" where needed.
* fds_apu.c now includes its own fds_apu.h header (was missing).
* fds_apu.h: removed unused FDSSoundRead declaration (the function
is internal-static).
* cartram.h: removed unused CartRAM_close declaration (the function
is internal-static).
* input.h: added a centralised block of FCEU_Init* prototypes
(Zapper, Mouse, Powerpad, Arkanoid, VirtualBoy, FKB, SuborKB,
PEC586KB, HS, Mahjong, FamilyTrainerA/B, OekaKids, TopRider,
BarcodeWorld, BattleBox, QuizKing, FTrainerA/B, SpaceShadow,
LCDCompZapper, ArkanoidFC) plus FCEU_ZapperSetTolerance. These
were previously declared as "extern" inside src/input.c.
* Static-ified FP_FASTAPASS callbacks in 106.c, 65.c, 67.c,
asic_h3001.c, asic_vrc3.c (those with no external callers);
left non-static for those that have header decls or are
referenced from sibling .c files (asic_mmc1, asic_vrc6,
asic_vrc7, flashrom).
* For a small set of cross-file functions where adding a header
was disproportionate to the value (MMC5_hb, NSFMMC5_Close,
GetKeyboard, FCEU_GetJoyJoy), placed a forward declaration
immediately above the definition. This satisfies
-Wmissing-prototypes (which checks for any prior declaration
in scope) without churning the public-header layout.
5. -Wshadow partial cleanup (not enabled permanently)
Fixed five real shadows that were either bugs or actively
misleading:
* src/boards/476.c: removed an inner "int i" that shadowed the
outer loop counter.
* src/boards/mmc5.c MMC5_hb: parameter "scanline" renamed to
"sl_param" (was shadowing the global "scanline").
* src/boards/n106.c DoNamcoSound: parameter "Wave" renamed to
"WaveBuf" (was shadowing the global Wave audio buffer); also
updated the forward declaration and the matching parameter
on sound.h's NeoFill function pointer typedef.
* src/boards/vrc7.c UpdateOPLNEO: same Wave -> WaveBuf rename.
* src/ntsc/nes_ntsc_impl.h: renamed an inner loop counter "n"
that shadowed an outer "n".
* src/drivers/libretro/libretro.c FCEUD_RegionOverride: local
"pal" renamed to "is_pal" (was shadowing the typedef "pal" from
palette.h).
* src/palette.c FCEUI_SetPaletteArray: parameter "pal" renamed
to "data" (same shadow); driver.h declaration updated to match.
-Wshadow itself is NOT enabled permanently because the remaining
warnings are deliberate parameter naming conventions (XBuf in
draw functions, X in cpu hooks) and third-party blargg ntsc code.
In addition, four files were touched as part of an MSVC-build fix
that came up mid-pass: src/fds.c, src/nsf.c, src/ines.c, and
src/drivers/libretro/libretro_dipswitch.c had snprintf() calls
introduced in pass 4 that fail to link on pre-MSVC2015 toolchains
when STATIC_LINKING=1 (the libretro-common compat_snprintf.c shim
isn't compiled in those configurations). Replaced each snprintf with
either sprintf-into-bounded-buffer (the format strings have known
maximum output) or strlcpy/strlcat for the dipswitch key-build case.
All output is still bounded; truncation happens via strl*'s normal
truncation semantics where applicable.
All added code is C89-clean (top-of-block declarations only, no
mixed decls, no // comments, INLINE macro from fceu-types.h instead
of bare "inline"). Builds clean under -std=gnu11 with -Wno-write-
strings -Wsign-compare -Wundef -Wmissing-prototypes; zero errors,
zero warnings.
Determinism audit (audit_determinism.py): no rand/time/long
double/threads issues introduced.
2026-05-04 04:44:52 +02:00
|
|
|
static void add_fkb_input(unsigned port, uint32_t variant, uint8_t *fkbkeys) {
|
2025-09-13 11:27:38 +02:00
|
|
|
unsigned k;
|
|
|
|
|
const uint32_t* map = fkbmap;
|
|
|
|
|
for (k = 0 ; k < 0x48 ; k++)
|
|
|
|
|
if (input_cb(0, RETRO_DEVICE_KEYBOARD, 0, map[k]))
|
|
|
|
|
fkbkeys[k]=1;
|
|
|
|
|
else
|
|
|
|
|
fkbkeys[k]=0;
|
|
|
|
|
}
|
|
|
|
|
|
core: stride-aware savestate, iNES2 helpers, -Wundef, -Wmissing-prototypes
Audit pass 5 - five distinct cleanups bundled into one omnibus.
1. Element-stride byte-swapping for savestate fields (state.h, state.c,
fceu-endian.{h,c})
The SFORMAT 's' field was previously {bit 31 = RLSB, bits 0..30 =
byte size}. RLSB triggers FlipByteOrder() on MSB hosts, which
reverses the entire entry buffer end-to-end. That is correct for a
single primitive (size <= 8 bytes) but wrong for an array of
multi-byte primitives - reversing the whole buffer would swap
element 0 with element N-1 and reverse their bytes too, scrambling
the data.
The previous workaround was either splitting an N-element array
into N separate single-primitive entries with distinct chunk IDs
(n106 PlayIndex split into IDX0..IDX7) or skipping the entry
entirely on big-endian hosts (the GEKKO #ifndef in vrc6.c / vrc7.c).
Both approaches mean BE saves are not portable to LE and vice
versa, and force the same workaround at every new array site.
This pass adds proper stride support:
* SFORMAT 's' encoding is now {bit 31 = RLSB, bits 24..30 =
stride in bytes (0 = legacy/unset), bits 0..23 = byte size}.
16 MiB max size, well above any actual savestate field.
* FCEUSTATE_RLSB_ARRAY(stride) macro for the new pattern.
* FlipByteOrderStrided() byte-swaps each element of an array
independently. Round-trip identity verified: [01 00 00 00 ...]
-> [00 00 00 01 ...] -> [01 00 00 00 ...].
* state.c's SubWrite / ReadStateChunk / CheckS use new helpers
sf_size() / sf_stride() / sf_flip() that mask the size out of
the new bit layout and dispatch to the strided variant when
stride < size.
Backwards compatible: legacy single-primitive entries (size == 1,
2, 4, 8) leave the stride bits at zero, which sf_stride() reads
as "stride equals size" and falls through to FlipByteOrder() as
before. No on-disk format change. Existing single-primitive RLSB
sites are unchanged.
The infrastructure is now in place so any future SFORMAT entry
that is an array of multi-byte primitives can be expressed as a
single entry (e.g. "{ buf, sizeof(buf) | FCEUSTATE_RLSB_ARRAY(4),
"BUF." }") without splitting or skipping. The existing PlayIndex
split and GEKKO #ifndefs are intentionally left untouched -
migrating them would alter the on-disk savestate format and is a
separate decision.
2. iNES1-vs-iNES2 sizing helpers (cart.h)
Twelve sites across the codebase encoded the same conditional:
info->iNES2 ? (info->PRGRamSize + info->PRGRamSaveSize) : default
Sometimes for PRGRAM, sometimes for CHRRAM, sometimes in bytes,
sometimes after dividing by 1024. The pattern is verbose and easy
to write inconsistently.
Added two inline helpers in cart.h:
- CartInfo_PRGRAM_bytes(info, default_bytes)
- CartInfo_CHRRAM_bytes(info, default_bytes)
Migrated 9 of the 13 sites: cartram.c (2), 162.c, 163.c, 134.c,
399.c, 478.c, 480.c, 484.c. The remaining 4 are non-helper-fitting
variants (164.c special masking, 2 cartram SaveGameLen sites with
different fallback semantics, mmc3.c Boogerman submapper detection).
3. -Wundef enabled permanently (Makefile.libretro)
Zero warnings out of the box - no #if-on-undefined-macro footguns
in the codebase. Now part of WARNING_DEFINES alongside the existing
-Wsign-compare.
4. -Wmissing-prototypes enabled permanently (Makefile.libretro)
Started at 198 warnings, cleared all of them:
* Mass-static-ified ~96 functions across 75 files that were
defined non-static but only used within their own translation
unit. (See static_prototype_fixer.py in the development notes.)
* K&R-style empty-parens prototypes "()" replaced with explicit
"(void)" across all asic_*.{c,h} files - GCC treats "()" as
"any args" and refuses to match it against a separate K&R
definition.
* Added missing forward declarations to public headers:
- fds.h (FDSLoad)
- nsf.h (NSFLoad)
- ines.h (iNESLoad)
- unif.h (UNIFLoad)
- latch.h (LatchHardReset, K&R fix)
- eeprom_93Cx6.h (eeprom_93Cx6_read, K&R fix)
Each header gained an "#include "file.h"" where needed.
* fds_apu.c now includes its own fds_apu.h header (was missing).
* fds_apu.h: removed unused FDSSoundRead declaration (the function
is internal-static).
* cartram.h: removed unused CartRAM_close declaration (the function
is internal-static).
* input.h: added a centralised block of FCEU_Init* prototypes
(Zapper, Mouse, Powerpad, Arkanoid, VirtualBoy, FKB, SuborKB,
PEC586KB, HS, Mahjong, FamilyTrainerA/B, OekaKids, TopRider,
BarcodeWorld, BattleBox, QuizKing, FTrainerA/B, SpaceShadow,
LCDCompZapper, ArkanoidFC) plus FCEU_ZapperSetTolerance. These
were previously declared as "extern" inside src/input.c.
* Static-ified FP_FASTAPASS callbacks in 106.c, 65.c, 67.c,
asic_h3001.c, asic_vrc3.c (those with no external callers);
left non-static for those that have header decls or are
referenced from sibling .c files (asic_mmc1, asic_vrc6,
asic_vrc7, flashrom).
* For a small set of cross-file functions where adding a header
was disproportionate to the value (MMC5_hb, NSFMMC5_Close,
GetKeyboard, FCEU_GetJoyJoy), placed a forward declaration
immediately above the definition. This satisfies
-Wmissing-prototypes (which checks for any prior declaration
in scope) without churning the public-header layout.
5. -Wshadow partial cleanup (not enabled permanently)
Fixed five real shadows that were either bugs or actively
misleading:
* src/boards/476.c: removed an inner "int i" that shadowed the
outer loop counter.
* src/boards/mmc5.c MMC5_hb: parameter "scanline" renamed to
"sl_param" (was shadowing the global "scanline").
* src/boards/n106.c DoNamcoSound: parameter "Wave" renamed to
"WaveBuf" (was shadowing the global Wave audio buffer); also
updated the forward declaration and the matching parameter
on sound.h's NeoFill function pointer typedef.
* src/boards/vrc7.c UpdateOPLNEO: same Wave -> WaveBuf rename.
* src/ntsc/nes_ntsc_impl.h: renamed an inner loop counter "n"
that shadowed an outer "n".
* src/drivers/libretro/libretro.c FCEUD_RegionOverride: local
"pal" renamed to "is_pal" (was shadowing the typedef "pal" from
palette.h).
* src/palette.c FCEUI_SetPaletteArray: parameter "pal" renamed
to "data" (same shadow); driver.h declaration updated to match.
-Wshadow itself is NOT enabled permanently because the remaining
warnings are deliberate parameter naming conventions (XBuf in
draw functions, X in cpu hooks) and third-party blargg ntsc code.
In addition, four files were touched as part of an MSVC-build fix
that came up mid-pass: src/fds.c, src/nsf.c, src/ines.c, and
src/drivers/libretro/libretro_dipswitch.c had snprintf() calls
introduced in pass 4 that fail to link on pre-MSVC2015 toolchains
when STATIC_LINKING=1 (the libretro-common compat_snprintf.c shim
isn't compiled in those configurations). Replaced each snprintf with
either sprintf-into-bounded-buffer (the format strings have known
maximum output) or strlcpy/strlcat for the dipswitch key-build case.
All output is still bounded; truncation happens via strl*'s normal
truncation semantics where applicable.
All added code is C89-clean (top-of-block declarations only, no
mixed decls, no // comments, INLINE macro from fceu-types.h instead
of bare "inline"). Builds clean under -std=gnu11 with -Wno-write-
strings -Wsign-compare -Wundef -Wmissing-prototypes; zero errors,
zero warnings.
Determinism audit (audit_determinism.py): no rand/time/long
double/threads issues introduced.
2026-05-04 04:44:52 +02:00
|
|
|
static void add_suborkey_input(unsigned port, uint32_t variant, uint8_t *suborkeys) {
|
2025-09-13 11:27:38 +02:00
|
|
|
unsigned k;
|
|
|
|
|
const uint32_t* map = suborkbmap;
|
|
|
|
|
for (k = 0 ; k < 0x65 ; k++)
|
|
|
|
|
if (input_cb(0, RETRO_DEVICE_KEYBOARD, 0, map[k]))
|
|
|
|
|
suborkeys[k]=1;
|
|
|
|
|
else
|
|
|
|
|
suborkeys[k]=0;
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-25 11:16:26 +08:00
|
|
|
static int mzx = 0, mzy = 0;
|
2017-09-16 16:24:59 +08:00
|
|
|
|
core: stride-aware savestate, iNES2 helpers, -Wundef, -Wmissing-prototypes
Audit pass 5 - five distinct cleanups bundled into one omnibus.
1. Element-stride byte-swapping for savestate fields (state.h, state.c,
fceu-endian.{h,c})
The SFORMAT 's' field was previously {bit 31 = RLSB, bits 0..30 =
byte size}. RLSB triggers FlipByteOrder() on MSB hosts, which
reverses the entire entry buffer end-to-end. That is correct for a
single primitive (size <= 8 bytes) but wrong for an array of
multi-byte primitives - reversing the whole buffer would swap
element 0 with element N-1 and reverse their bytes too, scrambling
the data.
The previous workaround was either splitting an N-element array
into N separate single-primitive entries with distinct chunk IDs
(n106 PlayIndex split into IDX0..IDX7) or skipping the entry
entirely on big-endian hosts (the GEKKO #ifndef in vrc6.c / vrc7.c).
Both approaches mean BE saves are not portable to LE and vice
versa, and force the same workaround at every new array site.
This pass adds proper stride support:
* SFORMAT 's' encoding is now {bit 31 = RLSB, bits 24..30 =
stride in bytes (0 = legacy/unset), bits 0..23 = byte size}.
16 MiB max size, well above any actual savestate field.
* FCEUSTATE_RLSB_ARRAY(stride) macro for the new pattern.
* FlipByteOrderStrided() byte-swaps each element of an array
independently. Round-trip identity verified: [01 00 00 00 ...]
-> [00 00 00 01 ...] -> [01 00 00 00 ...].
* state.c's SubWrite / ReadStateChunk / CheckS use new helpers
sf_size() / sf_stride() / sf_flip() that mask the size out of
the new bit layout and dispatch to the strided variant when
stride < size.
Backwards compatible: legacy single-primitive entries (size == 1,
2, 4, 8) leave the stride bits at zero, which sf_stride() reads
as "stride equals size" and falls through to FlipByteOrder() as
before. No on-disk format change. Existing single-primitive RLSB
sites are unchanged.
The infrastructure is now in place so any future SFORMAT entry
that is an array of multi-byte primitives can be expressed as a
single entry (e.g. "{ buf, sizeof(buf) | FCEUSTATE_RLSB_ARRAY(4),
"BUF." }") without splitting or skipping. The existing PlayIndex
split and GEKKO #ifndefs are intentionally left untouched -
migrating them would alter the on-disk savestate format and is a
separate decision.
2. iNES1-vs-iNES2 sizing helpers (cart.h)
Twelve sites across the codebase encoded the same conditional:
info->iNES2 ? (info->PRGRamSize + info->PRGRamSaveSize) : default
Sometimes for PRGRAM, sometimes for CHRRAM, sometimes in bytes,
sometimes after dividing by 1024. The pattern is verbose and easy
to write inconsistently.
Added two inline helpers in cart.h:
- CartInfo_PRGRAM_bytes(info, default_bytes)
- CartInfo_CHRRAM_bytes(info, default_bytes)
Migrated 9 of the 13 sites: cartram.c (2), 162.c, 163.c, 134.c,
399.c, 478.c, 480.c, 484.c. The remaining 4 are non-helper-fitting
variants (164.c special masking, 2 cartram SaveGameLen sites with
different fallback semantics, mmc3.c Boogerman submapper detection).
3. -Wundef enabled permanently (Makefile.libretro)
Zero warnings out of the box - no #if-on-undefined-macro footguns
in the codebase. Now part of WARNING_DEFINES alongside the existing
-Wsign-compare.
4. -Wmissing-prototypes enabled permanently (Makefile.libretro)
Started at 198 warnings, cleared all of them:
* Mass-static-ified ~96 functions across 75 files that were
defined non-static but only used within their own translation
unit. (See static_prototype_fixer.py in the development notes.)
* K&R-style empty-parens prototypes "()" replaced with explicit
"(void)" across all asic_*.{c,h} files - GCC treats "()" as
"any args" and refuses to match it against a separate K&R
definition.
* Added missing forward declarations to public headers:
- fds.h (FDSLoad)
- nsf.h (NSFLoad)
- ines.h (iNESLoad)
- unif.h (UNIFLoad)
- latch.h (LatchHardReset, K&R fix)
- eeprom_93Cx6.h (eeprom_93Cx6_read, K&R fix)
Each header gained an "#include "file.h"" where needed.
* fds_apu.c now includes its own fds_apu.h header (was missing).
* fds_apu.h: removed unused FDSSoundRead declaration (the function
is internal-static).
* cartram.h: removed unused CartRAM_close declaration (the function
is internal-static).
* input.h: added a centralised block of FCEU_Init* prototypes
(Zapper, Mouse, Powerpad, Arkanoid, VirtualBoy, FKB, SuborKB,
PEC586KB, HS, Mahjong, FamilyTrainerA/B, OekaKids, TopRider,
BarcodeWorld, BattleBox, QuizKing, FTrainerA/B, SpaceShadow,
LCDCompZapper, ArkanoidFC) plus FCEU_ZapperSetTolerance. These
were previously declared as "extern" inside src/input.c.
* Static-ified FP_FASTAPASS callbacks in 106.c, 65.c, 67.c,
asic_h3001.c, asic_vrc3.c (those with no external callers);
left non-static for those that have header decls or are
referenced from sibling .c files (asic_mmc1, asic_vrc6,
asic_vrc7, flashrom).
* For a small set of cross-file functions where adding a header
was disproportionate to the value (MMC5_hb, NSFMMC5_Close,
GetKeyboard, FCEU_GetJoyJoy), placed a forward declaration
immediately above the definition. This satisfies
-Wmissing-prototypes (which checks for any prior declaration
in scope) without churning the public-header layout.
5. -Wshadow partial cleanup (not enabled permanently)
Fixed five real shadows that were either bugs or actively
misleading:
* src/boards/476.c: removed an inner "int i" that shadowed the
outer loop counter.
* src/boards/mmc5.c MMC5_hb: parameter "scanline" renamed to
"sl_param" (was shadowing the global "scanline").
* src/boards/n106.c DoNamcoSound: parameter "Wave" renamed to
"WaveBuf" (was shadowing the global Wave audio buffer); also
updated the forward declaration and the matching parameter
on sound.h's NeoFill function pointer typedef.
* src/boards/vrc7.c UpdateOPLNEO: same Wave -> WaveBuf rename.
* src/ntsc/nes_ntsc_impl.h: renamed an inner loop counter "n"
that shadowed an outer "n".
* src/drivers/libretro/libretro.c FCEUD_RegionOverride: local
"pal" renamed to "is_pal" (was shadowing the typedef "pal" from
palette.h).
* src/palette.c FCEUI_SetPaletteArray: parameter "pal" renamed
to "data" (same shadow); driver.h declaration updated to match.
-Wshadow itself is NOT enabled permanently because the remaining
warnings are deliberate parameter naming conventions (XBuf in
draw functions, X in cpu hooks) and third-party blargg ntsc code.
In addition, four files were touched as part of an MSVC-build fix
that came up mid-pass: src/fds.c, src/nsf.c, src/ines.c, and
src/drivers/libretro/libretro_dipswitch.c had snprintf() calls
introduced in pass 4 that fail to link on pre-MSVC2015 toolchains
when STATIC_LINKING=1 (the libretro-common compat_snprintf.c shim
isn't compiled in those configurations). Replaced each snprintf with
either sprintf-into-bounded-buffer (the format strings have known
maximum output) or strlcpy/strlcat for the dipswitch key-build case.
All output is still bounded; truncation happens via strl*'s normal
truncation semantics where applicable.
All added code is C89-clean (top-of-block declarations only, no
mixed decls, no // comments, INLINE macro from fceu-types.h instead
of bare "inline"). Builds clean under -std=gnu11 with -Wno-write-
strings -Wsign-compare -Wundef -Wmissing-prototypes; zero errors,
zero warnings.
Determinism audit (audit_determinism.py): no rand/time/long
double/threads issues introduced.
2026-05-04 04:44:52 +02:00
|
|
|
static void get_mouse_input(unsigned port, uint32_t variant, uint32_t *mousedata)
|
2017-09-09 16:16:12 +08:00
|
|
|
{
|
2017-11-25 11:16:26 +08:00
|
|
|
int min_width, min_height, max_width, max_height;
|
2017-09-16 16:24:59 +08:00
|
|
|
|
2017-11-25 11:16:26 +08:00
|
|
|
max_width = 256;
|
|
|
|
|
max_height = 240;
|
2023-03-11 11:40:58 -06:00
|
|
|
mousedata[2] = 0; /* reset click state */
|
|
|
|
|
|
|
|
|
|
if (variant == RETRO_DEVICE_FC_ARKANOID)
|
|
|
|
|
variant = RETRO_DEVICE_ARKANOID;
|
2017-09-09 16:16:12 +08:00
|
|
|
|
2023-03-11 11:40:58 -06:00
|
|
|
if ((variant != RETRO_DEVICE_ARKANOID && zappermode == RetroMouse) ||
|
|
|
|
|
(variant == RETRO_DEVICE_ARKANOID && arkanoidmode == RetroArkanoidMouse)) /* mouse device */
|
2017-09-16 17:32:05 +08:00
|
|
|
{
|
2022-08-27 23:01:26 -05:00
|
|
|
int mouse_Lbutton;
|
|
|
|
|
int mouse_Rbutton;
|
|
|
|
|
|
2023-03-10 21:47:35 -05:00
|
|
|
min_width = crop_overscan_h_left + 1;
|
|
|
|
|
min_height = crop_overscan_v_top + 1;
|
|
|
|
|
max_width -= crop_overscan_h_right;
|
|
|
|
|
max_height -= crop_overscan_v_bottom;
|
2017-11-25 11:16:26 +08:00
|
|
|
|
2023-03-11 11:40:58 -06:00
|
|
|
mzx += mouseSensitivity * input_cb(port, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_X) / 100;
|
|
|
|
|
mzy += mouseSensitivity * input_cb(port, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_Y) / 100;
|
2017-09-16 17:32:05 +08:00
|
|
|
|
2023-03-11 11:40:58 -06:00
|
|
|
switch(variant) {
|
|
|
|
|
case RETRO_DEVICE_ARKANOID:
|
|
|
|
|
if (mzx < 0) mzx = 0;
|
|
|
|
|
else if (mzx > 240) mzx = 240;
|
|
|
|
|
if (mzy < min_height) mzy = min_height;
|
|
|
|
|
else if (mzy > max_height) mzy = max_height;
|
|
|
|
|
mousedata[1] = mzy;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case RETRO_DEVICE_ZAPPER:
|
|
|
|
|
default:
|
2017-11-25 11:16:26 +08:00
|
|
|
/* Set crosshair within the limits of current screen resolution */
|
|
|
|
|
if (mzx < min_width) mzx = min_width;
|
|
|
|
|
else if (mzx > max_width) mzx = max_width;
|
2023-03-11 11:40:58 -06:00
|
|
|
break;
|
|
|
|
|
}
|
2017-11-25 11:16:26 +08:00
|
|
|
|
2023-03-11 11:40:58 -06:00
|
|
|
mousedata[0] = mzx;
|
2022-08-27 23:01:26 -05:00
|
|
|
|
|
|
|
|
mouse_Lbutton = input_cb(port, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_LEFT);
|
|
|
|
|
mouse_Rbutton = input_cb(port, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_RIGHT);
|
2017-09-16 17:32:05 +08:00
|
|
|
|
2022-08-27 23:01:26 -05:00
|
|
|
if (mouse_Lbutton)
|
2023-03-11 11:40:58 -06:00
|
|
|
mousedata[2] |= 0x1;
|
2022-08-27 23:01:26 -05:00
|
|
|
if (mouse_Rbutton)
|
2023-03-11 11:40:58 -06:00
|
|
|
mousedata[2] |= 0x2;
|
2017-09-16 17:32:05 +08:00
|
|
|
}
|
2023-03-11 11:40:58 -06:00
|
|
|
else if (variant != RETRO_DEVICE_ARKANOID && zappermode == RetroPointer) {
|
2023-03-10 21:47:35 -05:00
|
|
|
int offset_x = (crop_overscan_h_left * 0x120) - 1;
|
|
|
|
|
int offset_y = (crop_overscan_v_top * 0x133) + 1;
|
2019-06-10 17:16:00 -10:00
|
|
|
|
|
|
|
|
int _x = input_cb(port, RETRO_DEVICE_POINTER, 0, RETRO_DEVICE_ID_POINTER_X);
|
|
|
|
|
int _y = input_cb(port, RETRO_DEVICE_POINTER, 0, RETRO_DEVICE_ID_POINTER_Y);
|
|
|
|
|
|
|
|
|
|
if (_x == 0 && _y == 0)
|
|
|
|
|
{
|
2023-03-11 11:40:58 -06:00
|
|
|
mousedata[0] = 0;
|
2019-06-10 17:16:00 -10:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2023-03-11 11:40:58 -06:00
|
|
|
mousedata[0] = (_x + (0x7FFF + offset_x)) * max_width / ((0x7FFF + offset_x) * 2);
|
|
|
|
|
mousedata[1] = (_y + (0x7FFF + offset_y)) * max_height / ((0x7FFF + offset_y) * 2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (input_cb(port, RETRO_DEVICE_POINTER, 0, RETRO_DEVICE_ID_POINTER_PRESSED))
|
|
|
|
|
mousedata[2] |= 0x1;
|
|
|
|
|
}
|
|
|
|
|
else if (variant == RETRO_DEVICE_ARKANOID && (arkanoidmode == RetroArkanoidAbsMouse || arkanoidmode == RetroArkanoidPointer)) {
|
|
|
|
|
int offset_x = (crop_overscan_h_left * 0x120) - 1;
|
|
|
|
|
|
|
|
|
|
int _x = input_cb(port, RETRO_DEVICE_POINTER, 0, RETRO_DEVICE_ID_POINTER_X);
|
|
|
|
|
int _y = input_cb(port, RETRO_DEVICE_POINTER, 0, RETRO_DEVICE_ID_POINTER_Y);
|
|
|
|
|
|
|
|
|
|
if (_x != 0 || _y != 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 raw = (_x + (0x7FFF + offset_x)) * max_width / ((0x7FFF + offset_x) * 2);
|
2023-03-11 11:40:58 -06:00
|
|
|
if (arkanoidmode == RetroArkanoidAbsMouse) {
|
2023-05-03 20:27:46 +02:00
|
|
|
/* remap so full screen movement ends up within the encoder range 0-240
|
|
|
|
|
game board: 176 wide
|
|
|
|
|
paddle: 32
|
|
|
|
|
range of movement: 176-32 = 144
|
|
|
|
|
left edge: 16
|
|
|
|
|
right edge: 64
|
2023-03-11 11:40:58 -06:00
|
|
|
|
2023-05-03 20:27:46 +02:00
|
|
|
increase movement by 10 to allow edges to be reached in case of problems
|
|
|
|
|
*/
|
2023-03-11 11:40:58 -06:00
|
|
|
raw = (raw - 128) * 140 / 128 + 128;
|
|
|
|
|
if (raw < 0)
|
|
|
|
|
raw = 0;
|
|
|
|
|
else if (raw > 255)
|
|
|
|
|
raw = 255;
|
|
|
|
|
|
|
|
|
|
mousedata[0] = raw * 240 / 255;
|
|
|
|
|
}
|
|
|
|
|
else {
|
2023-05-03 20:27:46 +02:00
|
|
|
/* remap so full board movement ends up within the encoder range 0-240 */
|
2023-03-11 11:40:58 -06:00
|
|
|
if (mousedata[0] < 16+(32/2))
|
|
|
|
|
mousedata[0] = 0;
|
|
|
|
|
else
|
|
|
|
|
mousedata[0] -= 16+(32/2);
|
|
|
|
|
if (mousedata[0] > 144)
|
|
|
|
|
mousedata[0] = 144;
|
|
|
|
|
mousedata[0] = raw * 240 / 144;
|
|
|
|
|
}
|
2019-06-10 17:16:00 -10:00
|
|
|
}
|
2023-03-11 11:40:58 -06:00
|
|
|
|
2019-06-10 17:16:00 -10:00
|
|
|
|
|
|
|
|
if (input_cb(port, RETRO_DEVICE_POINTER, 0, RETRO_DEVICE_ID_POINTER_PRESSED))
|
2023-03-11 11:40:58 -06:00
|
|
|
mousedata[2] |= 0x1;
|
|
|
|
|
}
|
|
|
|
|
else if (variant == RETRO_DEVICE_ARKANOID && arkanoidmode == RetroArkanoidStelladaptor) {
|
|
|
|
|
int x = input_cb(port, RETRO_DEVICE_ANALOG, 0, RETRO_DEVICE_ID_ANALOG_X);
|
|
|
|
|
mousedata[0] = (x+32768)*240/65535;
|
|
|
|
|
if (input_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A) || input_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B))
|
|
|
|
|
mousedata[2] |= 0x1;
|
2019-06-10 17:16:00 -10:00
|
|
|
}
|
2022-08-27 23:01:26 -05:00
|
|
|
else if (zappermode == RetroCLightgun) /* Crosshair lightgun device */
|
2017-09-16 17:32:05 +08:00
|
|
|
{
|
2023-03-10 21:47:35 -05:00
|
|
|
int offset_x = (crop_overscan_h_left * 0x120) - 1;
|
|
|
|
|
int offset_y = (crop_overscan_v_top * 0x133) + 1;
|
2018-09-27 08:43:12 +01:00
|
|
|
int offscreen;
|
|
|
|
|
int offscreen_shot;
|
|
|
|
|
int trigger;
|
2017-11-25 11:16:26 +08:00
|
|
|
|
2018-09-27 08:43:12 +01:00
|
|
|
offscreen = input_cb( port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_IS_OFFSCREEN );
|
|
|
|
|
offscreen_shot = input_cb( port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_RELOAD );
|
|
|
|
|
trigger = input_cb( port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_TRIGGER );
|
2017-11-25 11:16:26 +08:00
|
|
|
|
2018-09-27 08:43:12 +01:00
|
|
|
if ( offscreen || offscreen_shot )
|
2017-11-25 11:16:26 +08:00
|
|
|
{
|
2023-03-11 11:40:58 -06:00
|
|
|
mousedata[0] = 0;
|
|
|
|
|
mousedata[1] = 0;
|
2017-11-25 11:16:26 +08:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2022-08-27 23:01:26 -05:00
|
|
|
int _x = input_cb( port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_SCREEN_X );
|
|
|
|
|
int _y = input_cb( port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_SCREEN_Y );
|
2018-09-27 08:43:12 +01:00
|
|
|
|
2023-03-11 11:40:58 -06:00
|
|
|
mousedata[0] = (_x + (0x7FFF + offset_x)) * max_width / ((0x7FFF + offset_x) * 2);
|
|
|
|
|
mousedata[1] = (_y + (0x7FFF + offset_y)) * max_height / ((0x7FFF + offset_y) * 2);
|
2017-11-25 11:16:26 +08:00
|
|
|
}
|
2017-09-09 16:16:12 +08:00
|
|
|
|
2018-09-27 08:43:12 +01:00
|
|
|
if ( trigger || offscreen_shot )
|
2023-03-11 11:40:58 -06:00
|
|
|
mousedata[2] |= 0x1;
|
2017-09-16 17:32:05 +08:00
|
|
|
}
|
2022-08-27 23:01:26 -05:00
|
|
|
else /* Sequential targets lightgun device integration */
|
|
|
|
|
{
|
2023-03-11 11:40:58 -06:00
|
|
|
mousedata[2] = input_cb( port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_TRIGGER );
|
|
|
|
|
mousedata[3] = input_cb( port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_AUX_A );
|
2022-08-27 23:01:26 -05:00
|
|
|
}
|
2017-09-09 16:16:12 +08:00
|
|
|
}
|
|
|
|
|
|
2014-03-31 00:17:47 +02:00
|
|
|
static void FCEUD_UpdateInput(void)
|
2014-03-30 22:35:00 +02:00
|
|
|
{
|
2018-12-23 22:46:17 +08:00
|
|
|
unsigned player, port;
|
2021-10-22 13:30:27 +01:00
|
|
|
bool palette_prev = false;
|
|
|
|
|
bool palette_next = false;
|
2014-03-30 22:15:17 +02:00
|
|
|
|
2014-03-31 00:17:47 +02:00
|
|
|
poll_cb();
|
2014-03-30 22:15:17 +02:00
|
|
|
|
2020-06-04 13:26:02 +08:00
|
|
|
/* Reset input states */
|
|
|
|
|
nes_input.JSReturn = 0;
|
|
|
|
|
|
2018-12-04 10:25:17 +08:00
|
|
|
/* nes gamepad */
|
2017-11-13 08:22:46 +08:00
|
|
|
for (player = 0; player < MAX_PLAYERS; player++)
|
2017-08-24 07:31:25 +08:00
|
|
|
{
|
2018-12-23 22:46:17 +08:00
|
|
|
int i = 0;
|
|
|
|
|
uint8_t input_buf = 0;
|
2021-10-22 13:30:27 +01:00
|
|
|
int player_enabled = (nes_input.type[player] == RETRO_DEVICE_GAMEPAD) ||
|
|
|
|
|
(nes_input.type[player] == RETRO_DEVICE_JOYPAD);
|
2018-12-23 22:46:17 +08:00
|
|
|
|
|
|
|
|
if (player_enabled)
|
2017-02-16 20:30:24 -06:00
|
|
|
{
|
2019-06-21 11:21:46 +02:00
|
|
|
int16_t ret;
|
|
|
|
|
|
|
|
|
|
if (libretro_supports_bitmasks)
|
|
|
|
|
{
|
2021-10-22 13:30:27 +01:00
|
|
|
bool dpad_enabled = true;
|
|
|
|
|
|
2019-06-21 11:21:46 +02:00
|
|
|
ret = input_cb(player, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_MASK);
|
|
|
|
|
|
2021-10-22 13:30:27 +01:00
|
|
|
/* If palette switching is enabled, check if
|
|
|
|
|
* player 1 has the L2 button held down */
|
|
|
|
|
if ((player == 0) &&
|
|
|
|
|
palette_switch_enabled &&
|
|
|
|
|
(ret & (1 << RETRO_DEVICE_ID_JOYPAD_L2)))
|
|
|
|
|
{
|
|
|
|
|
/* D-Pad left/right are used to switch palettes */
|
|
|
|
|
palette_prev = (bool)(ret & (1 << RETRO_DEVICE_ID_JOYPAD_LEFT));
|
|
|
|
|
palette_next = (bool)(ret & (1 << RETRO_DEVICE_ID_JOYPAD_RIGHT));
|
|
|
|
|
|
|
|
|
|
/* Regular D-Pad input is disabled */
|
|
|
|
|
dpad_enabled = false;
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-21 11:21:46 +02:00
|
|
|
if (ret & (1 << RETRO_DEVICE_ID_JOYPAD_A))
|
|
|
|
|
input_buf |= JOY_A;
|
|
|
|
|
if (ret & (1 << RETRO_DEVICE_ID_JOYPAD_B))
|
|
|
|
|
input_buf |= JOY_B;
|
2021-12-10 23:53:45 +02:00
|
|
|
if (ret & (1 << RETRO_DEVICE_ID_JOYPAD_L3))
|
|
|
|
|
input_buf |= JOY_A | JOY_B;
|
2019-06-21 11:21:46 +02:00
|
|
|
if (ret & (1 << RETRO_DEVICE_ID_JOYPAD_SELECT))
|
|
|
|
|
input_buf |= JOY_SELECT;
|
|
|
|
|
if (ret & (1 << RETRO_DEVICE_ID_JOYPAD_START))
|
|
|
|
|
input_buf |= JOY_START;
|
2021-10-22 13:30:27 +01:00
|
|
|
|
|
|
|
|
if (dpad_enabled)
|
|
|
|
|
{
|
|
|
|
|
if (ret & (1 << RETRO_DEVICE_ID_JOYPAD_UP))
|
|
|
|
|
input_buf |= JOY_UP;
|
|
|
|
|
if (ret & (1 << RETRO_DEVICE_ID_JOYPAD_DOWN))
|
|
|
|
|
input_buf |= JOY_DOWN;
|
|
|
|
|
if (ret & (1 << RETRO_DEVICE_ID_JOYPAD_LEFT))
|
|
|
|
|
input_buf |= JOY_LEFT;
|
|
|
|
|
if (ret & (1 << RETRO_DEVICE_ID_JOYPAD_RIGHT))
|
|
|
|
|
input_buf |= JOY_RIGHT;
|
|
|
|
|
}
|
2019-06-21 11:21:46 +02:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
for (i = 0; i < MAX_BUTTONS; i++)
|
|
|
|
|
input_buf |= input_cb(player, RETRO_DEVICE_JOYPAD, 0,
|
|
|
|
|
bindmap[i].retro) ? bindmap[i].nes : 0;
|
2021-10-22 13:30:27 +01:00
|
|
|
|
|
|
|
|
/* If palette switching is enabled, check if
|
|
|
|
|
* player 1 has the L2 button held down */
|
|
|
|
|
if ((player == 0) &&
|
|
|
|
|
palette_switch_enabled &&
|
|
|
|
|
input_cb(player, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L2))
|
|
|
|
|
{
|
|
|
|
|
/* D-Pad left/right are used to switch palettes */
|
|
|
|
|
palette_prev = (bool)(input_buf & JOY_LEFT);
|
|
|
|
|
palette_next = (bool)(input_buf & JOY_RIGHT);
|
|
|
|
|
|
|
|
|
|
/* Regular D-Pad input is disabled */
|
|
|
|
|
input_buf &= ~(JOY_UP | JOY_DOWN | JOY_LEFT | JOY_RIGHT);
|
|
|
|
|
}
|
2019-06-21 11:21:46 +02:00
|
|
|
}
|
2018-12-23 22:46:17 +08:00
|
|
|
|
|
|
|
|
/* Turbo A and Turbo B buttons are
|
|
|
|
|
* mapped to Joypad X and Joypad Y
|
|
|
|
|
* in RetroArch joypad.
|
2021-12-10 23:53:45 +02:00
|
|
|
* Turbo A+B button is mapped to R3
|
|
|
|
|
* in RetroArch joypad.
|
2018-12-23 22:46:17 +08:00
|
|
|
*
|
|
|
|
|
* We achieve this by keeping track of
|
|
|
|
|
* the number of times it increments
|
|
|
|
|
* the toggle counter and fire or not fire
|
|
|
|
|
* depending on whether the delay value has
|
|
|
|
|
* been reached.
|
2021-12-10 23:53:45 +02:00
|
|
|
*
|
|
|
|
|
* Each turbo button is activated by
|
|
|
|
|
* corresponding mapped button
|
|
|
|
|
* OR mapped Turbo A+B button.
|
|
|
|
|
* This allows Turbo A+B button to use
|
|
|
|
|
* the same toggle counters as Turbo A
|
|
|
|
|
* and Turbo B buttons use separately.
|
2018-12-23 22:46:17 +08:00
|
|
|
*/
|
|
|
|
|
|
2020-05-05 13:35:30 +08:00
|
|
|
if (nes_input.turbo_enabler[player])
|
2018-01-03 20:06:13 +08:00
|
|
|
{
|
2021-12-10 23:53:45 +02:00
|
|
|
/* Handle Turbo A, B & A+B buttons */
|
2018-12-23 22:46:17 +08:00
|
|
|
for (i = 0; i < TURBO_BUTTONS; i++)
|
2017-08-24 07:31:25 +08:00
|
|
|
{
|
2021-12-10 23:53:45 +02:00
|
|
|
if (input_cb(player, RETRO_DEVICE_JOYPAD, 0, turbomap[i].retro) || input_cb(player, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3))
|
2018-12-23 22:46:17 +08:00
|
|
|
{
|
2025-05-02 00:19:38 +02:00
|
|
|
if (!turbo_button_toggle[player][i])
|
2018-12-23 22:46:17 +08:00
|
|
|
input_buf |= turbomap[i].nes;
|
|
|
|
|
turbo_button_toggle[player][i]++;
|
2020-05-05 13:35:30 +08:00
|
|
|
turbo_button_toggle[player][i] %= nes_input.turbo_delay + 1;
|
2018-12-23 22:46:17 +08:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
/* If the button is not pressed, just reset the toggle */
|
|
|
|
|
turbo_button_toggle[player][i] = 0;
|
2017-08-24 07:31:25 +08:00
|
|
|
}
|
2018-01-03 20:06:13 +08:00
|
|
|
}
|
|
|
|
|
}
|
2018-04-14 19:35:51 +02:00
|
|
|
|
2020-05-05 13:35:30 +08:00
|
|
|
if (!nes_input.up_down_allowed)
|
2018-04-14 19:35:51 +02:00
|
|
|
{
|
2021-10-22 13:30:27 +01:00
|
|
|
if ((input_buf & JOY_UP) && (input_buf & JOY_DOWN))
|
|
|
|
|
input_buf &= ~(JOY_UP | JOY_DOWN);
|
|
|
|
|
if ((input_buf & JOY_LEFT) && (input_buf & JOY_RIGHT))
|
|
|
|
|
input_buf &= ~(JOY_LEFT | JOY_RIGHT);
|
2018-04-14 19:35:51 +02:00
|
|
|
}
|
|
|
|
|
|
2020-06-04 13:26:02 +08:00
|
|
|
nes_input.JSReturn |= (input_buf & 0xff) << (player << 3);
|
2018-01-03 20:06:13 +08:00
|
|
|
}
|
2015-11-09 14:27:31 -06:00
|
|
|
|
2018-01-03 20:06:13 +08:00
|
|
|
/* other inputs*/
|
|
|
|
|
for (port = 0; port < MAX_PORTS; port++)
|
|
|
|
|
{
|
2020-05-05 13:35:30 +08:00
|
|
|
switch (nes_input.type[port])
|
2018-01-03 20:06:13 +08:00
|
|
|
{
|
2017-11-25 11:16:26 +08:00
|
|
|
case RETRO_DEVICE_ARKANOID:
|
2023-03-11 11:40:58 -06:00
|
|
|
case RETRO_DEVICE_FC_ARKANOID:
|
2017-11-13 08:22:46 +08:00
|
|
|
case RETRO_DEVICE_ZAPPER:
|
2023-03-11 11:40:58 -06:00
|
|
|
get_mouse_input(port, nes_input.type[port], nes_input.MouseData[port]);
|
2018-01-03 20:06:13 +08:00
|
|
|
break;
|
2017-11-13 08:22:46 +08:00
|
|
|
}
|
|
|
|
|
}
|
2017-09-09 16:16:12 +08:00
|
|
|
|
2023-01-01 07:30:56 -06:00
|
|
|
nes_input.PowerPadData = 0;
|
2022-12-31 21:27:46 -06:00
|
|
|
for (port = 0; port < MAX_PORTS; port++)
|
|
|
|
|
{
|
|
|
|
|
switch (nes_input.type[port])
|
|
|
|
|
{
|
|
|
|
|
case RETRO_DEVICE_POWERPADB:
|
|
|
|
|
case RETRO_DEVICE_POWERPADA:
|
2023-01-01 07:30:56 -06:00
|
|
|
add_powerpad_input(port, nes_input.type[port], &nes_input.PowerPadData);
|
2022-12-31 21:27:46 -06:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-03 20:06:13 +08:00
|
|
|
/* famicom inputs */
|
2020-05-05 13:35:30 +08:00
|
|
|
switch (nes_input.type[4])
|
2017-11-25 11:16:26 +08:00
|
|
|
{
|
|
|
|
|
case RETRO_DEVICE_FC_ARKANOID:
|
|
|
|
|
case RETRO_DEVICE_FC_OEKAKIDS:
|
|
|
|
|
case RETRO_DEVICE_FC_SHADOW:
|
2023-03-11 11:40:58 -06:00
|
|
|
get_mouse_input(0, nes_input.type[4], nes_input.FamicomData);
|
2017-11-25 11:16:26 +08:00
|
|
|
break;
|
2021-04-10 15:20:50 +08:00
|
|
|
case RETRO_DEVICE_FC_HYPERSHOT:
|
|
|
|
|
{
|
|
|
|
|
static int toggle;
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
nes_input.FamicomData[0] = 0;
|
|
|
|
|
toggle ^= 1;
|
|
|
|
|
for (i = 0; i < 2; i++)
|
|
|
|
|
{
|
2022-06-09 13:58:15 +08:00
|
|
|
|
2021-04-10 15:20:50 +08:00
|
|
|
if (input_cb(i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B))
|
|
|
|
|
nes_input.FamicomData[0] |= 0x02 << (i * 2);
|
|
|
|
|
else if (input_cb(i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y))
|
|
|
|
|
{
|
|
|
|
|
if (toggle)
|
|
|
|
|
nes_input.FamicomData[0] |= 0x02 << (i * 2);
|
|
|
|
|
else
|
|
|
|
|
nes_input.FamicomData[0] &= ~(0x02 << (i * 2));
|
|
|
|
|
}
|
|
|
|
|
if (input_cb(i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A))
|
|
|
|
|
nes_input.FamicomData[0] |= 0x04 << (i * 2);
|
|
|
|
|
else if (input_cb(i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X))
|
|
|
|
|
{
|
|
|
|
|
if (toggle)
|
|
|
|
|
nes_input.FamicomData[0] |= 0x04 << (i * 2);
|
|
|
|
|
else
|
|
|
|
|
nes_input.FamicomData[0] &= ~(0x04 << (i * 2));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
2023-09-15 19:48:52 -04:00
|
|
|
case RETRO_DEVICE_FC_FTRAINERB:
|
|
|
|
|
case RETRO_DEVICE_FC_FTRAINERA:
|
|
|
|
|
add_powerpad_input(4, nes_input.type[4], &nes_input.PowerPadData);
|
|
|
|
|
break;
|
2025-09-13 11:27:38 +02:00
|
|
|
case RETRO_DEVICE_FC_FKB:
|
|
|
|
|
add_fkb_input(4, nes_input.type[4], nes_input.FamilyKeyboardData);
|
|
|
|
|
break;
|
|
|
|
|
case RETRO_DEVICE_FC_SUBORKB:
|
|
|
|
|
case RETRO_DEVICE_FC_PEC586KB:
|
|
|
|
|
add_suborkey_input(4, nes_input.type[4], nes_input.SuborKeyboardData);
|
|
|
|
|
break;
|
2017-11-25 11:16:26 +08:00
|
|
|
}
|
|
|
|
|
|
2015-11-09 14:27:31 -06:00
|
|
|
if (input_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2))
|
2017-08-24 07:31:25 +08:00
|
|
|
FCEU_VSUniCoin(); /* Insert Coin VS System */
|
2017-02-16 20:30:24 -06:00
|
|
|
|
2017-08-24 07:31:25 +08:00
|
|
|
if (GameInfo->type == GIT_FDS) /* Famicom Disk System */
|
2015-09-24 12:52:41 -04:00
|
|
|
{
|
|
|
|
|
bool curL = input_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L);
|
2015-12-09 17:35:51 +00:00
|
|
|
bool curR = input_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R);
|
|
|
|
|
static bool prevL = false, prevR = false;
|
|
|
|
|
|
2015-09-24 12:52:41 -04:00
|
|
|
if (curL && !prevL)
|
2017-08-24 07:31:25 +08:00
|
|
|
FCEU_FDSSelect(); /* Swap FDisk side */
|
2015-09-24 12:52:41 -04:00
|
|
|
prevL = curL;
|
2015-12-09 17:35:51 +00:00
|
|
|
|
2015-09-24 12:52:41 -04:00
|
|
|
if (curR && !prevR)
|
2017-08-24 07:31:25 +08:00
|
|
|
FCEU_FDSInsert(-1); /* Insert or eject the disk */
|
2017-02-16 20:30:24 -06:00
|
|
|
prevR = curR;
|
2015-09-24 12:52:41 -04:00
|
|
|
}
|
2021-10-22 13:30:27 +01:00
|
|
|
|
|
|
|
|
/* Handle internal palette switching */
|
|
|
|
|
if (palette_prev || palette_next)
|
|
|
|
|
{
|
|
|
|
|
if (palette_switch_counter == 0)
|
|
|
|
|
{
|
|
|
|
|
int new_palette_index = palette_switch_get_current_index();
|
|
|
|
|
|
|
|
|
|
if (palette_prev)
|
|
|
|
|
{
|
|
|
|
|
if (new_palette_index > 0)
|
|
|
|
|
new_palette_index--;
|
|
|
|
|
else
|
|
|
|
|
new_palette_index = PAL_TOTAL - 1;
|
|
|
|
|
}
|
|
|
|
|
else /* palette_next */
|
|
|
|
|
{
|
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
|
|
|
if ((unsigned long)new_palette_index < PAL_TOTAL - 1)
|
2021-10-22 13:30:27 +01:00
|
|
|
new_palette_index++;
|
|
|
|
|
else
|
|
|
|
|
new_palette_index = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
palette_switch_set_index(new_palette_index);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
palette_switch_counter++;
|
|
|
|
|
if (palette_switch_counter >= PALETTE_SWITCH_PERIOD)
|
|
|
|
|
palette_switch_counter = 0;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
palette_switch_counter = 0;
|
2014-03-31 00:17:47 +02:00
|
|
|
}
|
|
|
|
|
|
core: stride-aware savestate, iNES2 helpers, -Wundef, -Wmissing-prototypes
Audit pass 5 - five distinct cleanups bundled into one omnibus.
1. Element-stride byte-swapping for savestate fields (state.h, state.c,
fceu-endian.{h,c})
The SFORMAT 's' field was previously {bit 31 = RLSB, bits 0..30 =
byte size}. RLSB triggers FlipByteOrder() on MSB hosts, which
reverses the entire entry buffer end-to-end. That is correct for a
single primitive (size <= 8 bytes) but wrong for an array of
multi-byte primitives - reversing the whole buffer would swap
element 0 with element N-1 and reverse their bytes too, scrambling
the data.
The previous workaround was either splitting an N-element array
into N separate single-primitive entries with distinct chunk IDs
(n106 PlayIndex split into IDX0..IDX7) or skipping the entry
entirely on big-endian hosts (the GEKKO #ifndef in vrc6.c / vrc7.c).
Both approaches mean BE saves are not portable to LE and vice
versa, and force the same workaround at every new array site.
This pass adds proper stride support:
* SFORMAT 's' encoding is now {bit 31 = RLSB, bits 24..30 =
stride in bytes (0 = legacy/unset), bits 0..23 = byte size}.
16 MiB max size, well above any actual savestate field.
* FCEUSTATE_RLSB_ARRAY(stride) macro for the new pattern.
* FlipByteOrderStrided() byte-swaps each element of an array
independently. Round-trip identity verified: [01 00 00 00 ...]
-> [00 00 00 01 ...] -> [01 00 00 00 ...].
* state.c's SubWrite / ReadStateChunk / CheckS use new helpers
sf_size() / sf_stride() / sf_flip() that mask the size out of
the new bit layout and dispatch to the strided variant when
stride < size.
Backwards compatible: legacy single-primitive entries (size == 1,
2, 4, 8) leave the stride bits at zero, which sf_stride() reads
as "stride equals size" and falls through to FlipByteOrder() as
before. No on-disk format change. Existing single-primitive RLSB
sites are unchanged.
The infrastructure is now in place so any future SFORMAT entry
that is an array of multi-byte primitives can be expressed as a
single entry (e.g. "{ buf, sizeof(buf) | FCEUSTATE_RLSB_ARRAY(4),
"BUF." }") without splitting or skipping. The existing PlayIndex
split and GEKKO #ifndefs are intentionally left untouched -
migrating them would alter the on-disk savestate format and is a
separate decision.
2. iNES1-vs-iNES2 sizing helpers (cart.h)
Twelve sites across the codebase encoded the same conditional:
info->iNES2 ? (info->PRGRamSize + info->PRGRamSaveSize) : default
Sometimes for PRGRAM, sometimes for CHRRAM, sometimes in bytes,
sometimes after dividing by 1024. The pattern is verbose and easy
to write inconsistently.
Added two inline helpers in cart.h:
- CartInfo_PRGRAM_bytes(info, default_bytes)
- CartInfo_CHRRAM_bytes(info, default_bytes)
Migrated 9 of the 13 sites: cartram.c (2), 162.c, 163.c, 134.c,
399.c, 478.c, 480.c, 484.c. The remaining 4 are non-helper-fitting
variants (164.c special masking, 2 cartram SaveGameLen sites with
different fallback semantics, mmc3.c Boogerman submapper detection).
3. -Wundef enabled permanently (Makefile.libretro)
Zero warnings out of the box - no #if-on-undefined-macro footguns
in the codebase. Now part of WARNING_DEFINES alongside the existing
-Wsign-compare.
4. -Wmissing-prototypes enabled permanently (Makefile.libretro)
Started at 198 warnings, cleared all of them:
* Mass-static-ified ~96 functions across 75 files that were
defined non-static but only used within their own translation
unit. (See static_prototype_fixer.py in the development notes.)
* K&R-style empty-parens prototypes "()" replaced with explicit
"(void)" across all asic_*.{c,h} files - GCC treats "()" as
"any args" and refuses to match it against a separate K&R
definition.
* Added missing forward declarations to public headers:
- fds.h (FDSLoad)
- nsf.h (NSFLoad)
- ines.h (iNESLoad)
- unif.h (UNIFLoad)
- latch.h (LatchHardReset, K&R fix)
- eeprom_93Cx6.h (eeprom_93Cx6_read, K&R fix)
Each header gained an "#include "file.h"" where needed.
* fds_apu.c now includes its own fds_apu.h header (was missing).
* fds_apu.h: removed unused FDSSoundRead declaration (the function
is internal-static).
* cartram.h: removed unused CartRAM_close declaration (the function
is internal-static).
* input.h: added a centralised block of FCEU_Init* prototypes
(Zapper, Mouse, Powerpad, Arkanoid, VirtualBoy, FKB, SuborKB,
PEC586KB, HS, Mahjong, FamilyTrainerA/B, OekaKids, TopRider,
BarcodeWorld, BattleBox, QuizKing, FTrainerA/B, SpaceShadow,
LCDCompZapper, ArkanoidFC) plus FCEU_ZapperSetTolerance. These
were previously declared as "extern" inside src/input.c.
* Static-ified FP_FASTAPASS callbacks in 106.c, 65.c, 67.c,
asic_h3001.c, asic_vrc3.c (those with no external callers);
left non-static for those that have header decls or are
referenced from sibling .c files (asic_mmc1, asic_vrc6,
asic_vrc7, flashrom).
* For a small set of cross-file functions where adding a header
was disproportionate to the value (MMC5_hb, NSFMMC5_Close,
GetKeyboard, FCEU_GetJoyJoy), placed a forward declaration
immediately above the definition. This satisfies
-Wmissing-prototypes (which checks for any prior declaration
in scope) without churning the public-header layout.
5. -Wshadow partial cleanup (not enabled permanently)
Fixed five real shadows that were either bugs or actively
misleading:
* src/boards/476.c: removed an inner "int i" that shadowed the
outer loop counter.
* src/boards/mmc5.c MMC5_hb: parameter "scanline" renamed to
"sl_param" (was shadowing the global "scanline").
* src/boards/n106.c DoNamcoSound: parameter "Wave" renamed to
"WaveBuf" (was shadowing the global Wave audio buffer); also
updated the forward declaration and the matching parameter
on sound.h's NeoFill function pointer typedef.
* src/boards/vrc7.c UpdateOPLNEO: same Wave -> WaveBuf rename.
* src/ntsc/nes_ntsc_impl.h: renamed an inner loop counter "n"
that shadowed an outer "n".
* src/drivers/libretro/libretro.c FCEUD_RegionOverride: local
"pal" renamed to "is_pal" (was shadowing the typedef "pal" from
palette.h).
* src/palette.c FCEUI_SetPaletteArray: parameter "pal" renamed
to "data" (same shadow); driver.h declaration updated to match.
-Wshadow itself is NOT enabled permanently because the remaining
warnings are deliberate parameter naming conventions (XBuf in
draw functions, X in cpu hooks) and third-party blargg ntsc code.
In addition, four files were touched as part of an MSVC-build fix
that came up mid-pass: src/fds.c, src/nsf.c, src/ines.c, and
src/drivers/libretro/libretro_dipswitch.c had snprintf() calls
introduced in pass 4 that fail to link on pre-MSVC2015 toolchains
when STATIC_LINKING=1 (the libretro-common compat_snprintf.c shim
isn't compiled in those configurations). Replaced each snprintf with
either sprintf-into-bounded-buffer (the format strings have known
maximum output) or strlcpy/strlcat for the dipswitch key-build case.
All output is still bounded; truncation happens via strl*'s normal
truncation semantics where applicable.
All added code is C89-clean (top-of-block declarations only, no
mixed decls, no // comments, INLINE macro from fceu-types.h instead
of bare "inline"). Builds clean under -std=gnu11 with -Wno-write-
strings -Wsign-compare -Wundef -Wmissing-prototypes; zero errors,
zero warnings.
Determinism audit (audit_determinism.py): no rand/time/long
double/threads issues introduced.
2026-05-04 04:44:52 +02:00
|
|
|
static void FCEUD_Update(uint8_t *XBuf, int32_t *Buffer, int Count)
|
2014-03-31 04:32:33 +02:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-28 12:41:37 +02:00
|
|
|
static void retro_run_blit(uint8_t *gfx)
|
2014-03-31 00:17:47 +02:00
|
|
|
{
|
2015-08-28 12:41:37 +02:00
|
|
|
unsigned x, y;
|
2016-02-19 05:17:47 +01:00
|
|
|
#ifdef PSP
|
|
|
|
|
static unsigned int __attribute__((aligned(16))) d_list[32];
|
2016-02-19 06:39:31 +01:00
|
|
|
void* texture_vram_p = NULL;
|
2016-02-19 05:17:47 +01:00
|
|
|
#endif
|
2015-08-28 12:41:37 +02:00
|
|
|
unsigned width = 256;
|
|
|
|
|
unsigned height = 240;
|
2025-09-01 22:07:53 +08:00
|
|
|
unsigned pitch = width * sizeof(bpp_t);
|
2014-03-31 04:32:33 +02:00
|
|
|
|
2017-09-02 02:40:28 +08:00
|
|
|
#ifdef PSP
|
2020-10-02 19:24:00 +08:00
|
|
|
if (crop_overscan)
|
2015-08-28 12:41:37 +02:00
|
|
|
{
|
|
|
|
|
width -= 16;
|
|
|
|
|
height -= 16;
|
|
|
|
|
}
|
2017-09-08 02:22:16 +08:00
|
|
|
texture_vram_p = (void*) (0x44200000 - (256 * 256)); /* max VRAM address - frame size */
|
2014-03-30 22:15:17 +02:00
|
|
|
|
2014-03-30 22:35:00 +02:00
|
|
|
sceKernelDcacheWritebackRange(retro_palette,256 * 2);
|
|
|
|
|
sceKernelDcacheWritebackRange(XBuf, 256*240 );
|
2014-03-30 22:15:17 +02:00
|
|
|
|
2014-03-30 22:35:00 +02:00
|
|
|
sceGuStart(GU_DIRECT, d_list);
|
2014-03-30 22:15:17 +02:00
|
|
|
|
2015-08-28 12:17:56 +02:00
|
|
|
/* sceGuCopyImage doesnt seem to work correctly with GU_PSM_T8
|
|
|
|
|
* so we use GU_PSM_4444 ( 2 Bytes per pixel ) instead
|
|
|
|
|
* with half the values for pitch / width / x offset
|
|
|
|
|
*/
|
2020-10-02 19:24:00 +08:00
|
|
|
if (crop_overscan)
|
2014-03-30 22:35:00 +02:00
|
|
|
sceGuCopyImage(GU_PSM_4444, 4, 4, 120, 224, 128, XBuf, 0, 0, 128, texture_vram_p);
|
2020-10-02 19:24:00 +08:00
|
|
|
else
|
|
|
|
|
sceGuCopyImage(GU_PSM_4444, 0, 0, 128, 240, 128, XBuf, 0, 0, 128, texture_vram_p);
|
2014-03-30 22:15:17 +02:00
|
|
|
|
2014-03-30 22:35:00 +02:00
|
|
|
sceGuTexSync();
|
|
|
|
|
sceGuTexImage(0, 256, 256, 256, texture_vram_p);
|
|
|
|
|
sceGuTexMode(GU_PSM_T8, 0, 0, GU_FALSE);
|
|
|
|
|
sceGuTexFunc(GU_TFX_REPLACE, GU_TCC_RGB);
|
|
|
|
|
sceGuDisable(GU_BLEND);
|
|
|
|
|
sceGuClutMode(GU_PSM_5650, 0, 0xFF, 0);
|
|
|
|
|
sceGuClutLoad(32, retro_palette);
|
2014-03-30 22:15:17 +02:00
|
|
|
|
2014-03-30 22:35:00 +02:00
|
|
|
sceGuFinish();
|
2015-03-19 23:16:52 +01:00
|
|
|
|
2015-08-28 12:41:37 +02:00
|
|
|
video_cb(texture_vram_p, width, height, 256);
|
2019-01-21 22:19:46 +01:00
|
|
|
#elif defined(RENDER_GSKIT_PS2)
|
|
|
|
|
uint32_t *buf = (uint32_t *)RETRO_HW_FRAME_BUFFER_VALID;
|
|
|
|
|
|
2019-02-16 18:41:46 +01:00
|
|
|
if (!ps2) {
|
|
|
|
|
if (!environ_cb(RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE, (void **)&ps2) || !ps2) {
|
2021-10-19 17:33:56 +01:00
|
|
|
FCEU_printf(" Failed to get HW rendering interface!\n");
|
2019-02-16 18:41:46 +01:00
|
|
|
return;
|
2020-02-29 09:48:09 +08:00
|
|
|
}
|
2019-01-21 22:19:46 +01:00
|
|
|
|
2019-02-16 18:41:46 +01:00
|
|
|
if (ps2->interface_version != RETRO_HW_RENDER_INTERFACE_GSKIT_PS2_VERSION) {
|
2021-10-19 17:33:56 +01:00
|
|
|
FCEU_printf(" HW render interface mismatch, expected %u, got %u!\n",
|
2019-02-16 18:41:46 +01:00
|
|
|
RETRO_HW_RENDER_INTERFACE_GSKIT_PS2_VERSION, ps2->interface_version);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-01-21 22:19:46 +01:00
|
|
|
|
|
|
|
|
ps2->coreTexture->Width = width;
|
|
|
|
|
ps2->coreTexture->Height = height;
|
|
|
|
|
ps2->coreTexture->PSM = GS_PSM_T8;
|
|
|
|
|
ps2->coreTexture->ClutPSM = GS_PSM_CT16;
|
|
|
|
|
ps2->coreTexture->Filter = GS_FILTER_LINEAR;
|
2023-03-10 21:47:35 -05:00
|
|
|
ps2->padding = (struct retro_hw_ps2_insets){ (float) crop_overscan_v_top,
|
|
|
|
|
(float) crop_overscan_h_left,
|
|
|
|
|
(float) crop_overscan_v_bottom,
|
|
|
|
|
(float) crop_overscan_h_right };
|
2019-01-21 22:19:46 +01:00
|
|
|
}
|
|
|
|
|
|
2019-02-22 00:05:24 +01:00
|
|
|
ps2->coreTexture->Clut = (u32*)retro_palette;
|
2019-02-16 18:41:46 +01:00
|
|
|
ps2->coreTexture->Mem = (u32*)gfx;
|
2019-01-21 22:19:46 +01:00
|
|
|
|
|
|
|
|
video_cb(buf, width, height, pitch);
|
2015-08-28 12:41:37 +02:00
|
|
|
#else
|
2020-01-26 10:22:42 +08:00
|
|
|
#ifdef HAVE_NTSC_FILTER
|
|
|
|
|
if (use_ntsc)
|
|
|
|
|
{
|
2020-02-25 11:51:41 +08:00
|
|
|
burst_phase ^= 1;
|
2020-02-27 20:23:08 +08:00
|
|
|
if (ntsc_setup.merge_fields)
|
|
|
|
|
burst_phase = 0;
|
2020-01-26 10:22:42 +08:00
|
|
|
|
2020-05-05 09:07:55 +08:00
|
|
|
nes_ntsc_blit(&nes_ntsc, (NES_NTSC_IN_T const*)gfx, (NES_NTSC_IN_T *)XDBuf,
|
2020-02-27 20:23:08 +08:00
|
|
|
NES_WIDTH, burst_phase, NES_WIDTH, NES_HEIGHT,
|
2025-09-01 22:07:53 +08:00
|
|
|
ntsc_video_out, NES_NTSC_WIDTH * sizeof(bpp_t));
|
2020-01-26 10:22:42 +08:00
|
|
|
|
2023-03-10 21:47:35 -05:00
|
|
|
width = NES_WIDTH - crop_overscan_h_left - crop_overscan_h_right;
|
2020-10-02 19:17:55 +08:00
|
|
|
width = NES_NTSC_OUT_WIDTH(width);
|
2023-03-10 21:47:35 -05:00
|
|
|
height = NES_HEIGHT - crop_overscan_v_top - crop_overscan_v_bottom;
|
2026-05-04 07:16:54 +02:00
|
|
|
pitch = NES_NTSC_WIDTH * sizeof(bpp_t);
|
|
|
|
|
|
|
|
|
|
/* Pass ntsc_video_out directly to the frontend with the wider
|
|
|
|
|
* source pitch instead of memcpy'ing into a tightly-packed
|
|
|
|
|
* fceu_video_out. video_cb's pitch parameter already lets the
|
|
|
|
|
* frontend skip past the unused right margin per scanline; the
|
|
|
|
|
* memcpy was redundant. Saves ~580 KB per frame at 32 bpp full
|
|
|
|
|
* NES_NTSC_OUT_WIDTH * NES_HEIGHT - i.e. ~35 MB/s of bandwidth
|
|
|
|
|
* at 60 fps. */
|
2020-01-26 10:22:42 +08:00
|
|
|
{
|
2026-05-04 07:16:54 +02:00
|
|
|
int32_t h_offset = (crop_overscan_h_left ? NES_NTSC_OUT_WIDTH(crop_overscan_h_left) : 0);
|
|
|
|
|
int32_t v_offset = crop_overscan_v_top;
|
2025-09-01 22:07:53 +08:00
|
|
|
const bpp_t *in = ntsc_video_out + h_offset + NES_NTSC_WIDTH * v_offset;
|
2026-05-04 07:16:54 +02:00
|
|
|
video_cb(in, width, height, pitch);
|
2020-02-27 20:23:08 +08:00
|
|
|
}
|
2014-03-30 22:35:00 +02:00
|
|
|
}
|
2015-08-28 12:34:59 +02:00
|
|
|
else
|
2020-01-26 10:22:42 +08:00
|
|
|
#endif /* HAVE_NTSC_FILTER */
|
2015-08-28 12:34:59 +02:00
|
|
|
{
|
2023-03-10 21:47:35 -05:00
|
|
|
width -= (crop_overscan_h_left + crop_overscan_h_right);
|
|
|
|
|
height -= (crop_overscan_v_top + crop_overscan_v_bottom);
|
2026-05-04 07:16:54 +02:00
|
|
|
pitch = width * sizeof(bpp_t);
|
2023-03-10 21:47:35 -05:00
|
|
|
gfx += (crop_overscan_v_top * 256) + crop_overscan_h_left;
|
2020-01-26 10:22:42 +08:00
|
|
|
|
|
|
|
|
{
|
2026-05-04 07:16:54 +02:00
|
|
|
/* Try GET_CURRENT_SOFTWARE_FRAMEBUFFER for zero-copy: if the
|
|
|
|
|
* frontend can hand us its own scanout buffer, we write the
|
|
|
|
|
* pixel-format conversion directly into it and skip a round
|
|
|
|
|
* trip through fceu_video_out. */
|
|
|
|
|
struct retro_framebuffer fb = {0};
|
|
|
|
|
bpp_t *target = fceu_video_out;
|
|
|
|
|
size_t target_stride = (size_t)width; /* in pixels */
|
|
|
|
|
|
|
|
|
|
fb.width = width;
|
|
|
|
|
fb.height = height;
|
|
|
|
|
fb.access_flags = RETRO_MEMORY_ACCESS_WRITE;
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_CURRENT_SOFTWARE_FRAMEBUFFER, &fb)
|
|
|
|
|
&& fb.format == active_pixformat
|
|
|
|
|
&& fb.data
|
|
|
|
|
&& (fb.pitch % sizeof(bpp_t)) == 0)
|
|
|
|
|
{
|
|
|
|
|
target = (bpp_t *)fb.data;
|
|
|
|
|
target_stride = fb.pitch / sizeof(bpp_t);
|
|
|
|
|
pitch = fb.pitch;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Hoist loop-invariant decisions out of the hot per-pixel
|
|
|
|
|
* loop. The PPU writes a uniform colour_emphasis byte to
|
|
|
|
|
* every entry of XDBuf for a given scanline (ppu.c:683-685),
|
|
|
|
|
* so emphasis is constant per row - branch once per row, not
|
|
|
|
|
* per pixel. NSF and use_raw_palette flags don't change mid-
|
|
|
|
|
* frame either. */
|
2024-10-15 22:35:46 +08:00
|
|
|
{
|
2026-05-04 07:16:54 +02:00
|
|
|
const uint8_t *deemp_row = XDBuf + (gfx - XBuf);
|
|
|
|
|
const bool is_nsf = (GameInfo->type == GIT_NSF);
|
|
|
|
|
const uint8_t pixel_mask = use_raw_palette ? 0x3F : 0xFF;
|
|
|
|
|
|
|
|
|
|
for (y = 0; y < height; y++)
|
2024-10-15 22:35:46 +08:00
|
|
|
{
|
2026-05-04 07:16:54 +02:00
|
|
|
bpp_t *out_row = target + (size_t)y * target_stride;
|
|
|
|
|
uint8_t deemp = is_nsf ? 0 : deemp_row[0];
|
|
|
|
|
|
|
|
|
|
if (deemp != 0)
|
2024-10-15 22:35:46 +08:00
|
|
|
{
|
2026-05-04 07:16:54 +02:00
|
|
|
unsigned base = 256u + ((unsigned)(deemp & 0x07) << 6);
|
|
|
|
|
for (x = 0; x < width; x++)
|
|
|
|
|
out_row[x] = retro_palette[base + (gfx[x] & 0x3F)];
|
2024-10-15 22:35:46 +08:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2026-05-04 07:16:54 +02:00
|
|
|
for (x = 0; x < width; x++)
|
|
|
|
|
out_row[x] = retro_palette[gfx[x] & pixel_mask];
|
2024-10-15 17:02:00 +08:00
|
|
|
}
|
2026-05-04 07:16:54 +02:00
|
|
|
gfx += 256;
|
|
|
|
|
deemp_row += 256;
|
2024-10-15 17:02:00 +08:00
|
|
|
}
|
2024-10-15 22:35:46 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-04 07:16:54 +02:00
|
|
|
video_cb(target, width, height, pitch);
|
|
|
|
|
}
|
2015-08-28 12:34:59 +02:00
|
|
|
}
|
|
|
|
|
#endif
|
2015-08-28 12:41:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void retro_run(void)
|
|
|
|
|
{
|
|
|
|
|
uint8_t *gfx;
|
2022-09-05 01:27:12 +02:00
|
|
|
int32_t ssize = 0;
|
|
|
|
|
bool updated = false;
|
2014-03-30 22:35:00 +02:00
|
|
|
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated)
|
2017-01-02 03:36:39 +01:00
|
|
|
check_variables(false);
|
2015-08-28 12:41:37 +02:00
|
|
|
|
2016-06-29 09:24:14 +02:00
|
|
|
FCEUD_UpdateInput();
|
|
|
|
|
FCEUI_Emulate(&gfx, &sound, &ssize, 0);
|
2015-08-28 12:41:37 +02:00
|
|
|
|
2022-04-06 13:36:22 +01:00
|
|
|
retro_run_blit(gfx);
|
2015-08-28 12:41:37 +02:00
|
|
|
|
2022-04-06 13:36:22 +01:00
|
|
|
stereo_filter_apply(sound, ssize);
|
2015-08-28 12:41:37 +02:00
|
|
|
audio_batch_cb((const int16_t*)sound, ssize);
|
2014-03-30 22:15:17 +02:00
|
|
|
}
|
|
|
|
|
|
2014-03-30 22:35:00 +02:00
|
|
|
size_t retro_serialize_size(void)
|
|
|
|
|
{
|
|
|
|
|
if (serialize_size == 0)
|
|
|
|
|
{
|
|
|
|
|
/* Something arbitrarily big.*/
|
|
|
|
|
uint8_t *buffer = (uint8_t*)malloc(1000000);
|
core: memory-safety, leak, and savestate-portability audit fixes
Squashed series of ~50 distinct bugs found during a multi-day
security/correctness audit, ranging from ROM-triggerable heap
corruption and savestate-triggered OOB read primitives down to
obscure cross-platform savestate breakage.
Build is clean on `make platform=unix` with zero new warnings.
CRITICAL (ROM-triggerable, exploitable on the user's machine)
=============================================================
* ines.c iNES 2.0 PRG/CHR exponent overflow leading to undersized
allocation followed by heap buffer overflow on fread (pow() with
attacker-chosen exponent up to 63). Cap exponent at 30 and
compute size in uint32 with explicit cap below 2 GiB before
storing in int.
* ines.c miscROMSize int wraparound from attacker-controlled PRG/
CHR sizes; previous '& 0x8000000' check missed common underflow
cases. Compute in int64 and reject <= 0 or > 128 MiB.
* unif.c MAPR chunk OOB heap read on chunks of size < 4. Validate
chunk size before allocating board-name buffer.
* unif.c chunk size truncation: int conversion + missing cap allowed
a 4 GiB chunk size to wrap. Cap at 16 MiB.
* unif.c FixRomSize infinite loop on size > 0x80000000. Cap input.
* unif.c DINF chunk: months[(m - 1) % 12] is UB for m==0; m comes
from the .unf file. Clamp m into [1..12] before subtracting.
* unif.c NAME chunk: GameInfo->name = malloc(...) was unchecked.
* nsf.c size underflow: FCEU_fgetsize() - 0x80 wraps to ~UINT64_MAX
on tiny files, propagating a huge size into uppow2/FCEU_malloc.
Validate size > 0x80 before subtracting.
* nsf.c NSFMaxBank * 4096 cap tightened from 1<<20 (UB on signed-int
overflow) to 1<<19 (fits in int).
* general.c uppow2 had signed-int UB (1 << 32) and wrapped to 0 for
inputs near uint32 max. Cap at 0x80000000.
* cheat.c SubCheats[256] BSS overflow when more than 256 GG/PAR
substitution cheats are active. The array is adjacent to MMapPtrs[64]
in BSS which is exposed via retro_get_memory_data, making this
overflow visible from outside the core.
* libretro.c retro_cheat_set strcpy stack overflow with arbitrary-
length cheat strings from the frontend. Use strlcpy.
CRITICAL (savestate-triggerable on a malicious .fcs file)
=========================================================
* fds.c FDS savestate OOB read primitive: InDisk loaded from
savestate is used as index into 8-element diskdata[] static
pointer array. A value 8..254 dereferences arbitrary memory as
a pointer (heap-read primitive). Also bounds-checked SelectDisk,
mapperFDS_block, mapperFDS_blockstart, mapperFDS_diskaddr,
mapperFDS_blocklen so blockstart+diskaddr stays within the 65500-
byte disk buffer.
* 11 mappers had savestate-loaded variables masked at write time
but not at restore time, used as indices into fixed arrays:
- 88.c, KS7037.c, 112.c cmd indexes reg[8]
- sachen.c (S8259, S74LS374N) cmd indexes latch[8]
- 357.c dipswitch indexes outer_bank[4]
- unrom512.c flash_state indexes erase_a/d/b[5]
- 368.c preg indexes banks[8]
- 69.c sndcmd indexes sreg[14]
- mmc2and4.c latch0/latch1 index creg[4]
None individually exploitable into RCE - the array writes corrupt
adjacent BSS with constrained data flowing in - but each is an
out-of-bounds read or write from attacker-controllable input.
HIGH (memory-safety, reachable on any load)
===========================================
* fceu-memory.c FCEU_malloc deref-of-NULL on allocation failure
('ret = 0; memset(ret, 0, size);').
* file.c multiple FCEUFILE/MakeMemWrap/MakeMemWrapBuffer unchecked
allocations and unchecked filestream_tell return.
* libretro.c GameInfo NULL-derefs in three entry points
(retro_set_controller_port_device, retro_get_memory_data,
retro_get_memory_size) reachable on operations called before a
successful load.
* libretro.c framebuffer leak ~256 KB per failed FCEUI_LoadGame
(the libretro frontend doesn't call retro_unload_game on a
failed load).
* libretro.c 3DS retro_deinit unchecked linearFree.
* libretro.c stereo / NTSC filter unchecked malloc returns.
* fceu.c FCEUI_LoadGame unchecked GameInfo malloc.
* fds.c SubLoad and FDSLoad unchecked FCEU_malloc on diskdatao
backup buffers (NULL-deref in subsequent memcpy).
* fceu.c AllocGenieRW partial-failure leak: AReadG allocated but
BWriteG malloc fails -> 256 KB leak per retry.
* input/bworld.c Update(): unbounded strcpy from attacker-supplied
data into 20-byte bdata. Replaced with length-bounded copy.
* cart.c setprg2r/4r and setchr1r/2r/4r/8r mask-underflow guards.
SetupCartPRGMapping/SetupCartCHRMapping compute (size >> N) - 1
which underflows to 0xFFFFFFFF for chips smaller than the unit.
setprg8r/16r/32r and setchr8r already had defensive size checks;
the smaller units did not. malee.c (2 KB chip) and mapper 218
(2 KB NTARAM-as-CHR) accidentally avoid OOB; the primitive is
unsafe for any future board.
* video.c FCEU_InitVirtualVideo XBuf/XDBuf partial-failure leak.
* video.c, fceu.c FCEU_DispMessage / FCEU_printf / FCEU_PrintError:
vsprintf into fixed stack buffers replaced with vsnprintf.
* core: validate magic-string read length on FDS/UNIF/NSF load
(reject files too short to contain the magic so previous static
buffer / stack garbage doesn't spuriously match).
LEAKS (every cart load/unload cycle)
====================================
* mmc5.c MMC5 cart-side: WRAM (up to 64K) + MMC5fill (1K) + ExRAM
(1K) leaked per cycle. NSFMMC5_Close existed but was only used
for NSF code path.
* onebus.c Mapper 270/436: 8 KB CHRRAM leaked per cycle.
* 330.c, 375.c, 528.c: 8 KB WRAM each, no Close at all.
CORRECTNESS (UB / ABI / endianness)
===================================
* fceu-endian.c FlipByteOrder loop bound was 'count' instead of
'count/2', making it a no-op for every even count and silently
breaking savestate portability for every FCEUSTATE_RLSB-marked
field. The '#ifndef GEKKO' workarounds scattered through the
codebase (sound.c, vrc7.c, vrc6.c) were symptoms of this root
cause.
* state.c AddExState bounds check ran AFTER writing the entry, so
when SFEXINDEX hit the 63-entry cap the next call would write
the entry then immediately overwrite it with the terminator.
* state.c FCEUSS_Save_Mem: post-save callback was guarded by
'if (SPreSave)' instead of 'if (SPostSave)'.
* Sequence-point UB in counter expressions across 4 board files,
e.g. 'x = !x ? a : --x' and 'x = ++x % n'. GCC -Wsequence-point
flags all five sites (285.c, 413.c, asic_mmc3.c, jyasic.c).
* SFORMAT size mismatches between declared variable type and
AddExState size argument:
- asic_vrc3.c VRC3_count/VRC3_reload (uint16, saved as 1 byte)
- mmc3.c m555_count (uint32, saved as 2 bytes), m555_count_-
expired (uint8, saved as 2 bytes - OOB write into adjacent
BSS).
* unrom512.c UNROM-512 mapper 30 .srm save format: host-endian
uint32 flash write counters. Now stored as LE on disk regardless
of host (Battle Kid 2, Twin Dragons, Lizard, Sole, etc.).
* ~70 multi-byte single-variable SFORMAT/AddExState entries across
36 board files were saved as host-byte-order. After the
FlipByteOrder fix, these are now byte-swapped on BE so cross-
platform savestates round-trip correctly.
* coolgirl.c new ExStateLE() macro added; 22 multi-byte sites.
* pic16c5x.c 11 multi-byte AddExState calls (m_PC, m_PREVPC,
m_CONFIG, m_WDT, m_prescaler, m_opcode, m_STACK[0/1], m_icount,
m_delay_timer, m_rtcc, m_inst_cycles, m_clock2cycle).
* onebus.c PowerJoy Supermax submapper detection used non-portable
*(uint32*)&info->MD5 cast that read different bytes on LE vs BE.
* fds.c clean up redundant FCEUSTATE_RLSB encoding in AddExState
calls that also passed type=1 (idempotent OR; readability fix).
DOCUMENTATION
=============
* input.c UpdateGP's *(uint32*)data cast looks like a typical
endian bug but is actually correct (the libretro frontend builds
JSReturn with matching host-uint32 shifts). Comment added to
prevent future "fixes" from breaking it.
Limitations not addressed
=========================
* Element-stride-aware byte swapping. The savestate byte-swap
mechanism (FlipByteOrder over the entire SFORMAT entry buffer)
is structurally wrong for arrays of multi-byte values: it
reverses the whole buffer end-to-end instead of byte-swapping
each element. Several places that need cross-platform-portable
arrays (VRC7 sound state, jyasic chr[8]) work around this by
either splitting arrays into per-element SFORMAT entries (n106
PlayIndex, bandai reg) or by skipping save entirely on BE via
#ifndef GEKKO. A proper fix would extend the size encoding with
an element-stride field. Left for a future change because it
would change the savestate format.
* Strict-aliasing UB in ppu.c (around 9 sites doing
*(uint32*)uint8_buf for fast 4-byte writes via FCEU_dwmemset).
Works in practice with all common compilers because the patterns
are byte-symmetric, but is formally UB.
* FCEU_gmalloc calls exit(1) on OOM. A libretro core should never
exit() because that takes down the whole frontend. Used by
100+ call sites; refactoring to return-NULL is out of scope here.
Testing
=======
* Build: clean on `make platform=unix` with -O2; no new warnings.
* FlipByteOrder fix verified by hand-trace and a standalone unit
test for counts 2, 4, 8.
* uppow2 fix verified by unit test across 13 boundary cases.
* SFORMAT size mismatches and missing-RLSB cases identified by
Python static-analysis scripts that cross-reference SFORMAT
entries against variable declarations.
* iNES 2.0 exponent fix verified by hand-tracing what byte 0xFF
produces post-fix: exp=30 (capped), mult=7, size=3 GiB nominal,
capped to 1 GiB, capped to 2 GiB by uppow2, FCEU_malloc returns
NULL on most systems, loader returns 0. No heap overflow for
any input byte.
* Savestate-loaded array index audit: built a Python scanner that
extracts each AddExState/SFORMAT entry and cross-references the
variable name against array-index uses in the same file. All
flagged sites covered.
* A libFuzzer harness and seed corpus generator (fuzz_main.c,
gen_seed_corpus.py) accompany this submission for ongoing
regression testing.
2026-05-04 02:15:40 +02:00
|
|
|
if (!buffer)
|
|
|
|
|
return 0;
|
2014-03-30 22:35:00 +02:00
|
|
|
memstream_set_buffer(buffer, 1000000);
|
2014-03-30 22:15:17 +02:00
|
|
|
|
2014-12-08 20:15:54 +01:00
|
|
|
FCEUSS_Save_Mem();
|
2014-03-30 22:35:00 +02:00
|
|
|
serialize_size = memstream_get_last_size();
|
|
|
|
|
free(buffer);
|
|
|
|
|
}
|
2014-03-30 22:15:17 +02:00
|
|
|
|
2014-03-30 22:35:00 +02:00
|
|
|
return serialize_size;
|
2014-03-30 22:15:17 +02:00
|
|
|
}
|
|
|
|
|
|
2014-03-30 22:35:00 +02:00
|
|
|
bool retro_serialize(void *data, size_t size)
|
|
|
|
|
{
|
2021-10-21 15:52:40 +01:00
|
|
|
/* Cannot save state while Game Genie
|
|
|
|
|
* screen is open */
|
|
|
|
|
if (geniestage == 1)
|
|
|
|
|
return false;
|
|
|
|
|
|
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
|
|
|
if (!data || size != retro_serialize_size())
|
2014-03-30 22:35:00 +02:00
|
|
|
return false;
|
2014-03-30 22:15:17 +02:00
|
|
|
|
2014-03-30 22:35:00 +02:00
|
|
|
memstream_set_buffer((uint8_t*)data, size);
|
2014-12-08 20:15:54 +01:00
|
|
|
FCEUSS_Save_Mem();
|
2014-03-30 22:35:00 +02:00
|
|
|
return true;
|
2014-03-30 22:15:17 +02:00
|
|
|
}
|
|
|
|
|
|
2014-03-30 22:35:00 +02:00
|
|
|
bool retro_unserialize(const void * data, size_t size)
|
|
|
|
|
{
|
2021-10-21 15:52:40 +01:00
|
|
|
/* Cannot load state while Game Genie
|
|
|
|
|
* screen is open */
|
|
|
|
|
if (geniestage == 1)
|
|
|
|
|
return false;
|
|
|
|
|
|
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
|
|
|
if (!data || size != retro_serialize_size())
|
2014-03-30 22:35:00 +02:00
|
|
|
return false;
|
2014-03-30 22:15:17 +02:00
|
|
|
|
2014-03-30 22:35:00 +02:00
|
|
|
memstream_set_buffer((uint8_t*)data, size);
|
2014-12-08 20:15:54 +01:00
|
|
|
FCEUSS_Load_Mem();
|
2014-03-30 22:35:00 +02:00
|
|
|
return true;
|
2014-03-30 22:15:17 +02:00
|
|
|
}
|
|
|
|
|
|
2020-09-09 11:33:36 +08:00
|
|
|
static int checkGG(char c)
|
|
|
|
|
{
|
|
|
|
|
static const char lets[16] = { 'A', 'P', 'Z', 'L', 'G', 'I', 'T', 'Y', 'E', 'O', 'X', 'U', 'K', 'S', 'V', 'N' };
|
|
|
|
|
int x;
|
|
|
|
|
|
|
|
|
|
for (x = 0; x < 16; x++)
|
|
|
|
|
if (lets[x] == toupper(c))
|
|
|
|
|
return 1;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int GGisvalid(const char *code)
|
|
|
|
|
{
|
|
|
|
|
size_t len = strlen(code);
|
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 i;
|
2022-06-09 13:58:15 +08:00
|
|
|
|
2020-09-09 11:33:36 +08:00
|
|
|
if (len != 6 && len != 8)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < len; i++)
|
|
|
|
|
if (!checkGG(code[i]))
|
|
|
|
|
return 0;
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-30 22:35:00 +02:00
|
|
|
void retro_cheat_reset(void)
|
2014-12-08 17:33:02 +01:00
|
|
|
{
|
|
|
|
|
FCEU_ResetCheats();
|
|
|
|
|
}
|
2014-03-30 22:15:17 +02:00
|
|
|
|
2014-12-08 17:33:02 +01:00
|
|
|
void retro_cheat_set(unsigned index, bool enabled, const char *code)
|
|
|
|
|
{
|
|
|
|
|
char name[256];
|
core: memory-safety, leak, and savestate-portability audit fixes
Squashed series of ~50 distinct bugs found during a multi-day
security/correctness audit, ranging from ROM-triggerable heap
corruption and savestate-triggered OOB read primitives down to
obscure cross-platform savestate breakage.
Build is clean on `make platform=unix` with zero new warnings.
CRITICAL (ROM-triggerable, exploitable on the user's machine)
=============================================================
* ines.c iNES 2.0 PRG/CHR exponent overflow leading to undersized
allocation followed by heap buffer overflow on fread (pow() with
attacker-chosen exponent up to 63). Cap exponent at 30 and
compute size in uint32 with explicit cap below 2 GiB before
storing in int.
* ines.c miscROMSize int wraparound from attacker-controlled PRG/
CHR sizes; previous '& 0x8000000' check missed common underflow
cases. Compute in int64 and reject <= 0 or > 128 MiB.
* unif.c MAPR chunk OOB heap read on chunks of size < 4. Validate
chunk size before allocating board-name buffer.
* unif.c chunk size truncation: int conversion + missing cap allowed
a 4 GiB chunk size to wrap. Cap at 16 MiB.
* unif.c FixRomSize infinite loop on size > 0x80000000. Cap input.
* unif.c DINF chunk: months[(m - 1) % 12] is UB for m==0; m comes
from the .unf file. Clamp m into [1..12] before subtracting.
* unif.c NAME chunk: GameInfo->name = malloc(...) was unchecked.
* nsf.c size underflow: FCEU_fgetsize() - 0x80 wraps to ~UINT64_MAX
on tiny files, propagating a huge size into uppow2/FCEU_malloc.
Validate size > 0x80 before subtracting.
* nsf.c NSFMaxBank * 4096 cap tightened from 1<<20 (UB on signed-int
overflow) to 1<<19 (fits in int).
* general.c uppow2 had signed-int UB (1 << 32) and wrapped to 0 for
inputs near uint32 max. Cap at 0x80000000.
* cheat.c SubCheats[256] BSS overflow when more than 256 GG/PAR
substitution cheats are active. The array is adjacent to MMapPtrs[64]
in BSS which is exposed via retro_get_memory_data, making this
overflow visible from outside the core.
* libretro.c retro_cheat_set strcpy stack overflow with arbitrary-
length cheat strings from the frontend. Use strlcpy.
CRITICAL (savestate-triggerable on a malicious .fcs file)
=========================================================
* fds.c FDS savestate OOB read primitive: InDisk loaded from
savestate is used as index into 8-element diskdata[] static
pointer array. A value 8..254 dereferences arbitrary memory as
a pointer (heap-read primitive). Also bounds-checked SelectDisk,
mapperFDS_block, mapperFDS_blockstart, mapperFDS_diskaddr,
mapperFDS_blocklen so blockstart+diskaddr stays within the 65500-
byte disk buffer.
* 11 mappers had savestate-loaded variables masked at write time
but not at restore time, used as indices into fixed arrays:
- 88.c, KS7037.c, 112.c cmd indexes reg[8]
- sachen.c (S8259, S74LS374N) cmd indexes latch[8]
- 357.c dipswitch indexes outer_bank[4]
- unrom512.c flash_state indexes erase_a/d/b[5]
- 368.c preg indexes banks[8]
- 69.c sndcmd indexes sreg[14]
- mmc2and4.c latch0/latch1 index creg[4]
None individually exploitable into RCE - the array writes corrupt
adjacent BSS with constrained data flowing in - but each is an
out-of-bounds read or write from attacker-controllable input.
HIGH (memory-safety, reachable on any load)
===========================================
* fceu-memory.c FCEU_malloc deref-of-NULL on allocation failure
('ret = 0; memset(ret, 0, size);').
* file.c multiple FCEUFILE/MakeMemWrap/MakeMemWrapBuffer unchecked
allocations and unchecked filestream_tell return.
* libretro.c GameInfo NULL-derefs in three entry points
(retro_set_controller_port_device, retro_get_memory_data,
retro_get_memory_size) reachable on operations called before a
successful load.
* libretro.c framebuffer leak ~256 KB per failed FCEUI_LoadGame
(the libretro frontend doesn't call retro_unload_game on a
failed load).
* libretro.c 3DS retro_deinit unchecked linearFree.
* libretro.c stereo / NTSC filter unchecked malloc returns.
* fceu.c FCEUI_LoadGame unchecked GameInfo malloc.
* fds.c SubLoad and FDSLoad unchecked FCEU_malloc on diskdatao
backup buffers (NULL-deref in subsequent memcpy).
* fceu.c AllocGenieRW partial-failure leak: AReadG allocated but
BWriteG malloc fails -> 256 KB leak per retry.
* input/bworld.c Update(): unbounded strcpy from attacker-supplied
data into 20-byte bdata. Replaced with length-bounded copy.
* cart.c setprg2r/4r and setchr1r/2r/4r/8r mask-underflow guards.
SetupCartPRGMapping/SetupCartCHRMapping compute (size >> N) - 1
which underflows to 0xFFFFFFFF for chips smaller than the unit.
setprg8r/16r/32r and setchr8r already had defensive size checks;
the smaller units did not. malee.c (2 KB chip) and mapper 218
(2 KB NTARAM-as-CHR) accidentally avoid OOB; the primitive is
unsafe for any future board.
* video.c FCEU_InitVirtualVideo XBuf/XDBuf partial-failure leak.
* video.c, fceu.c FCEU_DispMessage / FCEU_printf / FCEU_PrintError:
vsprintf into fixed stack buffers replaced with vsnprintf.
* core: validate magic-string read length on FDS/UNIF/NSF load
(reject files too short to contain the magic so previous static
buffer / stack garbage doesn't spuriously match).
LEAKS (every cart load/unload cycle)
====================================
* mmc5.c MMC5 cart-side: WRAM (up to 64K) + MMC5fill (1K) + ExRAM
(1K) leaked per cycle. NSFMMC5_Close existed but was only used
for NSF code path.
* onebus.c Mapper 270/436: 8 KB CHRRAM leaked per cycle.
* 330.c, 375.c, 528.c: 8 KB WRAM each, no Close at all.
CORRECTNESS (UB / ABI / endianness)
===================================
* fceu-endian.c FlipByteOrder loop bound was 'count' instead of
'count/2', making it a no-op for every even count and silently
breaking savestate portability for every FCEUSTATE_RLSB-marked
field. The '#ifndef GEKKO' workarounds scattered through the
codebase (sound.c, vrc7.c, vrc6.c) were symptoms of this root
cause.
* state.c AddExState bounds check ran AFTER writing the entry, so
when SFEXINDEX hit the 63-entry cap the next call would write
the entry then immediately overwrite it with the terminator.
* state.c FCEUSS_Save_Mem: post-save callback was guarded by
'if (SPreSave)' instead of 'if (SPostSave)'.
* Sequence-point UB in counter expressions across 4 board files,
e.g. 'x = !x ? a : --x' and 'x = ++x % n'. GCC -Wsequence-point
flags all five sites (285.c, 413.c, asic_mmc3.c, jyasic.c).
* SFORMAT size mismatches between declared variable type and
AddExState size argument:
- asic_vrc3.c VRC3_count/VRC3_reload (uint16, saved as 1 byte)
- mmc3.c m555_count (uint32, saved as 2 bytes), m555_count_-
expired (uint8, saved as 2 bytes - OOB write into adjacent
BSS).
* unrom512.c UNROM-512 mapper 30 .srm save format: host-endian
uint32 flash write counters. Now stored as LE on disk regardless
of host (Battle Kid 2, Twin Dragons, Lizard, Sole, etc.).
* ~70 multi-byte single-variable SFORMAT/AddExState entries across
36 board files were saved as host-byte-order. After the
FlipByteOrder fix, these are now byte-swapped on BE so cross-
platform savestates round-trip correctly.
* coolgirl.c new ExStateLE() macro added; 22 multi-byte sites.
* pic16c5x.c 11 multi-byte AddExState calls (m_PC, m_PREVPC,
m_CONFIG, m_WDT, m_prescaler, m_opcode, m_STACK[0/1], m_icount,
m_delay_timer, m_rtcc, m_inst_cycles, m_clock2cycle).
* onebus.c PowerJoy Supermax submapper detection used non-portable
*(uint32*)&info->MD5 cast that read different bytes on LE vs BE.
* fds.c clean up redundant FCEUSTATE_RLSB encoding in AddExState
calls that also passed type=1 (idempotent OR; readability fix).
DOCUMENTATION
=============
* input.c UpdateGP's *(uint32*)data cast looks like a typical
endian bug but is actually correct (the libretro frontend builds
JSReturn with matching host-uint32 shifts). Comment added to
prevent future "fixes" from breaking it.
Limitations not addressed
=========================
* Element-stride-aware byte swapping. The savestate byte-swap
mechanism (FlipByteOrder over the entire SFORMAT entry buffer)
is structurally wrong for arrays of multi-byte values: it
reverses the whole buffer end-to-end instead of byte-swapping
each element. Several places that need cross-platform-portable
arrays (VRC7 sound state, jyasic chr[8]) work around this by
either splitting arrays into per-element SFORMAT entries (n106
PlayIndex, bandai reg) or by skipping save entirely on BE via
#ifndef GEKKO. A proper fix would extend the size encoding with
an element-stride field. Left for a future change because it
would change the savestate format.
* Strict-aliasing UB in ppu.c (around 9 sites doing
*(uint32*)uint8_buf for fast 4-byte writes via FCEU_dwmemset).
Works in practice with all common compilers because the patterns
are byte-symmetric, but is formally UB.
* FCEU_gmalloc calls exit(1) on OOM. A libretro core should never
exit() because that takes down the whole frontend. Used by
100+ call sites; refactoring to return-NULL is out of scope here.
Testing
=======
* Build: clean on `make platform=unix` with -O2; no new warnings.
* FlipByteOrder fix verified by hand-trace and a standalone unit
test for counts 2, 4, 8.
* uppow2 fix verified by unit test across 13 boundary cases.
* SFORMAT size mismatches and missing-RLSB cases identified by
Python static-analysis scripts that cross-reference SFORMAT
entries against variable declarations.
* iNES 2.0 exponent fix verified by hand-tracing what byte 0xFF
produces post-fix: exp=30 (capped), mult=7, size=3 GiB nominal,
capped to 1 GiB, capped to 2 GiB by uppow2, FCEU_malloc returns
NULL on most systems, loader returns 0. No heap overflow for
any input byte.
* Savestate-loaded array index audit: built a Python scanner that
extracts each AddExState/SFORMAT entry and cross-references the
variable name against array-index uses in the same file. All
flagged sites covered.
* A libFuzzer harness and seed corpus generator (fuzz_main.c,
gen_seed_corpus.py) accompany this submission for ongoing
regression testing.
2026-05-04 02:15:40 +02:00
|
|
|
char temp[1024];
|
2020-08-31 10:37:59 +08:00
|
|
|
char *codepart;
|
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 a;
|
|
|
|
|
uint8_t v;
|
2014-12-08 17:33:02 +01:00
|
|
|
int c;
|
|
|
|
|
int type = 1;
|
|
|
|
|
|
2020-08-31 10:37:59 +08:00
|
|
|
if (code == NULL)
|
|
|
|
|
return;
|
2014-12-08 17:33:02 +01:00
|
|
|
|
core: memory-safety, leak, and savestate-portability audit fixes
Squashed series of ~50 distinct bugs found during a multi-day
security/correctness audit, ranging from ROM-triggerable heap
corruption and savestate-triggered OOB read primitives down to
obscure cross-platform savestate breakage.
Build is clean on `make platform=unix` with zero new warnings.
CRITICAL (ROM-triggerable, exploitable on the user's machine)
=============================================================
* ines.c iNES 2.0 PRG/CHR exponent overflow leading to undersized
allocation followed by heap buffer overflow on fread (pow() with
attacker-chosen exponent up to 63). Cap exponent at 30 and
compute size in uint32 with explicit cap below 2 GiB before
storing in int.
* ines.c miscROMSize int wraparound from attacker-controlled PRG/
CHR sizes; previous '& 0x8000000' check missed common underflow
cases. Compute in int64 and reject <= 0 or > 128 MiB.
* unif.c MAPR chunk OOB heap read on chunks of size < 4. Validate
chunk size before allocating board-name buffer.
* unif.c chunk size truncation: int conversion + missing cap allowed
a 4 GiB chunk size to wrap. Cap at 16 MiB.
* unif.c FixRomSize infinite loop on size > 0x80000000. Cap input.
* unif.c DINF chunk: months[(m - 1) % 12] is UB for m==0; m comes
from the .unf file. Clamp m into [1..12] before subtracting.
* unif.c NAME chunk: GameInfo->name = malloc(...) was unchecked.
* nsf.c size underflow: FCEU_fgetsize() - 0x80 wraps to ~UINT64_MAX
on tiny files, propagating a huge size into uppow2/FCEU_malloc.
Validate size > 0x80 before subtracting.
* nsf.c NSFMaxBank * 4096 cap tightened from 1<<20 (UB on signed-int
overflow) to 1<<19 (fits in int).
* general.c uppow2 had signed-int UB (1 << 32) and wrapped to 0 for
inputs near uint32 max. Cap at 0x80000000.
* cheat.c SubCheats[256] BSS overflow when more than 256 GG/PAR
substitution cheats are active. The array is adjacent to MMapPtrs[64]
in BSS which is exposed via retro_get_memory_data, making this
overflow visible from outside the core.
* libretro.c retro_cheat_set strcpy stack overflow with arbitrary-
length cheat strings from the frontend. Use strlcpy.
CRITICAL (savestate-triggerable on a malicious .fcs file)
=========================================================
* fds.c FDS savestate OOB read primitive: InDisk loaded from
savestate is used as index into 8-element diskdata[] static
pointer array. A value 8..254 dereferences arbitrary memory as
a pointer (heap-read primitive). Also bounds-checked SelectDisk,
mapperFDS_block, mapperFDS_blockstart, mapperFDS_diskaddr,
mapperFDS_blocklen so blockstart+diskaddr stays within the 65500-
byte disk buffer.
* 11 mappers had savestate-loaded variables masked at write time
but not at restore time, used as indices into fixed arrays:
- 88.c, KS7037.c, 112.c cmd indexes reg[8]
- sachen.c (S8259, S74LS374N) cmd indexes latch[8]
- 357.c dipswitch indexes outer_bank[4]
- unrom512.c flash_state indexes erase_a/d/b[5]
- 368.c preg indexes banks[8]
- 69.c sndcmd indexes sreg[14]
- mmc2and4.c latch0/latch1 index creg[4]
None individually exploitable into RCE - the array writes corrupt
adjacent BSS with constrained data flowing in - but each is an
out-of-bounds read or write from attacker-controllable input.
HIGH (memory-safety, reachable on any load)
===========================================
* fceu-memory.c FCEU_malloc deref-of-NULL on allocation failure
('ret = 0; memset(ret, 0, size);').
* file.c multiple FCEUFILE/MakeMemWrap/MakeMemWrapBuffer unchecked
allocations and unchecked filestream_tell return.
* libretro.c GameInfo NULL-derefs in three entry points
(retro_set_controller_port_device, retro_get_memory_data,
retro_get_memory_size) reachable on operations called before a
successful load.
* libretro.c framebuffer leak ~256 KB per failed FCEUI_LoadGame
(the libretro frontend doesn't call retro_unload_game on a
failed load).
* libretro.c 3DS retro_deinit unchecked linearFree.
* libretro.c stereo / NTSC filter unchecked malloc returns.
* fceu.c FCEUI_LoadGame unchecked GameInfo malloc.
* fds.c SubLoad and FDSLoad unchecked FCEU_malloc on diskdatao
backup buffers (NULL-deref in subsequent memcpy).
* fceu.c AllocGenieRW partial-failure leak: AReadG allocated but
BWriteG malloc fails -> 256 KB leak per retry.
* input/bworld.c Update(): unbounded strcpy from attacker-supplied
data into 20-byte bdata. Replaced with length-bounded copy.
* cart.c setprg2r/4r and setchr1r/2r/4r/8r mask-underflow guards.
SetupCartPRGMapping/SetupCartCHRMapping compute (size >> N) - 1
which underflows to 0xFFFFFFFF for chips smaller than the unit.
setprg8r/16r/32r and setchr8r already had defensive size checks;
the smaller units did not. malee.c (2 KB chip) and mapper 218
(2 KB NTARAM-as-CHR) accidentally avoid OOB; the primitive is
unsafe for any future board.
* video.c FCEU_InitVirtualVideo XBuf/XDBuf partial-failure leak.
* video.c, fceu.c FCEU_DispMessage / FCEU_printf / FCEU_PrintError:
vsprintf into fixed stack buffers replaced with vsnprintf.
* core: validate magic-string read length on FDS/UNIF/NSF load
(reject files too short to contain the magic so previous static
buffer / stack garbage doesn't spuriously match).
LEAKS (every cart load/unload cycle)
====================================
* mmc5.c MMC5 cart-side: WRAM (up to 64K) + MMC5fill (1K) + ExRAM
(1K) leaked per cycle. NSFMMC5_Close existed but was only used
for NSF code path.
* onebus.c Mapper 270/436: 8 KB CHRRAM leaked per cycle.
* 330.c, 375.c, 528.c: 8 KB WRAM each, no Close at all.
CORRECTNESS (UB / ABI / endianness)
===================================
* fceu-endian.c FlipByteOrder loop bound was 'count' instead of
'count/2', making it a no-op for every even count and silently
breaking savestate portability for every FCEUSTATE_RLSB-marked
field. The '#ifndef GEKKO' workarounds scattered through the
codebase (sound.c, vrc7.c, vrc6.c) were symptoms of this root
cause.
* state.c AddExState bounds check ran AFTER writing the entry, so
when SFEXINDEX hit the 63-entry cap the next call would write
the entry then immediately overwrite it with the terminator.
* state.c FCEUSS_Save_Mem: post-save callback was guarded by
'if (SPreSave)' instead of 'if (SPostSave)'.
* Sequence-point UB in counter expressions across 4 board files,
e.g. 'x = !x ? a : --x' and 'x = ++x % n'. GCC -Wsequence-point
flags all five sites (285.c, 413.c, asic_mmc3.c, jyasic.c).
* SFORMAT size mismatches between declared variable type and
AddExState size argument:
- asic_vrc3.c VRC3_count/VRC3_reload (uint16, saved as 1 byte)
- mmc3.c m555_count (uint32, saved as 2 bytes), m555_count_-
expired (uint8, saved as 2 bytes - OOB write into adjacent
BSS).
* unrom512.c UNROM-512 mapper 30 .srm save format: host-endian
uint32 flash write counters. Now stored as LE on disk regardless
of host (Battle Kid 2, Twin Dragons, Lizard, Sole, etc.).
* ~70 multi-byte single-variable SFORMAT/AddExState entries across
36 board files were saved as host-byte-order. After the
FlipByteOrder fix, these are now byte-swapped on BE so cross-
platform savestates round-trip correctly.
* coolgirl.c new ExStateLE() macro added; 22 multi-byte sites.
* pic16c5x.c 11 multi-byte AddExState calls (m_PC, m_PREVPC,
m_CONFIG, m_WDT, m_prescaler, m_opcode, m_STACK[0/1], m_icount,
m_delay_timer, m_rtcc, m_inst_cycles, m_clock2cycle).
* onebus.c PowerJoy Supermax submapper detection used non-portable
*(uint32*)&info->MD5 cast that read different bytes on LE vs BE.
* fds.c clean up redundant FCEUSTATE_RLSB encoding in AddExState
calls that also passed type=1 (idempotent OR; readability fix).
DOCUMENTATION
=============
* input.c UpdateGP's *(uint32*)data cast looks like a typical
endian bug but is actually correct (the libretro frontend builds
JSReturn with matching host-uint32 shifts). Comment added to
prevent future "fixes" from breaking it.
Limitations not addressed
=========================
* Element-stride-aware byte swapping. The savestate byte-swap
mechanism (FlipByteOrder over the entire SFORMAT entry buffer)
is structurally wrong for arrays of multi-byte values: it
reverses the whole buffer end-to-end instead of byte-swapping
each element. Several places that need cross-platform-portable
arrays (VRC7 sound state, jyasic chr[8]) work around this by
either splitting arrays into per-element SFORMAT entries (n106
PlayIndex, bandai reg) or by skipping save entirely on BE via
#ifndef GEKKO. A proper fix would extend the size encoding with
an element-stride field. Left for a future change because it
would change the savestate format.
* Strict-aliasing UB in ppu.c (around 9 sites doing
*(uint32*)uint8_buf for fast 4-byte writes via FCEU_dwmemset).
Works in practice with all common compilers because the patterns
are byte-symmetric, but is formally UB.
* FCEU_gmalloc calls exit(1) on OOM. A libretro core should never
exit() because that takes down the whole frontend. Used by
100+ call sites; refactoring to return-NULL is out of scope here.
Testing
=======
* Build: clean on `make platform=unix` with -O2; no new warnings.
* FlipByteOrder fix verified by hand-trace and a standalone unit
test for counts 2, 4, 8.
* uppow2 fix verified by unit test across 13 boundary cases.
* SFORMAT size mismatches and missing-RLSB cases identified by
Python static-analysis scripts that cross-reference SFORMAT
entries against variable declarations.
* iNES 2.0 exponent fix verified by hand-tracing what byte 0xFF
produces post-fix: exp=30 (capped), mult=7, size=3 GiB nominal,
capped to 1 GiB, capped to 2 GiB by uppow2, FCEU_malloc returns
NULL on most systems, loader returns 0. No heap overflow for
any input byte.
* Savestate-loaded array index audit: built a Python scanner that
extracts each AddExState/SFORMAT entry and cross-references the
variable name against array-index uses in the same file. All
flagged sites covered.
* A libFuzzer harness and seed corpus generator (fuzz_main.c,
gen_seed_corpus.py) accompany this submission for ongoing
regression testing.
2026-05-04 02:15:40 +02:00
|
|
|
/* Cheat strings can be arbitrarily long (multiple codes joined by
|
|
|
|
|
* separators). The previous strcpy into a 256-byte stack buffer was
|
|
|
|
|
* trivially overflowable. Use strlcpy and a larger buffer; truncate
|
|
|
|
|
* if necessary rather than overflow. */
|
core: strict-aliasing fixes, bounded string ops, drop FCEUDEF_DEBUGGER
Three follow-up cleanups on top of the determinism / typedef / LE
work in f2a5be3.
================================================================
Pass 1: fix strict-aliasing UB in PPU sprite buffer and FCEU_dwmemset
================================================================
Three sites were doing type-punning through uint32_t* casts that
GCC flags with -Wstrict-aliasing=2:
1. ppu.c FetchSpriteData (two near-identical sites). After populating
a local SPRB struct (4 bytes: ca[2], atr, x), the prior code did
'*(uint32_t*)&SPRBUF[ns << 2] = *(uint32_t*)&dst' to store the
struct as one 32-bit word. This violates strict-aliasing (uint8_t
buffer accessed as uint32_t, struct accessed as uint32_t) and
silently assumes 4-byte alignment of SPRBUF + (ns << 2). Use
memcpy instead - GCC and Clang lower it to the same single
32-bit store.
2. fceu-memory.h FCEU_dwmemset macro. Same pattern every iteration:
'*(uint32_t*)& (d)[_x] = c'. Replaced with memcpy(... &v, 4) in
the macro body. Same generated code, now alias- and align-clean.
Added #include <string.h> to fceu-memory.h itself (4 callers
relied on transitive includes for memcpy).
================================================================
Pass 2: bounded string operations
================================================================
Sweep across the core and libretro driver to replace every unbounded
string function with its size-aware counterpart:
strcpy -> strlcpy (with explicit destination size)
strncpy -> strlcpy (silent truncation -> guaranteed NUL termination
and known-size semantics; behaviourally identical
at every call site here because callers either
pre-zeroed the buffer or treated truncation as
a guaranteed terminator)
strcat -> strlcpy at offset (track 'pos' across appends to avoid
rescanning the buffer with strlen and to keep
the bound explicit at every step)
sprintf -> snprintf (with sizeof(buf) as the bound)
The compat/strl.h header from libretro-common is on the include path
for every translation unit; the libretro driver files were already
using strlcpy via stdstring.h transitive include. Core files (ines.c,
unif.c, cheat.c, nsf.c, state.c, general.c) gain explicit
'#include <compat/strl.h>'.
Per-file changes:
general.c
- FCEUI_SetBaseDirectory: strncpy + manual NUL -> strlcpy
- FCEU_MakeFName: malloc(strlen+1) + strcpy -> sized strlcpy,
malloc NULL-check added (was unchecked).
ines.c
- 'gigastr' iNES-header-warning building.
Pre-existing bug fixed: every sprintf(gigastr + gigastr_len, ...)
wrote at the SAME offset captured once at the top of the block,
so when multiple 'tofix' bits were set, only the LAST fragment
survived (each sprintf clobbered the previous). Replaced with a
running 'pos' offset across snprintf and strlcpy-at-offset calls.
- 6 sprintf -> snprintf, 3 strcat -> strlcpy at offset, 1 strcpy
-> strlcpy.
unif.c
- GameInfo->name allocation: strcpy -> strlcpy with explicit size.
cheat.c
- FCEUI_AddCheat: malloc + strcpy -> sized strlcpy.
- FCEUI_SetCheat: realloc + strcpy -> sized strlcpy.
nsf.c
- FCEUI_NSFGetInfo: 3x strncpy -> strlcpy.
- Visualiser snbuf sprintf -> snprintf (the 16-byte buffer was
technically overflowable for very large song counts).
state.c
- AddExState description copy: strncpy -> strlcpy. Preserves
4-byte SFORMAT-tag semantics.
fds.c
- Disk-tag formatting: sprintf "DDT%d" -> snprintf.
boards/__serial.c
- 2 sprintf -> snprintf (Windows-only SerialOpen path).
drivers/libretro/libretro.c
- retro_cheat_set: sprintf "N/A" + strcpy literal -> strlcpy.
drivers/libretro/libretro_dipswitch.c
- VS-DIP key building: sprintf -> snprintf.
- core_key calloc + strcpy -> sized strlcpy. calloc NULL-check
added (was unchecked - dereferencing NULL on OOM).
drivers/libretro/libretro_core_options.h
- values_buf assembly: single strcpy + N strcat replaced with
strlcpy + strlcpy-at-offset using a running 'pos'. Each step
bounded by remaining buffer space.
================================================================
Pass 3: remove FCEUDEF_DEBUGGER and the unused debugger scaffolding
================================================================
The FCEUDEF_DEBUGGER macro was never defined for libretro builds, so
every block guarded by it was dead code. Removing the macro takes
out:
- src/debug.c (FCEUI_DumpMem, FCEUI_DumpVid, FCEUI_LoadMem,
FCEUI_Disassemble, FCEUI_MemDump, FCEUI_MemSafePeek,
FCEUI_MemPoke, breakpoint set/get/list, FCEUI_SetCPUCallback).
Not in Makefile.common SOURCES_C, never compiled. None of the
declared functions had any caller in any compiled .c file.
- src/debug.h (declarations for the above).
- x6502.c X6502_RunDebug. The dual-implementation pattern with a
function pointer that switched between RunNormal and RunDebug
is now a single direct X6502_Run.
- x6502.c X6502_Debug, FCEUI_NMI, FCEUI_IRQ, FCEUI_GetIVectors.
Set/get debugger hooks. No callers.
- x6502.c RdMemHook, WrMemHook, XSave, debugmode. Hook scaffolding
used only by RunDebug.
- x6502struct.h X6502 fields preexec, CPUHook, ReadHook, WriteHook.
Set only inside RunDebug, never read elsewhere.
- fceuindbg variable. Set to 1 only in FCEUI_GetIVectors and
X6502_RunDebug (both removed); was always 0 elsewhere, so every
'if (!fceuindbg)' check was a no-op. Removed both the variable
(defined in ppu.c, declared in fceu.h) and every check site
across sound.c, input.c, fds.c, nsf.c, ppu.c, mmc5.c, n106.c,
BMW8544.c, and the input drivers (arkanoid, mahjong, mouse,
pec586kb, powerpad, zapper).
- All FCEUDEF_DEBUGGER conditional declarations from driver.h.
The 'if (!fceuindbg)' checks gated side-effects (joypad-bit-counter
increments, PPU register reads, etc.) so a future debugger could peek
at memory without advancing the emulator state. With the debugger
gone, those side-effects are now unconditional - which is what they
should have been anyway in a libretro build.
If a future developer needs a debugger they should resurrect this
out of git history into a separate debugger-frontend project rather
than re-introducing a build-time toggle that nothing in the libretro
build can ever exercise.
================================================================
Build status
================================================================
Build clean on `make platform=unix` with zero errors and zero
warnings. Output binary 184 bytes smaller than upstream f2a5be3
(4,388,840 -> 4,388,656). 31 files changed, 127 insertions, 863
deletions.
2026-05-04 03:23:56 +02:00
|
|
|
strlcpy(name, "N/A", sizeof(name));
|
core: memory-safety, leak, and savestate-portability audit fixes
Squashed series of ~50 distinct bugs found during a multi-day
security/correctness audit, ranging from ROM-triggerable heap
corruption and savestate-triggered OOB read primitives down to
obscure cross-platform savestate breakage.
Build is clean on `make platform=unix` with zero new warnings.
CRITICAL (ROM-triggerable, exploitable on the user's machine)
=============================================================
* ines.c iNES 2.0 PRG/CHR exponent overflow leading to undersized
allocation followed by heap buffer overflow on fread (pow() with
attacker-chosen exponent up to 63). Cap exponent at 30 and
compute size in uint32 with explicit cap below 2 GiB before
storing in int.
* ines.c miscROMSize int wraparound from attacker-controlled PRG/
CHR sizes; previous '& 0x8000000' check missed common underflow
cases. Compute in int64 and reject <= 0 or > 128 MiB.
* unif.c MAPR chunk OOB heap read on chunks of size < 4. Validate
chunk size before allocating board-name buffer.
* unif.c chunk size truncation: int conversion + missing cap allowed
a 4 GiB chunk size to wrap. Cap at 16 MiB.
* unif.c FixRomSize infinite loop on size > 0x80000000. Cap input.
* unif.c DINF chunk: months[(m - 1) % 12] is UB for m==0; m comes
from the .unf file. Clamp m into [1..12] before subtracting.
* unif.c NAME chunk: GameInfo->name = malloc(...) was unchecked.
* nsf.c size underflow: FCEU_fgetsize() - 0x80 wraps to ~UINT64_MAX
on tiny files, propagating a huge size into uppow2/FCEU_malloc.
Validate size > 0x80 before subtracting.
* nsf.c NSFMaxBank * 4096 cap tightened from 1<<20 (UB on signed-int
overflow) to 1<<19 (fits in int).
* general.c uppow2 had signed-int UB (1 << 32) and wrapped to 0 for
inputs near uint32 max. Cap at 0x80000000.
* cheat.c SubCheats[256] BSS overflow when more than 256 GG/PAR
substitution cheats are active. The array is adjacent to MMapPtrs[64]
in BSS which is exposed via retro_get_memory_data, making this
overflow visible from outside the core.
* libretro.c retro_cheat_set strcpy stack overflow with arbitrary-
length cheat strings from the frontend. Use strlcpy.
CRITICAL (savestate-triggerable on a malicious .fcs file)
=========================================================
* fds.c FDS savestate OOB read primitive: InDisk loaded from
savestate is used as index into 8-element diskdata[] static
pointer array. A value 8..254 dereferences arbitrary memory as
a pointer (heap-read primitive). Also bounds-checked SelectDisk,
mapperFDS_block, mapperFDS_blockstart, mapperFDS_diskaddr,
mapperFDS_blocklen so blockstart+diskaddr stays within the 65500-
byte disk buffer.
* 11 mappers had savestate-loaded variables masked at write time
but not at restore time, used as indices into fixed arrays:
- 88.c, KS7037.c, 112.c cmd indexes reg[8]
- sachen.c (S8259, S74LS374N) cmd indexes latch[8]
- 357.c dipswitch indexes outer_bank[4]
- unrom512.c flash_state indexes erase_a/d/b[5]
- 368.c preg indexes banks[8]
- 69.c sndcmd indexes sreg[14]
- mmc2and4.c latch0/latch1 index creg[4]
None individually exploitable into RCE - the array writes corrupt
adjacent BSS with constrained data flowing in - but each is an
out-of-bounds read or write from attacker-controllable input.
HIGH (memory-safety, reachable on any load)
===========================================
* fceu-memory.c FCEU_malloc deref-of-NULL on allocation failure
('ret = 0; memset(ret, 0, size);').
* file.c multiple FCEUFILE/MakeMemWrap/MakeMemWrapBuffer unchecked
allocations and unchecked filestream_tell return.
* libretro.c GameInfo NULL-derefs in three entry points
(retro_set_controller_port_device, retro_get_memory_data,
retro_get_memory_size) reachable on operations called before a
successful load.
* libretro.c framebuffer leak ~256 KB per failed FCEUI_LoadGame
(the libretro frontend doesn't call retro_unload_game on a
failed load).
* libretro.c 3DS retro_deinit unchecked linearFree.
* libretro.c stereo / NTSC filter unchecked malloc returns.
* fceu.c FCEUI_LoadGame unchecked GameInfo malloc.
* fds.c SubLoad and FDSLoad unchecked FCEU_malloc on diskdatao
backup buffers (NULL-deref in subsequent memcpy).
* fceu.c AllocGenieRW partial-failure leak: AReadG allocated but
BWriteG malloc fails -> 256 KB leak per retry.
* input/bworld.c Update(): unbounded strcpy from attacker-supplied
data into 20-byte bdata. Replaced with length-bounded copy.
* cart.c setprg2r/4r and setchr1r/2r/4r/8r mask-underflow guards.
SetupCartPRGMapping/SetupCartCHRMapping compute (size >> N) - 1
which underflows to 0xFFFFFFFF for chips smaller than the unit.
setprg8r/16r/32r and setchr8r already had defensive size checks;
the smaller units did not. malee.c (2 KB chip) and mapper 218
(2 KB NTARAM-as-CHR) accidentally avoid OOB; the primitive is
unsafe for any future board.
* video.c FCEU_InitVirtualVideo XBuf/XDBuf partial-failure leak.
* video.c, fceu.c FCEU_DispMessage / FCEU_printf / FCEU_PrintError:
vsprintf into fixed stack buffers replaced with vsnprintf.
* core: validate magic-string read length on FDS/UNIF/NSF load
(reject files too short to contain the magic so previous static
buffer / stack garbage doesn't spuriously match).
LEAKS (every cart load/unload cycle)
====================================
* mmc5.c MMC5 cart-side: WRAM (up to 64K) + MMC5fill (1K) + ExRAM
(1K) leaked per cycle. NSFMMC5_Close existed but was only used
for NSF code path.
* onebus.c Mapper 270/436: 8 KB CHRRAM leaked per cycle.
* 330.c, 375.c, 528.c: 8 KB WRAM each, no Close at all.
CORRECTNESS (UB / ABI / endianness)
===================================
* fceu-endian.c FlipByteOrder loop bound was 'count' instead of
'count/2', making it a no-op for every even count and silently
breaking savestate portability for every FCEUSTATE_RLSB-marked
field. The '#ifndef GEKKO' workarounds scattered through the
codebase (sound.c, vrc7.c, vrc6.c) were symptoms of this root
cause.
* state.c AddExState bounds check ran AFTER writing the entry, so
when SFEXINDEX hit the 63-entry cap the next call would write
the entry then immediately overwrite it with the terminator.
* state.c FCEUSS_Save_Mem: post-save callback was guarded by
'if (SPreSave)' instead of 'if (SPostSave)'.
* Sequence-point UB in counter expressions across 4 board files,
e.g. 'x = !x ? a : --x' and 'x = ++x % n'. GCC -Wsequence-point
flags all five sites (285.c, 413.c, asic_mmc3.c, jyasic.c).
* SFORMAT size mismatches between declared variable type and
AddExState size argument:
- asic_vrc3.c VRC3_count/VRC3_reload (uint16, saved as 1 byte)
- mmc3.c m555_count (uint32, saved as 2 bytes), m555_count_-
expired (uint8, saved as 2 bytes - OOB write into adjacent
BSS).
* unrom512.c UNROM-512 mapper 30 .srm save format: host-endian
uint32 flash write counters. Now stored as LE on disk regardless
of host (Battle Kid 2, Twin Dragons, Lizard, Sole, etc.).
* ~70 multi-byte single-variable SFORMAT/AddExState entries across
36 board files were saved as host-byte-order. After the
FlipByteOrder fix, these are now byte-swapped on BE so cross-
platform savestates round-trip correctly.
* coolgirl.c new ExStateLE() macro added; 22 multi-byte sites.
* pic16c5x.c 11 multi-byte AddExState calls (m_PC, m_PREVPC,
m_CONFIG, m_WDT, m_prescaler, m_opcode, m_STACK[0/1], m_icount,
m_delay_timer, m_rtcc, m_inst_cycles, m_clock2cycle).
* onebus.c PowerJoy Supermax submapper detection used non-portable
*(uint32*)&info->MD5 cast that read different bytes on LE vs BE.
* fds.c clean up redundant FCEUSTATE_RLSB encoding in AddExState
calls that also passed type=1 (idempotent OR; readability fix).
DOCUMENTATION
=============
* input.c UpdateGP's *(uint32*)data cast looks like a typical
endian bug but is actually correct (the libretro frontend builds
JSReturn with matching host-uint32 shifts). Comment added to
prevent future "fixes" from breaking it.
Limitations not addressed
=========================
* Element-stride-aware byte swapping. The savestate byte-swap
mechanism (FlipByteOrder over the entire SFORMAT entry buffer)
is structurally wrong for arrays of multi-byte values: it
reverses the whole buffer end-to-end instead of byte-swapping
each element. Several places that need cross-platform-portable
arrays (VRC7 sound state, jyasic chr[8]) work around this by
either splitting arrays into per-element SFORMAT entries (n106
PlayIndex, bandai reg) or by skipping save entirely on BE via
#ifndef GEKKO. A proper fix would extend the size encoding with
an element-stride field. Left for a future change because it
would change the savestate format.
* Strict-aliasing UB in ppu.c (around 9 sites doing
*(uint32*)uint8_buf for fast 4-byte writes via FCEU_dwmemset).
Works in practice with all common compilers because the patterns
are byte-symmetric, but is formally UB.
* FCEU_gmalloc calls exit(1) on OOM. A libretro core should never
exit() because that takes down the whole frontend. Used by
100+ call sites; refactoring to return-NULL is out of scope here.
Testing
=======
* Build: clean on `make platform=unix` with -O2; no new warnings.
* FlipByteOrder fix verified by hand-trace and a standalone unit
test for counts 2, 4, 8.
* uppow2 fix verified by unit test across 13 boundary cases.
* SFORMAT size mismatches and missing-RLSB cases identified by
Python static-analysis scripts that cross-reference SFORMAT
entries against variable declarations.
* iNES 2.0 exponent fix verified by hand-tracing what byte 0xFF
produces post-fix: exp=30 (capped), mult=7, size=3 GiB nominal,
capped to 1 GiB, capped to 2 GiB by uppow2, FCEU_malloc returns
NULL on most systems, loader returns 0. No heap overflow for
any input byte.
* Savestate-loaded array index audit: built a Python scanner that
extracts each AddExState/SFORMAT entry and cross-references the
variable name against array-index uses in the same file. All
flagged sites covered.
* A libFuzzer harness and seed corpus generator (fuzz_main.c,
gen_seed_corpus.py) accompany this submission for ongoing
regression testing.
2026-05-04 02:15:40 +02:00
|
|
|
strlcpy(temp, code, sizeof(temp));
|
2020-08-31 10:37:59 +08:00
|
|
|
codepart = strtok(temp, "+,;._ ");
|
2014-12-08 17:33:02 +01:00
|
|
|
|
2020-08-31 10:37:59 +08:00
|
|
|
while (codepart)
|
|
|
|
|
{
|
2022-07-21 17:25:38 +02:00
|
|
|
size_t codepart_len = strlen(codepart);
|
|
|
|
|
if ((codepart_len == 7) && (codepart[4]==':'))
|
2020-08-31 10:37:59 +08:00
|
|
|
{
|
2020-08-31 13:09:40 +08:00
|
|
|
/* raw code in xxxx:xx format */
|
2020-08-31 13:38:58 +08:00
|
|
|
log_cb.log(RETRO_LOG_DEBUG, "Cheat code added: '%s' (Raw)\n", codepart);
|
2020-08-31 10:37:59 +08:00
|
|
|
codepart[4] = '\0';
|
|
|
|
|
a = strtoul(codepart, NULL, 16);
|
|
|
|
|
v = strtoul(codepart + 5, NULL, 16);
|
2020-08-31 13:09:40 +08:00
|
|
|
c = -1;
|
2020-09-01 23:00:14 +08:00
|
|
|
/* Zero-page addressing modes don't go through the normal read/write handlers in FCEU, so
|
|
|
|
|
* we must do the old hacky method of RAM cheats. */
|
|
|
|
|
if (a < 0x0100) type = 0;
|
2020-08-31 13:09:40 +08:00
|
|
|
FCEUI_AddCheat(name, a, v, c, type);
|
|
|
|
|
}
|
2022-07-21 17:25:38 +02:00
|
|
|
else if ((codepart_len == 10) && (codepart[4] == '?') && (codepart[7] == ':'))
|
2020-08-31 13:09:40 +08:00
|
|
|
{
|
|
|
|
|
/* raw code in xxxx?xx:xx */
|
2020-08-31 13:38:58 +08:00
|
|
|
log_cb.log(RETRO_LOG_DEBUG, "Cheat code added: '%s' (Raw)\n", codepart);
|
2020-08-31 13:09:40 +08:00
|
|
|
codepart[4] = '\0';
|
|
|
|
|
codepart[7] = '\0';
|
|
|
|
|
a = strtoul(codepart, NULL, 16);
|
|
|
|
|
v = strtoul(codepart + 8, NULL, 16);
|
|
|
|
|
c = strtoul(codepart + 5, NULL, 16);
|
2020-09-01 23:00:14 +08:00
|
|
|
/* Zero-page addressing modes don't go through the normal read/write handlers in FCEU, so
|
|
|
|
|
* we must do the old hacky method of RAM cheats. */
|
|
|
|
|
if (a < 0x0100) type = 0;
|
2020-08-31 10:37:59 +08:00
|
|
|
FCEUI_AddCheat(name, a, v, c, type);
|
|
|
|
|
}
|
2020-09-09 11:33:36 +08:00
|
|
|
else if (GGisvalid(codepart) && FCEUI_DecodeGG(codepart, &a, &v, &c))
|
2020-08-31 10:37:59 +08:00
|
|
|
{
|
|
|
|
|
FCEUI_AddCheat(name, a, v, c, type);
|
|
|
|
|
log_cb.log(RETRO_LOG_DEBUG, "Cheat code added: '%s' (GG)\n", codepart);
|
|
|
|
|
}
|
|
|
|
|
else if (FCEUI_DecodePAR(codepart, &a, &v, &c, &type))
|
|
|
|
|
{
|
|
|
|
|
FCEUI_AddCheat(name, a, v, c, type);
|
|
|
|
|
log_cb.log(RETRO_LOG_DEBUG, "Cheat code added: '%s' (PAR)\n", codepart);
|
|
|
|
|
}
|
2020-09-09 11:33:36 +08:00
|
|
|
else
|
|
|
|
|
log_cb.log(RETRO_LOG_DEBUG, "Invalid or unknown code: '%s'\n", codepart);
|
2020-08-31 10:37:59 +08:00
|
|
|
codepart = strtok(NULL,"+,;._ ");
|
|
|
|
|
}
|
2014-12-08 17:33:02 +01:00
|
|
|
}
|
2014-03-30 22:35:00 +02:00
|
|
|
|
2015-08-28 13:31:21 +02:00
|
|
|
typedef struct cartridge_db
|
|
|
|
|
{
|
|
|
|
|
char title[256];
|
|
|
|
|
uint32_t crc;
|
|
|
|
|
} cartridge_db_t;
|
|
|
|
|
|
2017-02-16 20:30:24 -06:00
|
|
|
static const struct cartridge_db fourscore_db_list[] =
|
2015-08-28 13:31:21 +02:00
|
|
|
{
|
|
|
|
|
{
|
|
|
|
|
"Bomberman II (USA)",
|
|
|
|
|
0x1ebb5b42
|
|
|
|
|
},
|
|
|
|
|
#if 0
|
|
|
|
|
{
|
|
|
|
|
"Championship Bowling (USA)",
|
|
|
|
|
0xeac38105
|
|
|
|
|
},
|
|
|
|
|
#endif
|
|
|
|
|
{
|
|
|
|
|
"Chris Evert & Ivan Lendl in Top Players' Tennis (USA)",
|
|
|
|
|
0xf99e37eb
|
|
|
|
|
},
|
|
|
|
|
#if 0
|
2017-02-16 20:30:24 -06:00
|
|
|
{
|
2015-08-28 13:31:21 +02:00
|
|
|
"Crash 'n' the Boys - Street Challenge (USA)",
|
|
|
|
|
0xc7f0c457
|
|
|
|
|
},
|
|
|
|
|
#endif
|
|
|
|
|
{
|
|
|
|
|
"Four Players' Tennis (Europe)",
|
2017-02-16 20:30:24 -06:00
|
|
|
0x48b8ee58
|
2015-08-28 13:31:21 +02:00
|
|
|
},
|
2017-02-16 20:30:24 -06:00
|
|
|
{
|
2015-08-28 13:31:21 +02:00
|
|
|
"Danny Sullivan's Indy Heat (Europe)",
|
|
|
|
|
0x27ca0679,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Gauntlet II (Europe)",
|
|
|
|
|
0x79f688bc
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Gauntlet II (USA)",
|
|
|
|
|
0x1b71ccdb
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Greg Norman's Golf Power (USA)",
|
|
|
|
|
0x1352f1b9
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Harlem Globetrotters (USA)",
|
|
|
|
|
0x2e6ee98d
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Ivan 'Ironman' Stewart's Super Off Road (Europe)",
|
|
|
|
|
0x05104517
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Ivan 'Ironman' Stewart's Super Off Road (USA)",
|
|
|
|
|
0x4b041b6b
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Kings of the Beach - Professional Beach Volleyball (USA)",
|
|
|
|
|
0xf54b34bd
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Magic Johnson's Fast Break (USA)",
|
|
|
|
|
0xc6c2edb5
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"M.U.L.E. (USA)",
|
|
|
|
|
0x0939852f
|
|
|
|
|
},
|
2020-07-12 21:04:09 +07:00
|
|
|
{
|
|
|
|
|
"Micro Mages",
|
|
|
|
|
0x4e6b9078
|
|
|
|
|
},
|
2015-08-28 13:31:21 +02:00
|
|
|
{
|
|
|
|
|
"Monster Truck Rally (USA)",
|
|
|
|
|
0x2f698c4d
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"NES Play Action Football (USA)",
|
|
|
|
|
0xb9b4d9e0
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Nightmare on Elm Street, A (USA)",
|
|
|
|
|
0xda2cb59a
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Nintendo World Cup (Europe)",
|
|
|
|
|
0x8da6667d
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Nintendo World Cup (Europe) (Rev A)",
|
|
|
|
|
0x7c16f819
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Nintendo World Cup (Europe) (Rev B)",
|
|
|
|
|
0x7f08d0d9
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Nintendo World Cup (USA)",
|
|
|
|
|
0xa22657fa
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"R.C. Pro-Am II (Europe)",
|
|
|
|
|
0x308da987
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"R.C. Pro-Am II (USA)",
|
|
|
|
|
0x9edd2159
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Rackets & Rivals (Europe)",
|
|
|
|
|
0x8fa6e92c
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Roundball - 2-on-2 Challenge (Europe)",
|
|
|
|
|
0xad0394f0
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Roundball - 2-on-2 Challenge (USA)",
|
|
|
|
|
0x6e4dcfd2
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Spot - The Video Game (Japan)",
|
|
|
|
|
0x0abdd5ca
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Spot - The Video Game (USA)",
|
|
|
|
|
0xcfae9dfa
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Smash T.V. (Europe)",
|
|
|
|
|
0x0b8f8128
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Smash T.V. (USA)",
|
|
|
|
|
0x6ee94d32
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Super Jeopardy! (USA)",
|
|
|
|
|
0xcf4487a2
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Super Spike V'Ball (Europe)",
|
|
|
|
|
0xc05a63b2
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Super Spike V'Ball (USA)",
|
|
|
|
|
0xe840fd21
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Super Spike V'Ball + Nintendo World Cup (USA)",
|
|
|
|
|
0x407d6ffd
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Swords and Serpents (Europe)",
|
|
|
|
|
0xd153caf6
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Swords and Serpents (France)",
|
|
|
|
|
0x46135141
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Swords and Serpents (USA)",
|
|
|
|
|
0x3417ec46
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Battle City (Japan) (4 Players Hack) http://www.romhacking.net/hacks/2142/",
|
|
|
|
|
0x69977c9e
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Bomberman 3 (Homebrew) http://tempect.de/senil/games.html",
|
|
|
|
|
0x2da5ece0
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"K.Y.F.F. (Homebrew) http://slydogstudios.org/index.php/k-y-f-f/",
|
|
|
|
|
0x90d2e9f0
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Super PakPak (Homebrew) http://wiki.nesdev.com/w/index.php/Super_PakPak",
|
|
|
|
|
0x1394ded0
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Super Mario Bros. + Tetris + Nintendo World Cup (Europe)",
|
|
|
|
|
0x73298c87
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Super Mario Bros. + Tetris + Nintendo World Cup (Europe) (Rev A)",
|
|
|
|
|
0xf46ef39a
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2017-02-16 20:30:24 -06:00
|
|
|
static const struct cartridge_db famicom_4p_db_list[] =
|
2015-08-28 13:31:21 +02:00
|
|
|
{
|
|
|
|
|
{
|
|
|
|
|
"Bakutoushi Patton-Kun (Japan) (FDS)",
|
|
|
|
|
0xc39b3bb2
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Bomber Man II (Japan)",
|
|
|
|
|
0x0c401790
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Championship Bowling (Japan)",
|
|
|
|
|
0x9992f445
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Downtown - Nekketsu Koushinkyoku - Soreyuke Daiundoukai (Japan)",
|
|
|
|
|
0x3e470fe0
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Ike Ike! Nekketsu Hockey-bu - Subette Koronde Dairantou (Japan)",
|
|
|
|
|
0x4f032933
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Kunio-kun no Nekketsu Soccer League (Japan)",
|
|
|
|
|
0x4b5177e9
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Moero TwinBee - Cinnamon Hakase o Sukue! (Japan)",
|
|
|
|
|
0x9f03b11f
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Moero TwinBee - Cinnamon Hakase wo Sukue! (Japan) (FDS)",
|
|
|
|
|
0x13205221
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Nekketsu Kakutou Densetsu (Japan)",
|
|
|
|
|
0x37e24797
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Nekketsu Koukou Dodgeball-bu (Japan)",
|
|
|
|
|
0x62c67984
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Nekketsu! Street Basket - Ganbare Dunk Heroes (Japan)",
|
|
|
|
|
0x88062d9a
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Super Dodge Ball (USA) (3-4p with Game Genie code GEUOLZZA)",
|
|
|
|
|
0x689971f9
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Super Dodge Ball (USA) (patched) http://www.romhacking.net/hacks/71/",
|
|
|
|
|
0x4ff17864
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"U.S. Championship V'Ball (Japan)",
|
|
|
|
|
0x213cb3fb
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"U.S. Championship V'Ball (Japan) (Beta)",
|
|
|
|
|
0xd7077d96
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Wit's (Japan)",
|
|
|
|
|
0xb1b16b8a
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2021-10-20 16:31:45 +01:00
|
|
|
bool retro_load_game(const struct retro_game_info *info)
|
2014-03-30 22:35:00 +02:00
|
|
|
{
|
2017-07-13 00:23:01 +01:00
|
|
|
unsigned i, j;
|
2021-10-20 16:31:45 +01:00
|
|
|
const char *system_dir = NULL;
|
2015-08-28 13:31:21 +02:00
|
|
|
size_t fourscore_len = sizeof(fourscore_db_list) / sizeof(fourscore_db_list[0]);
|
|
|
|
|
size_t famicom_4p_len = sizeof(famicom_4p_db_list) / sizeof(famicom_4p_db_list[0]);
|
2025-09-01 22:07:53 +08:00
|
|
|
enum retro_pixel_format pixformat;
|
2015-08-28 12:22:54 +02:00
|
|
|
|
2014-12-05 16:04:50 +01:00
|
|
|
struct retro_input_descriptor desc[] = {
|
2021-10-22 13:30:27 +01:00
|
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left" },
|
|
|
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up" },
|
|
|
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down" },
|
|
|
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" },
|
|
|
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" },
|
|
|
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" },
|
2021-12-10 23:53:45 +02:00
|
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3, "A+B" },
|
2021-10-22 13:30:27 +01:00
|
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" },
|
|
|
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" },
|
2015-11-09 14:27:31 -06:00
|
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2, "(VSSystem) Insert Coin" },
|
2021-10-22 13:30:27 +01:00
|
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "(FDS) Disk Side Change" },
|
|
|
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, "(FDS) Insert/Eject Disk" },
|
|
|
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "Turbo A" },
|
|
|
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "Turbo B" },
|
2021-12-10 23:53:45 +02:00
|
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3, "Turbo A+B" },
|
2021-10-22 13:30:27 +01:00
|
|
|
|
|
|
|
|
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left" },
|
|
|
|
|
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up" },
|
|
|
|
|
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down" },
|
|
|
|
|
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" },
|
|
|
|
|
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" },
|
|
|
|
|
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" },
|
2021-12-10 23:53:45 +02:00
|
|
|
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3, "A+B" },
|
2021-10-22 13:30:27 +01:00
|
|
|
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" },
|
|
|
|
|
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" },
|
|
|
|
|
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "Turbo A" },
|
|
|
|
|
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "Turbo B" },
|
2021-12-10 23:53:45 +02:00
|
|
|
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3, "Turbo A+B" },
|
2021-10-22 13:30:27 +01:00
|
|
|
|
|
|
|
|
{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left" },
|
|
|
|
|
{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up" },
|
|
|
|
|
{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down" },
|
|
|
|
|
{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" },
|
|
|
|
|
{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" },
|
|
|
|
|
{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" },
|
2021-12-10 23:53:45 +02:00
|
|
|
{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3, "A+B" },
|
2021-10-22 13:30:27 +01:00
|
|
|
{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" },
|
|
|
|
|
{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" },
|
|
|
|
|
{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "Turbo A" },
|
|
|
|
|
{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "Turbo B" },
|
2021-12-10 23:53:45 +02:00
|
|
|
{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3, "Turbo A+B" },
|
2021-10-22 13:30:27 +01:00
|
|
|
|
|
|
|
|
{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left" },
|
|
|
|
|
{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up" },
|
|
|
|
|
{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down" },
|
|
|
|
|
{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" },
|
|
|
|
|
{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" },
|
|
|
|
|
{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" },
|
2021-12-10 23:53:45 +02:00
|
|
|
{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3, "A+B" },
|
2021-10-22 13:30:27 +01:00
|
|
|
{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" },
|
|
|
|
|
{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" },
|
|
|
|
|
{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "Turbo A" },
|
|
|
|
|
{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "Turbo B" },
|
2021-12-10 23:53:45 +02:00
|
|
|
{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3, "Turbo A+B" },
|
2014-12-05 16:04:50 +01:00
|
|
|
|
|
|
|
|
{ 0 },
|
|
|
|
|
};
|
2021-10-22 13:30:27 +01:00
|
|
|
|
|
|
|
|
struct retro_input_descriptor desc_ps[] = { /* ps: palette switching */
|
|
|
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left" },
|
|
|
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up" },
|
|
|
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down" },
|
|
|
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" },
|
|
|
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" },
|
|
|
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" },
|
2021-12-10 23:53:45 +02:00
|
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3, "A+B" },
|
2021-10-22 13:30:27 +01:00
|
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" },
|
|
|
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" },
|
|
|
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L2, "Switch Palette (+ Left/Right)" },
|
|
|
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2, "(VSSystem) Insert Coin" },
|
|
|
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "(FDS) Disk Side Change" },
|
|
|
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, "(FDS) Insert/Eject Disk" },
|
|
|
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "Turbo A" },
|
|
|
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "Turbo B" },
|
2021-12-10 23:53:45 +02:00
|
|
|
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3, "Turbo A+B" },
|
2021-10-22 13:30:27 +01:00
|
|
|
|
|
|
|
|
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left" },
|
|
|
|
|
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up" },
|
|
|
|
|
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down" },
|
|
|
|
|
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" },
|
|
|
|
|
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" },
|
|
|
|
|
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" },
|
2021-12-10 23:53:45 +02:00
|
|
|
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3, "A+B" },
|
2021-10-22 13:30:27 +01:00
|
|
|
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" },
|
|
|
|
|
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" },
|
|
|
|
|
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "Turbo A" },
|
|
|
|
|
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "Turbo B" },
|
2021-12-10 23:53:45 +02:00
|
|
|
{ 1, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3, "Turbo A+B" },
|
2021-10-22 13:30:27 +01:00
|
|
|
|
|
|
|
|
{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left" },
|
|
|
|
|
{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up" },
|
|
|
|
|
{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down" },
|
|
|
|
|
{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" },
|
|
|
|
|
{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" },
|
|
|
|
|
{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" },
|
2021-12-10 23:53:45 +02:00
|
|
|
{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3, "A+B" },
|
2021-10-22 13:30:27 +01:00
|
|
|
{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" },
|
|
|
|
|
{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" },
|
|
|
|
|
{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "Turbo A" },
|
|
|
|
|
{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "Turbo B" },
|
2021-12-10 23:53:45 +02:00
|
|
|
{ 2, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3, "Turbo A+B" },
|
2021-10-22 13:30:27 +01:00
|
|
|
|
|
|
|
|
{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left" },
|
|
|
|
|
{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up" },
|
|
|
|
|
{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down" },
|
|
|
|
|
{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" },
|
|
|
|
|
{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" },
|
|
|
|
|
{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" },
|
2021-12-10 23:53:45 +02:00
|
|
|
{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3, "A+B" },
|
2021-10-22 13:30:27 +01:00
|
|
|
{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" },
|
|
|
|
|
{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" },
|
|
|
|
|
{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "Turbo A" },
|
|
|
|
|
{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "Turbo B" },
|
2021-12-10 23:53:45 +02:00
|
|
|
{ 3, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3, "Turbo A+B" },
|
2021-10-22 13:30:27 +01:00
|
|
|
|
|
|
|
|
{ 0 },
|
|
|
|
|
};
|
|
|
|
|
|
2019-11-06 16:02:50 -08:00
|
|
|
size_t desc_base = 64;
|
2019-12-11 16:46:06 +01:00
|
|
|
struct retro_memory_descriptor descs[64 + 4];
|
2017-07-13 00:23:01 +01:00
|
|
|
struct retro_memory_map mmaps;
|
|
|
|
|
|
2021-10-20 16:31:45 +01:00
|
|
|
struct retro_game_info_ext *info_ext = NULL;
|
|
|
|
|
const uint8_t *content_data = NULL;
|
|
|
|
|
size_t content_size = 0;
|
|
|
|
|
char content_path[2048] = {0};
|
|
|
|
|
|
|
|
|
|
/* Attempt to fetch extended game info */
|
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_GAME_INFO_EXT, &info_ext) && info_ext)
|
|
|
|
|
{
|
|
|
|
|
content_data = (const uint8_t *)info_ext->data;
|
|
|
|
|
content_size = info_ext->size;
|
|
|
|
|
|
|
|
|
|
if (info_ext->file_in_archive)
|
|
|
|
|
{
|
|
|
|
|
/* We don't have a 'physical' file in this
|
|
|
|
|
* case, but the core still needs a filename
|
|
|
|
|
* in order to detect the region of iNES v1.0
|
|
|
|
|
* ROMs. We therefore fake it, using the content
|
|
|
|
|
* directory, canonical content name, and content
|
|
|
|
|
* file extension */
|
|
|
|
|
snprintf(content_path, sizeof(content_path), "%s%c%s.%s",
|
|
|
|
|
info_ext->dir,
|
|
|
|
|
PATH_DEFAULT_SLASH_C(),
|
|
|
|
|
info_ext->name,
|
|
|
|
|
info_ext->ext);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
strlcpy(content_path, info_ext->full_path,
|
|
|
|
|
sizeof(content_path));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (!info || string_is_empty(info->path))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
strlcpy(content_path, info->path,
|
|
|
|
|
sizeof(content_path));
|
|
|
|
|
}
|
2017-01-24 02:20:06 +01:00
|
|
|
|
2025-09-01 22:07:53 +08:00
|
|
|
#ifdef FRONTEND_SUPPORTS_RGB888
|
|
|
|
|
pixformat = RETRO_PIXEL_FORMAT_XRGB8888;
|
|
|
|
|
if(environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &pixformat))
|
2026-05-04 07:16:54 +02:00
|
|
|
{
|
2025-09-01 22:07:53 +08:00
|
|
|
log_cb.log(RETRO_LOG_INFO, "Frontend supports RGBX888 - will use that instead of XRGB1555.\n");
|
2026-05-04 07:16:54 +02:00
|
|
|
active_pixformat = pixformat;
|
|
|
|
|
}
|
2025-09-01 22:07:53 +08:00
|
|
|
#else
|
|
|
|
|
#if FRONTEND_SUPPORTS_RGB565
|
|
|
|
|
pixformat = RETRO_PIXEL_FORMAT_RGB565;
|
|
|
|
|
if(environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &pixformat))
|
2026-05-04 07:16:54 +02:00
|
|
|
{
|
2025-09-01 22:07:53 +08:00
|
|
|
log_cb.log(RETRO_LOG_INFO, "Frontend supports RGB565 - will use that instead of XRGB1555.\n");
|
2026-05-04 07:16:54 +02:00
|
|
|
active_pixformat = pixformat;
|
|
|
|
|
}
|
2025-09-01 22:07:53 +08:00
|
|
|
#endif
|
2020-10-27 04:00:53 +01:00
|
|
|
#endif
|
|
|
|
|
|
2019-03-11 15:39:57 +08:00
|
|
|
/* initialize some of the default variables */
|
|
|
|
|
#ifdef GEKKO
|
|
|
|
|
sndsamplerate = 32000;
|
|
|
|
|
#else
|
|
|
|
|
sndsamplerate = 48000;
|
|
|
|
|
#endif
|
|
|
|
|
sndquality = 0;
|
|
|
|
|
sndvolume = 150;
|
2021-06-05 15:05:07 +02:00
|
|
|
swapDuty = 0;
|
|
|
|
|
dendy = 0;
|
2021-03-01 06:33:17 +08:00
|
|
|
opt_region = 0;
|
2019-03-11 15:39:57 +08:00
|
|
|
|
|
|
|
|
/* Wii: initialize this or else last variable is passed through
|
|
|
|
|
* when loading another rom causing save state size change. */
|
|
|
|
|
serialize_size = 0;
|
|
|
|
|
|
2017-01-24 03:12:08 +01:00
|
|
|
PowerNES();
|
|
|
|
|
check_system_specs();
|
|
|
|
|
#if defined(_3DS)
|
|
|
|
|
fceu_video_out = (uint16_t*)linearMemAlign(256 * 240 * sizeof(uint16_t), 128);
|
|
|
|
|
#elif !defined(PSP)
|
2024-05-22 15:02:37 +02:00
|
|
|
|
2020-02-27 20:23:08 +08:00
|
|
|
#ifdef HAVE_NTSC_FILTER
|
2020-10-02 19:17:55 +08:00
|
|
|
#define FB_WIDTH NES_NTSC_WIDTH
|
2020-05-05 09:07:55 +08:00
|
|
|
#define FB_HEIGHT NES_HEIGHT
|
2020-02-27 20:23:08 +08:00
|
|
|
#else /* !HAVE_NTSC_FILTER */
|
2020-05-05 09:07:55 +08:00
|
|
|
#define FB_WIDTH NES_WIDTH
|
|
|
|
|
#define FB_HEIGHT NES_HEIGHT
|
2020-02-27 20:23:08 +08:00
|
|
|
#endif
|
2024-05-22 15:02:37 +02:00
|
|
|
|
|
|
|
|
#if defined(PS2)
|
|
|
|
|
fceu_video_out = (uint8_t*)malloc(FB_WIDTH * FB_HEIGHT * sizeof(uint8_t));
|
|
|
|
|
#else
|
2025-09-01 22:07:53 +08:00
|
|
|
fceu_video_out = (bpp_t*)malloc(FB_WIDTH * FB_HEIGHT * sizeof(bpp_t));
|
2024-05-22 15:02:37 +02:00
|
|
|
#endif
|
2017-01-24 03:12:08 +01:00
|
|
|
#endif
|
|
|
|
|
|
2021-10-20 16:31:45 +01:00
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &system_dir) && system_dir)
|
|
|
|
|
FCEUI_SetBaseDirectory(system_dir);
|
2015-08-28 12:22:54 +02:00
|
|
|
|
2020-02-25 07:13:42 +08:00
|
|
|
memset(base_palette, 0, sizeof(base_palette));
|
2020-01-26 10:22:42 +08:00
|
|
|
|
2015-08-28 12:22:54 +02:00
|
|
|
FCEUI_Initialize();
|
|
|
|
|
|
2017-09-09 14:54:02 +08:00
|
|
|
FCEUI_SetSoundVolume(sndvolume);
|
|
|
|
|
FCEUI_Sound(sndsamplerate);
|
2015-08-28 12:22:54 +02:00
|
|
|
|
2021-10-21 15:52:40 +01:00
|
|
|
GameInfo = (FCEUGI*)FCEUI_LoadGame(content_path, content_data, content_size,
|
|
|
|
|
frontend_post_load_init);
|
|
|
|
|
|
2015-08-28 12:22:54 +02:00
|
|
|
if (!GameInfo)
|
2016-07-19 19:06:09 +02:00
|
|
|
{
|
core: memory-safety, leak, and savestate-portability audit fixes
Squashed series of ~50 distinct bugs found during a multi-day
security/correctness audit, ranging from ROM-triggerable heap
corruption and savestate-triggered OOB read primitives down to
obscure cross-platform savestate breakage.
Build is clean on `make platform=unix` with zero new warnings.
CRITICAL (ROM-triggerable, exploitable on the user's machine)
=============================================================
* ines.c iNES 2.0 PRG/CHR exponent overflow leading to undersized
allocation followed by heap buffer overflow on fread (pow() with
attacker-chosen exponent up to 63). Cap exponent at 30 and
compute size in uint32 with explicit cap below 2 GiB before
storing in int.
* ines.c miscROMSize int wraparound from attacker-controlled PRG/
CHR sizes; previous '& 0x8000000' check missed common underflow
cases. Compute in int64 and reject <= 0 or > 128 MiB.
* unif.c MAPR chunk OOB heap read on chunks of size < 4. Validate
chunk size before allocating board-name buffer.
* unif.c chunk size truncation: int conversion + missing cap allowed
a 4 GiB chunk size to wrap. Cap at 16 MiB.
* unif.c FixRomSize infinite loop on size > 0x80000000. Cap input.
* unif.c DINF chunk: months[(m - 1) % 12] is UB for m==0; m comes
from the .unf file. Clamp m into [1..12] before subtracting.
* unif.c NAME chunk: GameInfo->name = malloc(...) was unchecked.
* nsf.c size underflow: FCEU_fgetsize() - 0x80 wraps to ~UINT64_MAX
on tiny files, propagating a huge size into uppow2/FCEU_malloc.
Validate size > 0x80 before subtracting.
* nsf.c NSFMaxBank * 4096 cap tightened from 1<<20 (UB on signed-int
overflow) to 1<<19 (fits in int).
* general.c uppow2 had signed-int UB (1 << 32) and wrapped to 0 for
inputs near uint32 max. Cap at 0x80000000.
* cheat.c SubCheats[256] BSS overflow when more than 256 GG/PAR
substitution cheats are active. The array is adjacent to MMapPtrs[64]
in BSS which is exposed via retro_get_memory_data, making this
overflow visible from outside the core.
* libretro.c retro_cheat_set strcpy stack overflow with arbitrary-
length cheat strings from the frontend. Use strlcpy.
CRITICAL (savestate-triggerable on a malicious .fcs file)
=========================================================
* fds.c FDS savestate OOB read primitive: InDisk loaded from
savestate is used as index into 8-element diskdata[] static
pointer array. A value 8..254 dereferences arbitrary memory as
a pointer (heap-read primitive). Also bounds-checked SelectDisk,
mapperFDS_block, mapperFDS_blockstart, mapperFDS_diskaddr,
mapperFDS_blocklen so blockstart+diskaddr stays within the 65500-
byte disk buffer.
* 11 mappers had savestate-loaded variables masked at write time
but not at restore time, used as indices into fixed arrays:
- 88.c, KS7037.c, 112.c cmd indexes reg[8]
- sachen.c (S8259, S74LS374N) cmd indexes latch[8]
- 357.c dipswitch indexes outer_bank[4]
- unrom512.c flash_state indexes erase_a/d/b[5]
- 368.c preg indexes banks[8]
- 69.c sndcmd indexes sreg[14]
- mmc2and4.c latch0/latch1 index creg[4]
None individually exploitable into RCE - the array writes corrupt
adjacent BSS with constrained data flowing in - but each is an
out-of-bounds read or write from attacker-controllable input.
HIGH (memory-safety, reachable on any load)
===========================================
* fceu-memory.c FCEU_malloc deref-of-NULL on allocation failure
('ret = 0; memset(ret, 0, size);').
* file.c multiple FCEUFILE/MakeMemWrap/MakeMemWrapBuffer unchecked
allocations and unchecked filestream_tell return.
* libretro.c GameInfo NULL-derefs in three entry points
(retro_set_controller_port_device, retro_get_memory_data,
retro_get_memory_size) reachable on operations called before a
successful load.
* libretro.c framebuffer leak ~256 KB per failed FCEUI_LoadGame
(the libretro frontend doesn't call retro_unload_game on a
failed load).
* libretro.c 3DS retro_deinit unchecked linearFree.
* libretro.c stereo / NTSC filter unchecked malloc returns.
* fceu.c FCEUI_LoadGame unchecked GameInfo malloc.
* fds.c SubLoad and FDSLoad unchecked FCEU_malloc on diskdatao
backup buffers (NULL-deref in subsequent memcpy).
* fceu.c AllocGenieRW partial-failure leak: AReadG allocated but
BWriteG malloc fails -> 256 KB leak per retry.
* input/bworld.c Update(): unbounded strcpy from attacker-supplied
data into 20-byte bdata. Replaced with length-bounded copy.
* cart.c setprg2r/4r and setchr1r/2r/4r/8r mask-underflow guards.
SetupCartPRGMapping/SetupCartCHRMapping compute (size >> N) - 1
which underflows to 0xFFFFFFFF for chips smaller than the unit.
setprg8r/16r/32r and setchr8r already had defensive size checks;
the smaller units did not. malee.c (2 KB chip) and mapper 218
(2 KB NTARAM-as-CHR) accidentally avoid OOB; the primitive is
unsafe for any future board.
* video.c FCEU_InitVirtualVideo XBuf/XDBuf partial-failure leak.
* video.c, fceu.c FCEU_DispMessage / FCEU_printf / FCEU_PrintError:
vsprintf into fixed stack buffers replaced with vsnprintf.
* core: validate magic-string read length on FDS/UNIF/NSF load
(reject files too short to contain the magic so previous static
buffer / stack garbage doesn't spuriously match).
LEAKS (every cart load/unload cycle)
====================================
* mmc5.c MMC5 cart-side: WRAM (up to 64K) + MMC5fill (1K) + ExRAM
(1K) leaked per cycle. NSFMMC5_Close existed but was only used
for NSF code path.
* onebus.c Mapper 270/436: 8 KB CHRRAM leaked per cycle.
* 330.c, 375.c, 528.c: 8 KB WRAM each, no Close at all.
CORRECTNESS (UB / ABI / endianness)
===================================
* fceu-endian.c FlipByteOrder loop bound was 'count' instead of
'count/2', making it a no-op for every even count and silently
breaking savestate portability for every FCEUSTATE_RLSB-marked
field. The '#ifndef GEKKO' workarounds scattered through the
codebase (sound.c, vrc7.c, vrc6.c) were symptoms of this root
cause.
* state.c AddExState bounds check ran AFTER writing the entry, so
when SFEXINDEX hit the 63-entry cap the next call would write
the entry then immediately overwrite it with the terminator.
* state.c FCEUSS_Save_Mem: post-save callback was guarded by
'if (SPreSave)' instead of 'if (SPostSave)'.
* Sequence-point UB in counter expressions across 4 board files,
e.g. 'x = !x ? a : --x' and 'x = ++x % n'. GCC -Wsequence-point
flags all five sites (285.c, 413.c, asic_mmc3.c, jyasic.c).
* SFORMAT size mismatches between declared variable type and
AddExState size argument:
- asic_vrc3.c VRC3_count/VRC3_reload (uint16, saved as 1 byte)
- mmc3.c m555_count (uint32, saved as 2 bytes), m555_count_-
expired (uint8, saved as 2 bytes - OOB write into adjacent
BSS).
* unrom512.c UNROM-512 mapper 30 .srm save format: host-endian
uint32 flash write counters. Now stored as LE on disk regardless
of host (Battle Kid 2, Twin Dragons, Lizard, Sole, etc.).
* ~70 multi-byte single-variable SFORMAT/AddExState entries across
36 board files were saved as host-byte-order. After the
FlipByteOrder fix, these are now byte-swapped on BE so cross-
platform savestates round-trip correctly.
* coolgirl.c new ExStateLE() macro added; 22 multi-byte sites.
* pic16c5x.c 11 multi-byte AddExState calls (m_PC, m_PREVPC,
m_CONFIG, m_WDT, m_prescaler, m_opcode, m_STACK[0/1], m_icount,
m_delay_timer, m_rtcc, m_inst_cycles, m_clock2cycle).
* onebus.c PowerJoy Supermax submapper detection used non-portable
*(uint32*)&info->MD5 cast that read different bytes on LE vs BE.
* fds.c clean up redundant FCEUSTATE_RLSB encoding in AddExState
calls that also passed type=1 (idempotent OR; readability fix).
DOCUMENTATION
=============
* input.c UpdateGP's *(uint32*)data cast looks like a typical
endian bug but is actually correct (the libretro frontend builds
JSReturn with matching host-uint32 shifts). Comment added to
prevent future "fixes" from breaking it.
Limitations not addressed
=========================
* Element-stride-aware byte swapping. The savestate byte-swap
mechanism (FlipByteOrder over the entire SFORMAT entry buffer)
is structurally wrong for arrays of multi-byte values: it
reverses the whole buffer end-to-end instead of byte-swapping
each element. Several places that need cross-platform-portable
arrays (VRC7 sound state, jyasic chr[8]) work around this by
either splitting arrays into per-element SFORMAT entries (n106
PlayIndex, bandai reg) or by skipping save entirely on BE via
#ifndef GEKKO. A proper fix would extend the size encoding with
an element-stride field. Left for a future change because it
would change the savestate format.
* Strict-aliasing UB in ppu.c (around 9 sites doing
*(uint32*)uint8_buf for fast 4-byte writes via FCEU_dwmemset).
Works in practice with all common compilers because the patterns
are byte-symmetric, but is formally UB.
* FCEU_gmalloc calls exit(1) on OOM. A libretro core should never
exit() because that takes down the whole frontend. Used by
100+ call sites; refactoring to return-NULL is out of scope here.
Testing
=======
* Build: clean on `make platform=unix` with -O2; no new warnings.
* FlipByteOrder fix verified by hand-trace and a standalone unit
test for counts 2, 4, 8.
* uppow2 fix verified by unit test across 13 boundary cases.
* SFORMAT size mismatches and missing-RLSB cases identified by
Python static-analysis scripts that cross-reference SFORMAT
entries against variable declarations.
* iNES 2.0 exponent fix verified by hand-tracing what byte 0xFF
produces post-fix: exp=30 (capped), mult=7, size=3 GiB nominal,
capped to 1 GiB, capped to 2 GiB by uppow2, FCEU_malloc returns
NULL on most systems, loader returns 0. No heap overflow for
any input byte.
* Savestate-loaded array index audit: built a Python scanner that
extracts each AddExState/SFORMAT entry and cross-references the
variable name against array-index uses in the same file. All
flagged sites covered.
* A libFuzzer harness and seed corpus generator (fuzz_main.c,
gen_seed_corpus.py) accompany this submission for ongoing
regression testing.
2026-05-04 02:15:40 +02:00
|
|
|
/* On load failure the libretro frontend won't call retro_unload_game,
|
|
|
|
|
* so free the framebuffer we just allocated to avoid leaking ~256 KB
|
|
|
|
|
* per failed load. */
|
|
|
|
|
#if defined(_3DS)
|
|
|
|
|
if (fceu_video_out)
|
|
|
|
|
linearFree(fceu_video_out);
|
|
|
|
|
#elif !defined(PSP)
|
|
|
|
|
if (fceu_video_out)
|
|
|
|
|
free(fceu_video_out);
|
|
|
|
|
#endif
|
|
|
|
|
fceu_video_out = NULL;
|
2021-10-21 15:52:40 +01:00
|
|
|
#if 0
|
|
|
|
|
/* An error message here is superfluous - the frontend
|
|
|
|
|
* will report that content loading has failed */
|
|
|
|
|
FCEUD_DispMessage(RETRO_LOG_ERROR, 3000, "ROM loading failed...");
|
|
|
|
|
#endif
|
2014-04-17 21:32:53 +02:00
|
|
|
return false;
|
2016-07-19 19:06:09 +02:00
|
|
|
}
|
2015-08-28 12:22:54 +02:00
|
|
|
|
2021-10-22 13:30:27 +01:00
|
|
|
if (palette_switch_enabled)
|
|
|
|
|
environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc_ps);
|
|
|
|
|
else
|
|
|
|
|
environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc);
|
|
|
|
|
|
2019-02-24 01:51:44 +08:00
|
|
|
for (i = 0; i < MAX_PORTS; i++) {
|
2020-06-04 13:26:02 +08:00
|
|
|
FCEUI_SetInput(i, SI_GAMEPAD, &nes_input.JSReturn, 0);
|
2020-05-05 13:35:30 +08:00
|
|
|
nes_input.type[i] = RETRO_DEVICE_JOYPAD;
|
2019-02-24 01:51:44 +08:00
|
|
|
}
|
2018-11-23 01:59:20 +08:00
|
|
|
|
2024-10-15 22:35:46 +08:00
|
|
|
external_palette_exist = palette_game_available;
|
2017-09-05 21:37:21 +08:00
|
|
|
if (external_palette_exist)
|
2021-10-20 16:31:45 +01:00
|
|
|
FCEU_printf(" Loading custom palette: %s%cnes.pal\n",
|
|
|
|
|
system_dir, PATH_DEFAULT_SLASH_C());
|
2017-09-04 19:20:27 +08:00
|
|
|
|
2020-02-07 14:53:14 +08:00
|
|
|
/* Save region and dendy mode for region-auto detect */
|
2021-06-05 15:05:07 +02:00
|
|
|
systemRegion = (dendy << 1) | (retro_get_region() & 1);
|
2017-10-07 13:35:00 +08:00
|
|
|
|
2020-02-25 09:08:44 +08:00
|
|
|
current_palette = 0;
|
|
|
|
|
|
|
|
|
|
ResetPalette();
|
2015-08-28 12:22:54 +02:00
|
|
|
FCEUD_SoundToggle();
|
2017-01-02 03:36:39 +01:00
|
|
|
check_variables(true);
|
2022-04-06 13:36:22 +01:00
|
|
|
stereo_filter_init();
|
2019-05-11 23:11:55 +08:00
|
|
|
PowerNES();
|
2014-03-30 22:15:17 +02:00
|
|
|
|
2015-08-28 13:31:21 +02:00
|
|
|
FCEUI_DisableFourScore(1);
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < fourscore_len; i++)
|
|
|
|
|
{
|
2020-02-16 21:54:21 +08:00
|
|
|
if (fourscore_db_list[i].crc == iNESCart.CRC32)
|
2015-08-28 13:31:21 +02:00
|
|
|
{
|
|
|
|
|
FCEUI_DisableFourScore(0);
|
2020-05-05 13:35:30 +08:00
|
|
|
nes_input.enable_4player = true;
|
2015-08-28 13:31:21 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < famicom_4p_len; i++)
|
|
|
|
|
{
|
2020-02-16 21:54:21 +08:00
|
|
|
if (famicom_4p_db_list[i].crc == iNESCart.CRC32)
|
2015-08-28 13:31:21 +02:00
|
|
|
{
|
2018-11-23 01:59:20 +08:00
|
|
|
GameInfo->inputfc = SIFC_4PLAYER;
|
2020-06-04 13:26:02 +08:00
|
|
|
FCEUI_SetInputFC(SIFC_4PLAYER, &nes_input.JSReturn, 0);
|
2020-05-05 13:35:30 +08:00
|
|
|
nes_input.enable_4player = true;
|
2015-08-28 13:31:21 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-13 00:23:01 +01:00
|
|
|
memset(descs, 0, sizeof(descs));
|
|
|
|
|
i = 0;
|
|
|
|
|
|
2019-11-06 16:02:50 -08:00
|
|
|
for (j = 0; j < desc_base; j++)
|
2019-11-06 16:06:09 -08:00
|
|
|
{
|
2019-11-06 16:02:50 -08:00
|
|
|
if (MMapPtrs[j] != NULL)
|
2019-11-06 16:06:09 -08:00
|
|
|
{
|
|
|
|
|
descs[i].ptr = MMapPtrs[j];
|
|
|
|
|
descs[i].start = j * 1024;
|
|
|
|
|
descs[i].len = 1024;
|
|
|
|
|
descs[i].select = 0;
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-01-24 19:43:40 +08:00
|
|
|
/* This doesn't map in 2004--2007 but those aren't really
|
2020-01-26 10:22:42 +08:00
|
|
|
* worthwhile to read from on a vblank anyway
|
2020-01-24 19:43:40 +08:00
|
|
|
*/
|
2019-11-06 16:02:50 -08:00
|
|
|
descs[i].flags = 0;
|
|
|
|
|
descs[i].ptr = PPU;
|
|
|
|
|
descs[i].offset = 0;
|
|
|
|
|
descs[i].start = 0x2000;
|
|
|
|
|
descs[i].select = 0;
|
|
|
|
|
descs[i].disconnect = 0;
|
|
|
|
|
descs[i].len = 4;
|
|
|
|
|
descs[i].addrspace="PPUREG";
|
|
|
|
|
i++;
|
2020-01-24 19:43:40 +08:00
|
|
|
/* In the future, it would be good to map pattern tables 1 and 2,
|
|
|
|
|
* but these must be remapped often
|
|
|
|
|
*/
|
2019-11-06 16:02:50 -08:00
|
|
|
/* descs[i] = (struct retro_memory_descriptor){0, ????, 0, 0x0000 | PPU_BIT, PPU_BIT, PPU_BIT, 0x1000, "PAT0"}; */
|
|
|
|
|
/* i++; */
|
|
|
|
|
/* descs[i] = (struct retro_memory_descriptor){0, ????, 0, 0x1000 | PPU_BIT, PPU_BIT, PPU_BIT, 0x1000, "PAT1"}; */
|
|
|
|
|
/* i++; */
|
2020-01-24 19:43:40 +08:00
|
|
|
/* Likewise it would be better to use "vnapage" for this but
|
|
|
|
|
* RetroArch API is inconvenient for handles like that, so we'll
|
|
|
|
|
* just blithely assume the client will handle mapping and that
|
|
|
|
|
* we'll ignore those carts that have extra NTARAM.
|
|
|
|
|
*/
|
2019-11-06 16:02:50 -08:00
|
|
|
descs[i].flags = 0;
|
|
|
|
|
descs[i].ptr = NTARAM;
|
|
|
|
|
descs[i].offset = 0;
|
|
|
|
|
descs[i].start = PPU_BIT | 0x2000;
|
|
|
|
|
descs[i].select = PPU_BIT;
|
|
|
|
|
descs[i].disconnect = PPU_BIT;
|
|
|
|
|
descs[i].len = 0x0800;
|
|
|
|
|
descs[i].addrspace="NTARAM";
|
|
|
|
|
i++;
|
|
|
|
|
descs[i].flags = 0;
|
|
|
|
|
descs[i].ptr = PALRAM;
|
|
|
|
|
descs[i].offset = 0;
|
|
|
|
|
descs[i].start = PPU_BIT | 0x3000;
|
|
|
|
|
descs[i].select = PPU_BIT;
|
|
|
|
|
descs[i].disconnect = PPU_BIT;
|
|
|
|
|
descs[i].len = 0x020;
|
|
|
|
|
descs[i].addrspace="PALRAM";
|
|
|
|
|
i++;
|
2020-01-24 19:43:40 +08:00
|
|
|
/* OAM doesn't really live anywhere in address space so I'll put it at 0x4000. */
|
2019-11-06 16:02:50 -08:00
|
|
|
descs[i].flags = 0;
|
|
|
|
|
descs[i].ptr = SPRAM;
|
|
|
|
|
descs[i].offset = 0;
|
|
|
|
|
descs[i].start = PPU_BIT | 0x4000;
|
|
|
|
|
descs[i].select = PPU_BIT;
|
|
|
|
|
descs[i].disconnect = PPU_BIT;
|
|
|
|
|
descs[i].len = 0x100;
|
|
|
|
|
descs[i].addrspace="OAM";
|
|
|
|
|
i++;
|
2017-07-13 00:23:01 +01:00
|
|
|
mmaps.descriptors = descs;
|
|
|
|
|
mmaps.num_descriptors = i;
|
|
|
|
|
environ_cb(RETRO_ENVIRONMENT_SET_MEMORY_MAPS, &mmaps);
|
2017-08-24 07:31:25 +08:00
|
|
|
|
2014-03-30 22:35:00 +02:00
|
|
|
return true;
|
2014-03-30 22:15:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool retro_load_game_special(
|
2014-03-30 22:35:00 +02:00
|
|
|
unsigned game_type,
|
|
|
|
|
const struct retro_game_info *info, size_t num_info
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
2014-03-30 22:15:17 +02:00
|
|
|
}
|
|
|
|
|
|
2014-03-30 22:35:00 +02:00
|
|
|
void retro_unload_game(void)
|
|
|
|
|
{
|
|
|
|
|
FCEUI_CloseGame();
|
2017-01-24 03:12:08 +01:00
|
|
|
#if defined(_3DS)
|
|
|
|
|
if (fceu_video_out)
|
|
|
|
|
linearFree(fceu_video_out);
|
|
|
|
|
#else
|
|
|
|
|
if (fceu_video_out)
|
|
|
|
|
free(fceu_video_out);
|
|
|
|
|
fceu_video_out = NULL;
|
|
|
|
|
#endif
|
2019-02-22 00:05:24 +01:00
|
|
|
#if defined(RENDER_GSKIT_PS2)
|
|
|
|
|
ps2 = NULL;
|
|
|
|
|
#endif
|
2020-01-26 10:22:42 +08:00
|
|
|
#ifdef HAVE_NTSC_FILTER
|
2020-02-25 09:08:44 +08:00
|
|
|
NTSCFilter_Cleanup();
|
2020-01-26 10:22:42 +08:00
|
|
|
#endif
|
2014-03-30 22:15:17 +02:00
|
|
|
}
|
|
|
|
|
|
2014-03-30 22:35:00 +02:00
|
|
|
unsigned retro_get_region(void)
|
|
|
|
|
{
|
|
|
|
|
return FSettings.PAL ? RETRO_REGION_PAL : RETRO_REGION_NTSC;
|
2014-03-30 22:15:17 +02:00
|
|
|
}
|
|
|
|
|
|
2016-03-11 03:35:26 +01:00
|
|
|
void *retro_get_memory_data(unsigned type)
|
|
|
|
|
{
|
|
|
|
|
uint8_t* data;
|
2014-03-30 22:15:17 +02:00
|
|
|
|
2016-03-11 03:35:26 +01:00
|
|
|
switch(type)
|
|
|
|
|
{
|
|
|
|
|
case RETRO_MEMORY_SAVE_RAM:
|
2018-11-30 09:22:36 +08:00
|
|
|
if (iNESCart.battery && iNESCart.SaveGame[0] && iNESCart.SaveGameLen[0])
|
2017-08-31 19:33:22 +08:00
|
|
|
return iNESCart.SaveGame[0];
|
2018-11-30 09:22:36 +08:00
|
|
|
else if (UNIFCart.battery && UNIFCart.SaveGame[0] && UNIFCart.SaveGameLen[0])
|
2016-03-11 03:35:26 +01:00
|
|
|
return UNIFCart.SaveGame[0];
|
core: memory-safety, leak, and savestate-portability audit fixes
Squashed series of ~50 distinct bugs found during a multi-day
security/correctness audit, ranging from ROM-triggerable heap
corruption and savestate-triggered OOB read primitives down to
obscure cross-platform savestate breakage.
Build is clean on `make platform=unix` with zero new warnings.
CRITICAL (ROM-triggerable, exploitable on the user's machine)
=============================================================
* ines.c iNES 2.0 PRG/CHR exponent overflow leading to undersized
allocation followed by heap buffer overflow on fread (pow() with
attacker-chosen exponent up to 63). Cap exponent at 30 and
compute size in uint32 with explicit cap below 2 GiB before
storing in int.
* ines.c miscROMSize int wraparound from attacker-controlled PRG/
CHR sizes; previous '& 0x8000000' check missed common underflow
cases. Compute in int64 and reject <= 0 or > 128 MiB.
* unif.c MAPR chunk OOB heap read on chunks of size < 4. Validate
chunk size before allocating board-name buffer.
* unif.c chunk size truncation: int conversion + missing cap allowed
a 4 GiB chunk size to wrap. Cap at 16 MiB.
* unif.c FixRomSize infinite loop on size > 0x80000000. Cap input.
* unif.c DINF chunk: months[(m - 1) % 12] is UB for m==0; m comes
from the .unf file. Clamp m into [1..12] before subtracting.
* unif.c NAME chunk: GameInfo->name = malloc(...) was unchecked.
* nsf.c size underflow: FCEU_fgetsize() - 0x80 wraps to ~UINT64_MAX
on tiny files, propagating a huge size into uppow2/FCEU_malloc.
Validate size > 0x80 before subtracting.
* nsf.c NSFMaxBank * 4096 cap tightened from 1<<20 (UB on signed-int
overflow) to 1<<19 (fits in int).
* general.c uppow2 had signed-int UB (1 << 32) and wrapped to 0 for
inputs near uint32 max. Cap at 0x80000000.
* cheat.c SubCheats[256] BSS overflow when more than 256 GG/PAR
substitution cheats are active. The array is adjacent to MMapPtrs[64]
in BSS which is exposed via retro_get_memory_data, making this
overflow visible from outside the core.
* libretro.c retro_cheat_set strcpy stack overflow with arbitrary-
length cheat strings from the frontend. Use strlcpy.
CRITICAL (savestate-triggerable on a malicious .fcs file)
=========================================================
* fds.c FDS savestate OOB read primitive: InDisk loaded from
savestate is used as index into 8-element diskdata[] static
pointer array. A value 8..254 dereferences arbitrary memory as
a pointer (heap-read primitive). Also bounds-checked SelectDisk,
mapperFDS_block, mapperFDS_blockstart, mapperFDS_diskaddr,
mapperFDS_blocklen so blockstart+diskaddr stays within the 65500-
byte disk buffer.
* 11 mappers had savestate-loaded variables masked at write time
but not at restore time, used as indices into fixed arrays:
- 88.c, KS7037.c, 112.c cmd indexes reg[8]
- sachen.c (S8259, S74LS374N) cmd indexes latch[8]
- 357.c dipswitch indexes outer_bank[4]
- unrom512.c flash_state indexes erase_a/d/b[5]
- 368.c preg indexes banks[8]
- 69.c sndcmd indexes sreg[14]
- mmc2and4.c latch0/latch1 index creg[4]
None individually exploitable into RCE - the array writes corrupt
adjacent BSS with constrained data flowing in - but each is an
out-of-bounds read or write from attacker-controllable input.
HIGH (memory-safety, reachable on any load)
===========================================
* fceu-memory.c FCEU_malloc deref-of-NULL on allocation failure
('ret = 0; memset(ret, 0, size);').
* file.c multiple FCEUFILE/MakeMemWrap/MakeMemWrapBuffer unchecked
allocations and unchecked filestream_tell return.
* libretro.c GameInfo NULL-derefs in three entry points
(retro_set_controller_port_device, retro_get_memory_data,
retro_get_memory_size) reachable on operations called before a
successful load.
* libretro.c framebuffer leak ~256 KB per failed FCEUI_LoadGame
(the libretro frontend doesn't call retro_unload_game on a
failed load).
* libretro.c 3DS retro_deinit unchecked linearFree.
* libretro.c stereo / NTSC filter unchecked malloc returns.
* fceu.c FCEUI_LoadGame unchecked GameInfo malloc.
* fds.c SubLoad and FDSLoad unchecked FCEU_malloc on diskdatao
backup buffers (NULL-deref in subsequent memcpy).
* fceu.c AllocGenieRW partial-failure leak: AReadG allocated but
BWriteG malloc fails -> 256 KB leak per retry.
* input/bworld.c Update(): unbounded strcpy from attacker-supplied
data into 20-byte bdata. Replaced with length-bounded copy.
* cart.c setprg2r/4r and setchr1r/2r/4r/8r mask-underflow guards.
SetupCartPRGMapping/SetupCartCHRMapping compute (size >> N) - 1
which underflows to 0xFFFFFFFF for chips smaller than the unit.
setprg8r/16r/32r and setchr8r already had defensive size checks;
the smaller units did not. malee.c (2 KB chip) and mapper 218
(2 KB NTARAM-as-CHR) accidentally avoid OOB; the primitive is
unsafe for any future board.
* video.c FCEU_InitVirtualVideo XBuf/XDBuf partial-failure leak.
* video.c, fceu.c FCEU_DispMessage / FCEU_printf / FCEU_PrintError:
vsprintf into fixed stack buffers replaced with vsnprintf.
* core: validate magic-string read length on FDS/UNIF/NSF load
(reject files too short to contain the magic so previous static
buffer / stack garbage doesn't spuriously match).
LEAKS (every cart load/unload cycle)
====================================
* mmc5.c MMC5 cart-side: WRAM (up to 64K) + MMC5fill (1K) + ExRAM
(1K) leaked per cycle. NSFMMC5_Close existed but was only used
for NSF code path.
* onebus.c Mapper 270/436: 8 KB CHRRAM leaked per cycle.
* 330.c, 375.c, 528.c: 8 KB WRAM each, no Close at all.
CORRECTNESS (UB / ABI / endianness)
===================================
* fceu-endian.c FlipByteOrder loop bound was 'count' instead of
'count/2', making it a no-op for every even count and silently
breaking savestate portability for every FCEUSTATE_RLSB-marked
field. The '#ifndef GEKKO' workarounds scattered through the
codebase (sound.c, vrc7.c, vrc6.c) were symptoms of this root
cause.
* state.c AddExState bounds check ran AFTER writing the entry, so
when SFEXINDEX hit the 63-entry cap the next call would write
the entry then immediately overwrite it with the terminator.
* state.c FCEUSS_Save_Mem: post-save callback was guarded by
'if (SPreSave)' instead of 'if (SPostSave)'.
* Sequence-point UB in counter expressions across 4 board files,
e.g. 'x = !x ? a : --x' and 'x = ++x % n'. GCC -Wsequence-point
flags all five sites (285.c, 413.c, asic_mmc3.c, jyasic.c).
* SFORMAT size mismatches between declared variable type and
AddExState size argument:
- asic_vrc3.c VRC3_count/VRC3_reload (uint16, saved as 1 byte)
- mmc3.c m555_count (uint32, saved as 2 bytes), m555_count_-
expired (uint8, saved as 2 bytes - OOB write into adjacent
BSS).
* unrom512.c UNROM-512 mapper 30 .srm save format: host-endian
uint32 flash write counters. Now stored as LE on disk regardless
of host (Battle Kid 2, Twin Dragons, Lizard, Sole, etc.).
* ~70 multi-byte single-variable SFORMAT/AddExState entries across
36 board files were saved as host-byte-order. After the
FlipByteOrder fix, these are now byte-swapped on BE so cross-
platform savestates round-trip correctly.
* coolgirl.c new ExStateLE() macro added; 22 multi-byte sites.
* pic16c5x.c 11 multi-byte AddExState calls (m_PC, m_PREVPC,
m_CONFIG, m_WDT, m_prescaler, m_opcode, m_STACK[0/1], m_icount,
m_delay_timer, m_rtcc, m_inst_cycles, m_clock2cycle).
* onebus.c PowerJoy Supermax submapper detection used non-portable
*(uint32*)&info->MD5 cast that read different bytes on LE vs BE.
* fds.c clean up redundant FCEUSTATE_RLSB encoding in AddExState
calls that also passed type=1 (idempotent OR; readability fix).
DOCUMENTATION
=============
* input.c UpdateGP's *(uint32*)data cast looks like a typical
endian bug but is actually correct (the libretro frontend builds
JSReturn with matching host-uint32 shifts). Comment added to
prevent future "fixes" from breaking it.
Limitations not addressed
=========================
* Element-stride-aware byte swapping. The savestate byte-swap
mechanism (FlipByteOrder over the entire SFORMAT entry buffer)
is structurally wrong for arrays of multi-byte values: it
reverses the whole buffer end-to-end instead of byte-swapping
each element. Several places that need cross-platform-portable
arrays (VRC7 sound state, jyasic chr[8]) work around this by
either splitting arrays into per-element SFORMAT entries (n106
PlayIndex, bandai reg) or by skipping save entirely on BE via
#ifndef GEKKO. A proper fix would extend the size encoding with
an element-stride field. Left for a future change because it
would change the savestate format.
* Strict-aliasing UB in ppu.c (around 9 sites doing
*(uint32*)uint8_buf for fast 4-byte writes via FCEU_dwmemset).
Works in practice with all common compilers because the patterns
are byte-symmetric, but is formally UB.
* FCEU_gmalloc calls exit(1) on OOM. A libretro core should never
exit() because that takes down the whole frontend. Used by
100+ call sites; refactoring to return-NULL is out of scope here.
Testing
=======
* Build: clean on `make platform=unix` with -O2; no new warnings.
* FlipByteOrder fix verified by hand-trace and a standalone unit
test for counts 2, 4, 8.
* uppow2 fix verified by unit test across 13 boundary cases.
* SFORMAT size mismatches and missing-RLSB cases identified by
Python static-analysis scripts that cross-reference SFORMAT
entries against variable declarations.
* iNES 2.0 exponent fix verified by hand-tracing what byte 0xFF
produces post-fix: exp=30 (capped), mult=7, size=3 GiB nominal,
capped to 1 GiB, capped to 2 GiB by uppow2, FCEU_malloc returns
NULL on most systems, loader returns 0. No heap overflow for
any input byte.
* Savestate-loaded array index audit: built a Python scanner that
extracts each AddExState/SFORMAT entry and cross-references the
variable name against array-index uses in the same file. All
flagged sites covered.
* A libFuzzer harness and seed corpus generator (fuzz_main.c,
gen_seed_corpus.py) accompany this submission for ongoing
regression testing.
2026-05-04 02:15:40 +02:00
|
|
|
else if (GameInfo && GameInfo->type == GIT_FDS)
|
2019-01-04 17:13:09 +08:00
|
|
|
return FDSROM_ptr();
|
2016-03-11 03:35:26 +01:00
|
|
|
else
|
|
|
|
|
data = NULL;
|
|
|
|
|
break;
|
|
|
|
|
case RETRO_MEMORY_SYSTEM_RAM:
|
|
|
|
|
data = RAM;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
data = NULL;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2017-02-16 20:30:24 -06:00
|
|
|
|
2016-03-11 03:35:26 +01:00
|
|
|
return data;
|
2014-03-30 22:15:17 +02:00
|
|
|
}
|
|
|
|
|
|
2016-03-11 03:35:26 +01:00
|
|
|
size_t retro_get_memory_size(unsigned type)
|
2014-03-30 22:35:00 +02:00
|
|
|
{
|
2016-03-11 03:35:26 +01:00
|
|
|
unsigned size;
|
2014-03-30 22:15:17 +02:00
|
|
|
|
2016-03-11 03:35:26 +01:00
|
|
|
switch(type)
|
|
|
|
|
{
|
|
|
|
|
case RETRO_MEMORY_SAVE_RAM:
|
2018-11-30 09:22:36 +08:00
|
|
|
if (iNESCart.battery && iNESCart.SaveGame[0] && iNESCart.SaveGameLen[0])
|
2016-03-11 03:35:26 +01:00
|
|
|
size = iNESCart.SaveGameLen[0];
|
2018-11-30 09:22:36 +08:00
|
|
|
else if (UNIFCart.battery && UNIFCart.SaveGame[0] && UNIFCart.SaveGameLen[0])
|
2016-03-11 03:35:26 +01:00
|
|
|
size = UNIFCart.SaveGameLen[0];
|
core: memory-safety, leak, and savestate-portability audit fixes
Squashed series of ~50 distinct bugs found during a multi-day
security/correctness audit, ranging from ROM-triggerable heap
corruption and savestate-triggered OOB read primitives down to
obscure cross-platform savestate breakage.
Build is clean on `make platform=unix` with zero new warnings.
CRITICAL (ROM-triggerable, exploitable on the user's machine)
=============================================================
* ines.c iNES 2.0 PRG/CHR exponent overflow leading to undersized
allocation followed by heap buffer overflow on fread (pow() with
attacker-chosen exponent up to 63). Cap exponent at 30 and
compute size in uint32 with explicit cap below 2 GiB before
storing in int.
* ines.c miscROMSize int wraparound from attacker-controlled PRG/
CHR sizes; previous '& 0x8000000' check missed common underflow
cases. Compute in int64 and reject <= 0 or > 128 MiB.
* unif.c MAPR chunk OOB heap read on chunks of size < 4. Validate
chunk size before allocating board-name buffer.
* unif.c chunk size truncation: int conversion + missing cap allowed
a 4 GiB chunk size to wrap. Cap at 16 MiB.
* unif.c FixRomSize infinite loop on size > 0x80000000. Cap input.
* unif.c DINF chunk: months[(m - 1) % 12] is UB for m==0; m comes
from the .unf file. Clamp m into [1..12] before subtracting.
* unif.c NAME chunk: GameInfo->name = malloc(...) was unchecked.
* nsf.c size underflow: FCEU_fgetsize() - 0x80 wraps to ~UINT64_MAX
on tiny files, propagating a huge size into uppow2/FCEU_malloc.
Validate size > 0x80 before subtracting.
* nsf.c NSFMaxBank * 4096 cap tightened from 1<<20 (UB on signed-int
overflow) to 1<<19 (fits in int).
* general.c uppow2 had signed-int UB (1 << 32) and wrapped to 0 for
inputs near uint32 max. Cap at 0x80000000.
* cheat.c SubCheats[256] BSS overflow when more than 256 GG/PAR
substitution cheats are active. The array is adjacent to MMapPtrs[64]
in BSS which is exposed via retro_get_memory_data, making this
overflow visible from outside the core.
* libretro.c retro_cheat_set strcpy stack overflow with arbitrary-
length cheat strings from the frontend. Use strlcpy.
CRITICAL (savestate-triggerable on a malicious .fcs file)
=========================================================
* fds.c FDS savestate OOB read primitive: InDisk loaded from
savestate is used as index into 8-element diskdata[] static
pointer array. A value 8..254 dereferences arbitrary memory as
a pointer (heap-read primitive). Also bounds-checked SelectDisk,
mapperFDS_block, mapperFDS_blockstart, mapperFDS_diskaddr,
mapperFDS_blocklen so blockstart+diskaddr stays within the 65500-
byte disk buffer.
* 11 mappers had savestate-loaded variables masked at write time
but not at restore time, used as indices into fixed arrays:
- 88.c, KS7037.c, 112.c cmd indexes reg[8]
- sachen.c (S8259, S74LS374N) cmd indexes latch[8]
- 357.c dipswitch indexes outer_bank[4]
- unrom512.c flash_state indexes erase_a/d/b[5]
- 368.c preg indexes banks[8]
- 69.c sndcmd indexes sreg[14]
- mmc2and4.c latch0/latch1 index creg[4]
None individually exploitable into RCE - the array writes corrupt
adjacent BSS with constrained data flowing in - but each is an
out-of-bounds read or write from attacker-controllable input.
HIGH (memory-safety, reachable on any load)
===========================================
* fceu-memory.c FCEU_malloc deref-of-NULL on allocation failure
('ret = 0; memset(ret, 0, size);').
* file.c multiple FCEUFILE/MakeMemWrap/MakeMemWrapBuffer unchecked
allocations and unchecked filestream_tell return.
* libretro.c GameInfo NULL-derefs in three entry points
(retro_set_controller_port_device, retro_get_memory_data,
retro_get_memory_size) reachable on operations called before a
successful load.
* libretro.c framebuffer leak ~256 KB per failed FCEUI_LoadGame
(the libretro frontend doesn't call retro_unload_game on a
failed load).
* libretro.c 3DS retro_deinit unchecked linearFree.
* libretro.c stereo / NTSC filter unchecked malloc returns.
* fceu.c FCEUI_LoadGame unchecked GameInfo malloc.
* fds.c SubLoad and FDSLoad unchecked FCEU_malloc on diskdatao
backup buffers (NULL-deref in subsequent memcpy).
* fceu.c AllocGenieRW partial-failure leak: AReadG allocated but
BWriteG malloc fails -> 256 KB leak per retry.
* input/bworld.c Update(): unbounded strcpy from attacker-supplied
data into 20-byte bdata. Replaced with length-bounded copy.
* cart.c setprg2r/4r and setchr1r/2r/4r/8r mask-underflow guards.
SetupCartPRGMapping/SetupCartCHRMapping compute (size >> N) - 1
which underflows to 0xFFFFFFFF for chips smaller than the unit.
setprg8r/16r/32r and setchr8r already had defensive size checks;
the smaller units did not. malee.c (2 KB chip) and mapper 218
(2 KB NTARAM-as-CHR) accidentally avoid OOB; the primitive is
unsafe for any future board.
* video.c FCEU_InitVirtualVideo XBuf/XDBuf partial-failure leak.
* video.c, fceu.c FCEU_DispMessage / FCEU_printf / FCEU_PrintError:
vsprintf into fixed stack buffers replaced with vsnprintf.
* core: validate magic-string read length on FDS/UNIF/NSF load
(reject files too short to contain the magic so previous static
buffer / stack garbage doesn't spuriously match).
LEAKS (every cart load/unload cycle)
====================================
* mmc5.c MMC5 cart-side: WRAM (up to 64K) + MMC5fill (1K) + ExRAM
(1K) leaked per cycle. NSFMMC5_Close existed but was only used
for NSF code path.
* onebus.c Mapper 270/436: 8 KB CHRRAM leaked per cycle.
* 330.c, 375.c, 528.c: 8 KB WRAM each, no Close at all.
CORRECTNESS (UB / ABI / endianness)
===================================
* fceu-endian.c FlipByteOrder loop bound was 'count' instead of
'count/2', making it a no-op for every even count and silently
breaking savestate portability for every FCEUSTATE_RLSB-marked
field. The '#ifndef GEKKO' workarounds scattered through the
codebase (sound.c, vrc7.c, vrc6.c) were symptoms of this root
cause.
* state.c AddExState bounds check ran AFTER writing the entry, so
when SFEXINDEX hit the 63-entry cap the next call would write
the entry then immediately overwrite it with the terminator.
* state.c FCEUSS_Save_Mem: post-save callback was guarded by
'if (SPreSave)' instead of 'if (SPostSave)'.
* Sequence-point UB in counter expressions across 4 board files,
e.g. 'x = !x ? a : --x' and 'x = ++x % n'. GCC -Wsequence-point
flags all five sites (285.c, 413.c, asic_mmc3.c, jyasic.c).
* SFORMAT size mismatches between declared variable type and
AddExState size argument:
- asic_vrc3.c VRC3_count/VRC3_reload (uint16, saved as 1 byte)
- mmc3.c m555_count (uint32, saved as 2 bytes), m555_count_-
expired (uint8, saved as 2 bytes - OOB write into adjacent
BSS).
* unrom512.c UNROM-512 mapper 30 .srm save format: host-endian
uint32 flash write counters. Now stored as LE on disk regardless
of host (Battle Kid 2, Twin Dragons, Lizard, Sole, etc.).
* ~70 multi-byte single-variable SFORMAT/AddExState entries across
36 board files were saved as host-byte-order. After the
FlipByteOrder fix, these are now byte-swapped on BE so cross-
platform savestates round-trip correctly.
* coolgirl.c new ExStateLE() macro added; 22 multi-byte sites.
* pic16c5x.c 11 multi-byte AddExState calls (m_PC, m_PREVPC,
m_CONFIG, m_WDT, m_prescaler, m_opcode, m_STACK[0/1], m_icount,
m_delay_timer, m_rtcc, m_inst_cycles, m_clock2cycle).
* onebus.c PowerJoy Supermax submapper detection used non-portable
*(uint32*)&info->MD5 cast that read different bytes on LE vs BE.
* fds.c clean up redundant FCEUSTATE_RLSB encoding in AddExState
calls that also passed type=1 (idempotent OR; readability fix).
DOCUMENTATION
=============
* input.c UpdateGP's *(uint32*)data cast looks like a typical
endian bug but is actually correct (the libretro frontend builds
JSReturn with matching host-uint32 shifts). Comment added to
prevent future "fixes" from breaking it.
Limitations not addressed
=========================
* Element-stride-aware byte swapping. The savestate byte-swap
mechanism (FlipByteOrder over the entire SFORMAT entry buffer)
is structurally wrong for arrays of multi-byte values: it
reverses the whole buffer end-to-end instead of byte-swapping
each element. Several places that need cross-platform-portable
arrays (VRC7 sound state, jyasic chr[8]) work around this by
either splitting arrays into per-element SFORMAT entries (n106
PlayIndex, bandai reg) or by skipping save entirely on BE via
#ifndef GEKKO. A proper fix would extend the size encoding with
an element-stride field. Left for a future change because it
would change the savestate format.
* Strict-aliasing UB in ppu.c (around 9 sites doing
*(uint32*)uint8_buf for fast 4-byte writes via FCEU_dwmemset).
Works in practice with all common compilers because the patterns
are byte-symmetric, but is formally UB.
* FCEU_gmalloc calls exit(1) on OOM. A libretro core should never
exit() because that takes down the whole frontend. Used by
100+ call sites; refactoring to return-NULL is out of scope here.
Testing
=======
* Build: clean on `make platform=unix` with -O2; no new warnings.
* FlipByteOrder fix verified by hand-trace and a standalone unit
test for counts 2, 4, 8.
* uppow2 fix verified by unit test across 13 boundary cases.
* SFORMAT size mismatches and missing-RLSB cases identified by
Python static-analysis scripts that cross-reference SFORMAT
entries against variable declarations.
* iNES 2.0 exponent fix verified by hand-tracing what byte 0xFF
produces post-fix: exp=30 (capped), mult=7, size=3 GiB nominal,
capped to 1 GiB, capped to 2 GiB by uppow2, FCEU_malloc returns
NULL on most systems, loader returns 0. No heap overflow for
any input byte.
* Savestate-loaded array index audit: built a Python scanner that
extracts each AddExState/SFORMAT entry and cross-references the
variable name against array-index uses in the same file. All
flagged sites covered.
* A libFuzzer harness and seed corpus generator (fuzz_main.c,
gen_seed_corpus.py) accompany this submission for ongoing
regression testing.
2026-05-04 02:15:40 +02:00
|
|
|
else if (GameInfo && GameInfo->type == GIT_FDS)
|
2019-01-04 17:13:09 +08:00
|
|
|
size = FDSROM_size();
|
2016-03-11 03:35:26 +01:00
|
|
|
else
|
|
|
|
|
size = 0;
|
|
|
|
|
break;
|
|
|
|
|
case RETRO_MEMORY_SYSTEM_RAM:
|
|
|
|
|
size = 0x800;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
size = 0;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2017-02-16 20:30:24 -06:00
|
|
|
|
2016-03-11 03:35:26 +01:00
|
|
|
return size;
|
2014-03-30 22:15:17 +02:00
|
|
|
}
|