Stream existed to let the savestate and patch code work over either a
file or a buffer. Nothing needs the file half any more: savestates go
through S9xFreezeGameMem / S9xUnfreezeGameMem, and patches now arrive as
buffers whether they came from a loose file, a zip entry or an .msu1
pack member. What was left was three subclasses, a virtual dispatch per
byte in places, and a macro block in snes9x.h that had to be hidden
behind __cplusplus so the C sources could include the header at all.
bytestream.h replaces it: a struct with buf/size/pos and inline
read/write/seek/tell. A NULL buffer counts bytes without storing them,
which is the S9xFreezeSize sizing pass that nulStream used to do. Reads
past the end come up short and seeks are clamped, so a malformed
savestate or patch cannot walk off the buffer.
Removed with it:
- stream.cpp and stream.h, and the STREAM macro family in snes9x.h,
which means snes9x.h no longer needs its __cplusplus guard.
- S9xFreezeGame and S9xUnfreezeGame, the file-backed savestate entry
points, along with S9xOpenSnapshotFile and S9xCloseSnapshotFile.
- S9xResetSaveTimer and the oops-snapshot machinery it drove, whose
only purpose was writing a .oops file through those entry points,
and S9xMessageFromResult, which reported their failures.
- the WDM debug opcode that wrote a .wdm snapshot. It was inside
#ifdef DEBUGGER, which this tree does not build, and it was the only
other S9xFreezeGame caller.
- conffile.cpp and conffile.h. Nothing constructs a ConfigFile; the
single reference outside the file was an empty S9xParsePortConfig
stub in the port, implementing a declaration nobody calls.
- ReadStreamUntilEOF, which existed because Stream had no size(). The
UPS and BPS readers wanted the whole patch in memory and now just
address the cursor's buffer directly, so a patch is no longer copied
a second time to be read.
Verified: Killer Instinct is unchanged on four input scripts - same
audio hash, same sample count, same video. Savestates round-trip
(preemptive-frames replay 0/240 mismatches, save/restore audio
deterministic), IPS and BPS patches apply from a loose file and from
both stored and deflated zips, ROMs load from loose files and zips, and
the MSU-1, input and peripheral suites all pass.
The 2010 core's MSU1 had diverged well ahead of this one. It rounds the
volume scale to nearest instead of truncating toward zero (matching
ares' double-precision reference to the LSB), streams the track through
a read-ahead buffer instead of a 4-byte stdio read per 44.1 kHz frame,
caches the track size, skips reopening a track that is already mounted,
serialises its interpolator state so a savestate resumes mid-waveform
rather than reseating, interpolates with a 4-point Catmull-Rom kernel
instead of 2-point linear, reads companion files out of an .msu1 pack,
accepts msu1.rom as a data-ROM name, and treats a lone track-0 pcm as
enough to enable MSU1 (the data ROM is optional).
Rather than port those one at a time, take the file. msu1.cpp is gone;
msu1.c, msu1_pack.c and their headers come across as-is.
The integration differs in one structural way. This core clocked MSU1
from the DSP - S9xMSU1Generate(2) inside SPC_DSP_OUT_HOOK - into a
second Resampler, then mixed after resampling, and S9xGetSampleCount
had to return the minimum of the two resamplers so partial MSU1
generation could not trip the assert. The C implementation is pulled by
the mixer instead: S9xMSU1Mix resamples to the caller's rate and adds
into the buffer. So the DSP hook goes, the msu:: resampler namespace
goes, sample count comes from the SPC side alone, and MSU-1 Enhanced
Audio still works because it sets SoundPlaybackRate to 44100 and the
kernel then runs at unity.
Supporting changes:
- memmap hands MSU1 the fully-qualified ROM path. Memory.ROMFilename
alone is not enough: the libretro port loads from memory and passes
only the basename, so companion files would be sought in the working
directory.
- the Mercurial Magic patch.bps path is reimplemented on the pack
reader, which is offset-addressed rather than stream-based: read the
member into memory and hand ReadBPSPatch a memStream.
- snapshot version 13. struct SMSU1 changed wholesale - flags out of
the packed status byte into individual fields, offsets renamed, plus
the serialised interpolator. S9xMSU1PreSaveState is now called at the
freeze site, which this core never had.
- snes9x.h guards its stream.h include and the STREAM macros behind
__cplusplus, since Stream is a C++ class and the C sources need
snes9x.h for Settings. The existing C files only included port.h, so
nothing else changes.
- encoding_deflate.c (libretro-common's cleanroom RFC 1951 decoder) is
vendored for the pack reader's deflated entries. No zlib, no minizip.
- the MSU1 instance stays in globals.cpp, as before.
Verified with an MSU1 probe ROM that seeks the data ROM through
$2000-$2003, reads 16 bytes back through $2001, mounts track 1, plays
it and samples $2000 every frame, with the same content laid out three
ways - loose files, a stored .msu1 pack, a deflated .msu1 pack. All
three give identical data bytes, status 0x12 (revision 2 +
AudioPlaying) and bit-identical audio. A savestate taken mid-track and
reloaded 30 frames later keeps playing. Total audio energy over the run
matches the 2010 core exactly (74689407), confirming the decode path is
now the same one. Killer Instinct is bit-identical to before the port
on four input scripts, and the input, peripheral and preemptive-frames
suites are unchanged.
fxemu.cpp and fxinst.cpp are replaced by snes9x2010's single fxemu.c
(plus its fxemu.h/fxinst.h), compiled as C. After the cross-audit
round-trip, the 2010 core is a strict superset of this codebase's GSU
behavior: it carries the execution-gating and PLOT-2bpp fixes ported
from here (their 8595b9f) plus its own ares-verified RPIX flag
handling, a deterministic integer speedPerLine derivation from the
master clock (replacing the float fps/V_Max form), and a per-opcode
GSU cycle-cost model.
Bridging: SFXFillRAM (= Memory.FillRAM), SuperFXSpeedPerLineHz
(scaled by the snes9x_overclock_superfx option, replacing the
SuperFXClockMultiplier scaling the old exec applied internally),
SuperFXPalFlag, and S9xSuperFXIRQHook/ClearHook implemented in
cpu.cpp for CPU.IRQExternal. The GSU MMIO handlers (S9xSetSuperFX /
S9xGetSuperFX) move into fxemu.c, taking the 2010-maintained SetByte
body. The ported core does not gate execution internally, so the
HC_HCOUNTER_MAX_EVENT call site now applies CHECK_EXEC_SUPERFX like
the 2010 call sites do. The fork's 8MB SuperFX high-bank offset
(8980c6fc) is applied to the ported bank setup.
Savestates: every SnapFX field binds by name into the 2010 FxRegs_s;
the pfPlot/pfRpix post-load fixup is replaced by the avRegAddr reseat
(dispatch tables rebind from vMode inside fx_readRegisterSpace on the
next exec), matching 2010's own snapshot handling.
Needs game validation on the usual set (Star Fox, Yoshi's Island,
Doom, Stunt Race FX): the cycle-cost model and 2x CLSR scaling change
effective GSU timing relative to the old 5/2 heuristic.
Sanitizer sweep: ASan+UBSan+LSan full lifecycle probe clean, no
leaks; TSan separately, 0 warnings.