The internal noise shift register in this file uses a bit-reversed
layout vs the canonical nesdev wiki model: output is read from bit 14
(not bit 0), the feedback taps are at 13/14 in long mode and 8/14 in
short mode (mirrors of 0/1 and 0/6), and the register shifts left
rather than right. The algorithm is algebraically equivalent to the
wiki form, but the power-on state has to be mirrored as well.
Real hardware initialises the LFSR to $0001 with bit 0 set. Under
this file's mirroring, the equivalent state has bit 14 set - i.e.
nreg = 0x4000, not nreg = 1. Starting at 1 placed the LFSR 14 steps
off-phase from real hardware in long mode and at an entirely
different position in the 93-cycle short-mode loop.
Reported by NSFPlay author Brad Smith (rainwarrior) comparing
moon8.nes audio against Mesen. Reproduced via headless libretro
harness with per-channel solo bisection against negativeExponent's
fceumm_next branch (which uses the un-mirrored nesdev layout):
channel RMS diff vs _next (noise solo, 22-60s window)
------- ----------------------------------------------
square1 1 ( 0.4%) - bit-identical
square2 1 ( 0.4%) - bit-identical
triangle 178 (14.0%) - minor difference
noise 1914 (125.8%) - completely off
dmc 2 ( 0.4%) - bit-identical
After this patch the noise-solo RMS drops to 6 (0.4%), bringing the
noise channel into bit-exact agreement with _next/Mesen/NSFPlay.
Full-mix moon8 audio RMS vs _next falls from 1135 to 126 across the
60-second test.
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.
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.
Pass 10 audit of the savestate path against malformed input under
AddressSanitizer + UndefinedBehaviorSanitizer. retro_unserialize is
attacker-controlled in any frontend that loads .state files from disk,
so any out-of-bounds read/write or DoS in the parser is a real attack
surface.
Real OOB-read crash caught by ASAN savestate fuzzing
====================================================
Random-byte mutation of a valid savestate hits a SEGV in
RDoTriangleNoisePCMLQ at sound.c. The LQ Tri/Noise/PCM mixer indexes
wlookup2[scaled_tcout + noiseout + scaled_dmc]
with all three operands derived from state fields that are loaded
straight from the savestate (lq_tcout as raw uint32_t,
EnvUnits[2].decvolume as uint8_t, RawDALatch as uint8_t). The static
table is only 203 entries; under normal play the channel-write paths
keep the operands inside [0, 202] (lq_tcout ≤ 45, noiseout ≤ 30,
RawDALatch ≤ 127), but a malformed state can drive the index to any
value, producing a heap-buffer-overflow read in the audio inner loop.
Same class of bug at four more wlookup2 sites in the same function,
plus the HQ wlookup2[(b >> 16) & 255] in WriteHi (mask is 8 bits, but
the table is only 203 entries so the upper 53 indices are OOB), and
wlookup1[b >> 24] (mask 8 bits, table only 32 entries).
A separate but related issue at the LQ Squares mixer: wlookup1 is
indexed by ttable[0][RectDutyCount[0]] + ttable[1][RectDutyCount[1]],
where RectDutyCount[] is loaded from savestate as int32_t and may be
> 7. ttable is int[2][8], so the indirection itself reads adjacent
memory before feeding it into wlookup1.
Crashes also caught by targeted-input testing on master: an all-zero
state, an all-0xFF state, or any state with a corrupted magic header
all crash because the chunk loop enters with totalsize parsed as
INT_MIN and never exits cleanly.
Changes
=======
src/state.c:
* ReadStateChunk: 4-byte 'toa' read now requires exactly 4 bytes
returned (was <= 0, allowing 1- to 3-byte short reads to leave
toa[] partially uninitialised before strncmp consumed it). Same
for the per-chunk payload memstream_read into tmp->v. A failed
memstream_seek on an unknown chunk is now a parse error - the
loop previously kept spinning at the same offset on out-of-range
seeks because the return was ignored.
* ReadStateChunks: chunk size + 5 housekeeping is now overflow-
checked before being subtracted from totalsize. The previous
'totalsize -= size + 5' computed size + 5 as uint32_t, so a
chunk size of e.g. 0xFFFFFFFB wrapped to 0 and the outer loop
failed to make progress.
* FCEUSS_Load_Mem: zero-initialise the 16-byte stack header so
short reads see deterministic zeros. memstream_read return now
checked. NULL-check memstream_open. Read totalsize as uint32_t
and reject any value > INT32_MAX rather than silently casting
to a negative signed int that makes the chunk loop never enter.
src/sound.c:
* New helper wl2(idx) clamps the index to wlookup2[]'s size before
reading. Used at all five LQ Tri/Noise/PCM call sites and the HQ
WriteHi pass.
* HQ wlookup1[b >> 24] in WriteHi now masks index by & 31 to match
the 32-entry table.
* LQ Squares mixer: RectDutyCount[x] masked & 7 at every
ttable[x][RectDutyCount[x]] call site. The inner-loop increments
already mask the value but the first call after savestate load
did not.
Verification
============
Random-byte fuzz: 50,000 iterations of a baseline savestate with 1-32
random byte mutations each, run through retro_unserialize +
retro_run. Master crashes within ~50 iterations (sound.c SEGV in
RDoTriangleNoisePCMLQ); pass 10 completes 50,000 / 50,000 iterations
without any ASAN-reported issue.
Targeted edge cases (wrong magic, totalsize overflow, chunk-size
+ 5 wrap, all-zero state, all-0xFF state): every input that
crashed master now rejects cleanly on pass 10.
Bit-exact regression: master vs pass 10 on 9 valid test ROMs (silent,
idle, active, 5-channel max-volume stress, plus the per-APU-channel
ROMs from the earlier APU pass) at 1500 audio frames each - byte-
identical audio output. Video output identical at 60 frames each.
Cross-version compatibility: states saved on master load on pass 10
and vice versa; no format change.
Pre-existing issue noted but not fixed in this pass: retro_unserialize
unconditionally returns true if the size matches, regardless of
whether FCEUSS_Load_Mem actually accepted the state. The frontend has
no way to know a malformed savestate was rejected. Worth a follow-up
to plumb a return code through FCEUSS_Load_Mem to retro_unserialize.
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.
Pass 6 (commit 9f6b84c) optimized SetSoundVariables() in LQ mode by
pointing DoSQ2, DoNoise, and DoPCM at Dummyfunc instead of redundantly
calling RDoSQLQ / RDoTriangleNoisePCMLQ. The reasoning at the time:
those workers guard with `if (end <= start) return;` so re-entry
within one FlushEmulateSound is a no-op and the second/third/fourth
calls are wasted dispatch overhead.
That reasoning was wrong. The Do* hooks are also called from two
mid-frame paths:
* Write_PSG (sound.c:189) on every APU register write, BEFORE the
register update, to flush pending samples up to the current
SOUNDTS using the pre-write register state.
* FCEU_SoundCPUHook (sound.c:493) on every DMC bit advance, to
flush samples up to the current SOUNDTS so the new PCM byte gets
rendered against the correct prior state.
Between any two such calls, sound_timestamp has grown with each CPU
instruction, so each Do* call IS legitimately doing work - the early-
return guard only fires within a single FlushEmulateSound batch, NOT
across calls separated by CPU cycles.
Stubbing DoSQ2 / DoNoise / DoPCM to Dummyfunc therefore skipped all
the mid-frame flushes triggered by writes to $4004-$4007 (SQ2
registers), $400C-$400F (noise registers), and $4010-$4013 (DMC
registers). For any game that writes to multiple APU registers in
sequence (essentially all of them), the audio output diverged from
the pre-pass-6 baseline.
Bisect: built three test ROMs covering "APU disabled", "channels
enabled with steady tone, no register writes after init", and "all
channels active with continuous register pumping". The
register-writing scenarios diverged starting at the first
post-initialization register write; the steady-tone scenario
diverged starting at the next DMC bit advance after init. Reverting
just the LQ dispatch dedup restored bit-identical audio for all
three scenarios.
Fix: restore DoSQ2 -> RDoSQLQ, DoNoise -> RDoTriangleNoisePCMLQ,
DoPCM -> RDoTriangleNoisePCMLQ. Add a comment explaining why this
optimization is incorrect, so the same mistake is not repeated.
The other pass 6 changes remain in place and are verified safe:
* SexyFilter_Reset() called from FCEUSND_Power - filter state
determinism fix.
* sexyfilter2_acc lifted to file scope, added to FCEUSND_STATEINFO
as "FAC3" - savestate completeness fix.
* WaveHi memset bound tightened from sizeof(WaveHi) - left*4 to
(SOUNDTS - left) * sizeof(uint32_t) - HQ-mode optimization.
* RDoSQLQ silent-channels branch loop body removed - totalout is
provably 0 in that branch, so the loop was a true no-op for
~30000 NES cycles per silent frame.
* stereo_filter_apply_delay element-by-element loop replaced with
memcpy.
* filter.c #ifdef moo / #if 0 dead blocks and unused <math.h>
include removed.
Verified bit-identical to pre-pass-6 baseline for all three test
ROMs. Performance impact of the cleanup pass (with the LQ dedup
correctly reverted, attributed to the actually-correct
optimizations) is ~9% per-frame reduction across silent / idle /
active scenarios. Pass 6's previously-reported ~25% speedup on the
active scenario was inflated by the buggy optimization skipping
legitimate work.
Build clean under -std=gnu11 -Wno-write-strings -Wsign-compare
-Wundef -Wmissing-prototypes; zero errors, zero warnings.
audit_determinism.py clean.
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.
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.
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).
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.
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.
In the past while fixing state problems i Wii, this section was the last one i found to be a problem as well. Ignoring this state variable fixes the last Wii state loading problem. PS3 has recently been reported as having state problems and could be similar issue. So, lets just ignore this state variable for all big-endian platforms.
* Wii: Fix uninitialized save state size causing load state problems
- Issue origially from Wii and possibly affects similar devices where
retroarch and the core are one program. The problem was that the
serialize size was not reset when you change game, causing the last
serialized size to be carried over to the next game loaded causing save
state issues due to change in size. Initializing this variable during
retro_init() seems enough for most of the games.
- In this same observation, also initialized some variables in a similar
way.
- also exluded a variable used for sound state that was added for smooth
sound after load state which causes similar loading issues as well.
- default sample rate has been lowered to 32K as well as to minimize
some stuttering for these devices, while maintaining 48K sample rate for
others.
- Some comments are added to modified section as necessary.
* Add workaround for save state issue in Wii with expansion audio
- Some mappers are not loading states as well. Seems to affect those
that are using expansion audio. some of these mappers(bandai, mmc5,
namco106), the state variables has to be expanded so that it will
load states fine with big endian while others (vrc6, vrc7 sunsoft), some
variables that were added for smoother audio during load state has been
removed.
- This does not guarantee though that other mappers might not have
similar incompatibilities after loading a state but so far, some of the
most common roms in each mapper has been tested to load fine.
* FDS: Expand state variables for big endian compatibility
* FDS: Change OSD label from Disk 0 to Disk 1... when switching disks
- Minor osd label change that now shows Disk 1 of (# of disks) instead
of just Disk 0 Side nth.
* Replace C++ comments into C comments
* Move INLINE out of Makefile.common, move to fceu-types.h
An INLINE define is still existing in Makefile.common for msvc (dont
have a way to to test that). Moved the rest into fceu-types.h using the
one already existing from emu2413.h.
* Increase maximum volume for apu channels
* Add option to enable/disable audio channels
Audio options are backports from FCEUX. Options are set as on/off toggle for each apu channel.
Compile with DEBUG=1 to see options.
* Add missing audio state vars, fix for hq audio when using runahead
* Additional checks for save ram (battery)
* Cleanup
* Update libretro header