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.
Pull the libretro VFS stack into libretro/libretro-common (from the
snes9x2010 tree's curated copy): vfs_implementation, file_stream,
file_stream_transforms, file_path/file_path_io, the compat family
(strl/posix_string/strcasestr/snprintf/fopen_utf8), encoding_utf,
stdstring and rtime, plus the include tree. libretro.h is upgraded to
the current version -- required because file_path_io uses the
retro_vfs_stat_64_t extension the old 2020-era header predates. A
file_stream_transforms.h header is reconstructed from the .c's
signatures (the source tree ships the implementation without it).
Makefile.common builds these outside STATIC_LINKING, same split as
snes9x2010: statically linked frontends export all of these symbols
themselves. retro_set_environment now requests
RETRO_ENVIRONMENT_GET_VFS_INTERFACE and initializes filestream and
path with it; without a frontend VFS, filestream falls back to its
local implementation.
Conversion:
- The FSTREAM macro layer in snes9x.h moves wholesale from the
ZLIB/stdio dual implementation to RFILE (rfopen family), which
converts every STREAM/fStream consumer at once: stream.cpp,
snapshot.cpp, msu1's fStream path, conffile parsing, memmap's
soft-patch loader and snes9x.cpp. Compressed (.gz) stream support
is gone by design -- the frontend handles compressed content.
REOPEN_FSTREAM / reopenStreamFromFd (fdopen-based, no VFS
equivalent, zero callers) are deleted.
- Direct stdio converted to RFILE: memmap (BIOS + SRTC RTC data),
crosshairs (text format), cheats2 (load/save), apu (SPC dump),
conffile SaveTo, bsx (Satellaview stream files; SBSX struct fields
retyped), libretro.cpp's BIOS loader, and the DEBUGGER trace
handles in debug.h / snes9x.cpp / cpuops.cpp.
- bml switches from std::ifstream to filestream_read_file plus an
std::istringstream; the parser itself now takes std::istream.
- minizip's ioapi.c backend is rewritten on RFILE, so unzOpen (zipped
ROM loading, MSU-1 .msu1 packs) goes through the VFS; the stdio
fopen64/fseeko64 platform selection in ioapi.h is removed. The
unused minizip tools (zip.c, miniunz.c, minizip.c, mztools,
iowin32) are deleted. zlib remains linked for DEFLATE
decompression only.
- crosshairs' HAVE_LIBPNG block is removed: never defined in this
build, and png_init_io requires stdio FILE.
stderr/stdout console logging is intentionally untouched -- it is not
file I/O. After conversion, a tree-wide audit finds no fopen family,
gzopen, fstream, or fdopen outside libretro-common.
Validation:
- Functional VFS smoke: loading a ROM by path (data == NULL) through
memmap -> fStream -> filestream produces the byte-identical m7 test
ROM hash, and loading the same ROM from a .zip exercises
loadzip -> unzip -> the RFILE ioapi backend with the identical
hash again.
- Full gauntlet byte-exact: Contra III savestate vs pre-port oracle,
Mega Man X2, Mode 5 bright/dim, m7 option matrix, probe
(serialize size 823407).
- ASan+UBSan+LSan over the probe, the path-load and the zip-load
scenarios: zero findings in the new I/O paths; the only report is
a pre-existing 288-byte S9xGetCommandT leak (controls.cpp input
bindings, unrelated).
Untested but converted by the same mechanical mapping: SRTC RTC
file, BS-X Satellaview streams, cheat file save, SPC dump, DEBUGGER
traces.