Backport survey against snes9xgit master through 2ab06b3, covering the
shanytc GSU/IRQ series and its adjacent correctness fixes. Two of the
four core items were already here: the cycle-accurate GSU timing model
itself (8783b54 + OV2's 1994532 follow-up) landed earlier as the
snes9x_superfx_timing core option - the libretro equivalent of the
settings-file/command-line switch OV2 added - and the $4211 pending-IRQ
latch (c0dffce) is already in ppu.cpp. The two that were missing land
here.
1. Winter Gold pose-cel hold (the #533 half of 8783b54/1994532).
At the race-start pose switch the game polls the GSU-published pose cel
at $70:EBC0 once per frame and commits the skier arrangement from it.
The scanline-batch model publishes one frame earlier than hardware, so a
stale arrangement is committed over the new pose tiles - the garbled
skier. Hold CPU visibility of the 0 -> nonzero publish transition for
fx_cel_delay lines (312, one frame, matching upstream's Mesen2-verified
figure), set only for FX SKIING NINTENDO 96 by ApplyROMFixes.
Two deliberate divergences from upstream's shim: the hold is ticked from
the per-line event in cpuexec rather than inside S9xSuperFXExec, because
this port only calls the exec with GO set and the publish happens
precisely when the GSU stops - a hold counted there would freeze the
moment it mattered; and a republish during the hold window is captured
and restored when the hold expires rather than lost. The hold state
lives in FxRegs_s beside vHwFillMask and like it is deliberately not
serialized: a savestate landing inside the window at worst re-times that
one publish by a frame, in one handshake of one game.
2. RPIX stops clearing DREG too early (c1aa979, snes9xgit#1055/#1056).
CLRFLAGS resets pvDreg/pvSreg to R0, and all three rpix variants ran it
before storing the result. With a TO rn prefix the pixel read therefore
landed in R0 instead of rn, and TESTR14 tested the stale pointer, so TO
R14 : RPIX failed to re-trigger the ROM buffer read. Move CLRFLAGS after
the stores and TESTR14, the order every other DREG-writing op already
uses (fx_swap is the pattern). fx_rpix_obj is an empty stub here and
needs nothing.
Not taken, with reasons: the win32 commits (59d6c2b, fa20c02, 9cd4c1b)
are frontend-only; the 11MB SuperFX mapper (3813bf5) and UPS bounds
check (96b3661) are not part of the shanytc series and can ride a later
sync.
Builds clean through the LTO link.
SRAMMask is derived from a header byte with no upper bound. The SRAM
allocation is 512 KB, so from SRAMSize 10 upward the mask is larger than
the buffer and every masked SRAM access - including the writes - runs
off the end of it. Nothing exotic is needed to reach this: a bad dump or
a hacked header carries whatever byte it likes, and the value is used
directly. Clamp SRAMSize to 9, the largest the allocation can represent,
and clamp the resulting mask to the buffer as a backstop.
The same byte feeds a shift, and two other header bytes feed shifts of
their own: ScoreLoROM's 1 << (buf[0xd7] - 7) and the checksum
comparison's 1 << (ROMSize - 7). Below 7 the count goes negative and
above 12 the result overflows, both undefined - a fuzzed image produced
shift exponents of 248 and 258. All three now test the meaningful 7..12
range instead of shifting first.
Also silences a sign-compare warning in the loose-patch read introduced
with the byte-cursor conversion.
Found by fuzzing retro_load_game with truncated and header-corrupted
images under ASan and UBSan; 600 iterations across four seeds are clean
afterwards.
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.
unzip/ was minizip plus a zlib dependency, pulled in for three things:
loading a ROM out of a .zip, reading a patch out of the same zip, and
the unzStream wrapper that fed those patches to the patch readers. The
MSU1 pack support added recently already had to read zip entries and
did it with rinflate, libretro-common's cleanroom RFC 1951 decoder, so
the tree carried two zip readers and a compression library it no longer
needed.
Generalise the pack reader into zipfile.c: a zip_archive holds the
central directory, entries are found by name, extension or suffix, and
a zip_file reads one entry by offset. Stored entries seek for free;
deflated entries restart the inflate on a backward seek, which is what
minizip did too. msu1_pack.c is gone, msu1.c uses the general reader,
and the patch paths read an entry into memory and hand ReadIPSPatch or
ReadBPSPatch a memStream, which retires unzStream entirely.
Deleted: unzip/ (6 files), -lz from every platform, SYSTEM_ZIP, and
the minizip includes in memmap.cpp, stream.cpp, stream.h and
conffile.h. The binary no longer links libz.
Zip support was previously gated behind UNZIP_SUPPORT and only enabled
for the unix and win platform sets, because the vendored library was
only built there. The new reader is a core source with no external
dependency, so the gate is gone and zip content works everywhere -
including Android and the console targets, which silently answered
"This binary was not created with Zip support" until now.
Verified against generated archives: a 4 MB ROM loads bit-identically
from a loose file, a stored zip and a deflated zip (identical video and
audio hashes over 900 frames of scripted input). Each of the three
entry-selection rules is exercised - largest entry, program.rom, and
the ".1" multi-part extension - plus an archive with a decoy entry. An
IPS patch inside a zip applies from both stored and deflated archives.
MSU1 loose, stored-pack and deflated-pack playback is unchanged, and
the input, peripheral and preemptive-frames suites still 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.
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.
None of it is reachable from the libretro port: movie recording and
playback are frontend features here (libretro handles input recording
itself), netplay.cpp/server.cpp were already gone so netplay.h only
provided a Settings field and a pad-swap branch behind an undefined
NETPLAY_SUPPORT, and no screenshot.cpp exists in this tree.
Removed: movie.cpp, movie.h, netplay.h, the S9xMovie* call sites in
snapshot.cpp, controls.cpp, cpuexec.cpp, memmap.cpp, gfx.cpp and
libretro.cpp, the BeginRecordingMovie/EndRecordingMovie/LoadMovie and
SeekToFrame commands, the NetPlay/Port/ServerName/Movie* Settings
fields with their config keys and -net/-port/-server CLI handling, and
the MOVIE_* language strings, S9X_*_MOVIE_* message enums and the
WRONG_MOVIE_SNAPSHOT/NOT_A_MOVIE_SNAPSHOT/SNAPSHOT_INCONSISTENT result
codes (all three were already unproducible before this change, since
movie.cpp was their only source and was not in the build).
Kept: the MovieGet*/MovieSet* helpers in controls.cpp. Despite the
name they are plain current-controller-state accessors, used by the
on-screen input display and not by movie code; their declarations and
the *_DATA_SIZE constants move from movie.h to controls.h. The
SnapshotScreenshotInfo/"SHO" block also stays -- it is savestate
payload, and dropping it would break state compatibility.
Savestates: "MOV" and "MID" were the trailing blocks in the stream,
so no longer reading them is safe. States written by a movie-recording
build still load: nothing is parsed after those blocks. States written
by this build simply omit them. UnfreezeStruct() became dead with the
"MOV" read and is removed.
Verified behaviourally inert: 600 frames of scripted Mega Man X2
gameplay from a user savestate (idle, Y-shoot, and full
shoot/jump/dash scripts) are byte-identical in video and WRAM before
and after this change, and the joypad-register probe shows held B and
turbo cadence unchanged.
Sanitizer sweep: ASan+UBSan+LSan full lifecycle probe clean, no
leaks; TSan separately, 0 warnings.
bsx.cpp becomes bsx.c, compiled as C, so the whole BS-X
implementation (bsx.c + bsflash.c) can be backported to snes9x2010's
C89 codebase as-is. Both files are clean under gcc -std=c89 -pedantic
-Wall -Wextra -Wdeclaration-after-statement -Wold-style-definition
-Wstrict-prototypes -Wmissing-prototypes, with and without
-DBSX_DEBUG.
Conversion notes:
- The Memory.* macro indirection at the top of the file now points at
C bridge pointers (BSXMemMap/BSXBlockIsRAM/BSXBlockIsROM/
BSXRAMBase/BSXSRAMBase/BSXPSRAMBase/BSXBIOSROMBase/BSXROMBase),
set in memmap.cpp next to the other coprocessor bridges. The MAP_*
sentinels are pinned to the memmap.h enum values with a comment,
as in spc7110.c.
- C++-side state that C cannot reach goes through extern "C"
accessors implemented in memmap.cpp: map_WriteProtectROM, the
LoROM/HiROM flags, Settings.BS/BSXItself/BSXBootup,
Memory.CalculatedSize, Multi.cartOffsetB,
SNESGameFixes.SRAMInitialValue, and the SAT/BIOS directory paths
(std::string flattened to static char buffers).
- The satellite stream engine drops std::ifstream for FILE*: fopen/
fseek/ftell/getc/feof/fclose, with ifstream::get()'s -1-at-EOF
behaviour preserved via a small helper returning 0xFF at EOF, which
is what the old code stored. std::string path assembly becomes
bounded sprintf (snprintf is not in MSVC C89).
- C89 hygiene throughout: all // comments converted, declarations
hoisted to block tops (verified by -Wdeclaration-after-statement,
which MSVC C89 rejects outright), case-label declarations braced,
bool -> uint8_t. The two debug-only loops under BSX_DEBUG had
C99 for-scoped counters and are hoisted too, so enabling the debug
define does not break the C89 build. The three stream/RTC helpers
with no external callers are static, and the unused parameter in
S9xGetBasePointerBSX is voided.
- port.h and pixform.h had C++-style comments (including on
preprocessor directives) that broke -std=c89 -pedantic for every C
translation unit including them; converted. With the include path
set, every ported coprocessor now passes the same pedantic C89
check: bsx, bsflash, c4emu, dsp, fxemu, obc1, sdd1, seta, spc7110,
srtc.
Behaviour is unchanged: same register model, same stream engine, same
flash layering as the previous commit. Savestates unaffected (SnapBSX
binds into the same struct; the stream FILE handles were never
serialized, matching the old ifstreams).
Sanitizer sweep: ASan+UBSan+LSan full lifecycle probe clean, no
leaks; TSan separately, 0 warnings.
srtc.cpp/srtcemu.cpp and spc7110.cpp/spc7110emu.cpp/spc7110dec.cpp are
replaced by snes9x2010's srtc.c and spc7110.c (with their emu/dec
headers), compiled as C.
The decisive win on both is the RTC model: the byuu implementations
here call host time(0) on access, so the emulated clock is
nondeterministic -- movies desync, runahead and netplay diverge, and
two runs of the same inputs disagree. The 2010 versions seed once from
the host at init and then tick one second per emulated second on the
frame clock (S9xSRTCTick / S9xSPC7110RTCTick, wired into
HC_HCOUNTER_MAX_EVENT next to the SuperFX exec), which is the ares
model. The SPC7110 decompressor is the same byuu lineage on both sides
(verified by algorithm markers: context/evolution/probability
structure identical), so decompression accuracy is unchanged, and the
2010 file adds compile-time reverse-Morton lookup tables.
Bridges: SRTCPalFlag/SRTCEnabled, SPC7110Map (= Memory.Map),
SPC7110ROM, SPC7110ROMSize, SPC7110RTCEnabled, OpenBus, and the two
Map sentinels pinned to the memmap.h enum values with a comment. The
S-CPU DMA decompression special case in dma.cpp moves from the C++
s7emu.decomp.read() object to 2010's spc7110_decomp_read()/r4809/
r480a. Savestates stay compatible: SnapSRTCSnap and SnapSPC7110Snap
bind by name into the same-lineage snapshot structs, via the ported
Pre/PostSaveState pair.
Validation titles: Daikaijuu Monogatari II (S-RTC), Far East of Eden
Zero and Momotarou Dentetsu Happy (SPC7110+RTC).
Sanitizer sweep: ASan+UBSan+LSan full lifecycle probe clean, no
leaks; TSan separately, 0 warnings.
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.
Audit results, ares/MiSTer as oracles:
SETA ST-010: snes9x2010 replaced the float sqrt in OP04 with an
integer square root verified bit-identical to (int)sqrt((double)n)
across the full 2^31 input range, and its constant tables were
checked bit-identical against the MiSTer ST010 data ROM. Results are
unchanged and the path is integer-only, removing float rounding
variance across platforms. seta.cpp/seta010/011/018.cpp are replaced
by the single seta.c.
S-DD1: the MMC bank registers ($4804-$4807) select 1MB pages with
four bank bits (ares and the MiSTer FPGA core both decode bits 0-3);
this codebase masked writes with 7, capping the compressed-data space
at 8MB, and mapped raw page offsets without folding into the loaded
image size. snes9x2010 masks four bits and mirrors each mapped block
into CalculatedSize with map_mirror's power-of-two decomposition,
matching cartridge decoding for non-power-of-two images. Verified
there against retail behavior: Star Ocean and SFA2 write only
in-range pages, framebuffer hashes byte-identical over 5000
boot frames. sdd1.cpp/sdd1emu.cpp are replaced by sdd1.c, which
carries the decompressor.
The C files reach the memory system through a new s9xbridge.h
(SRAM/ROM/FillRAM/Map pointers and SRAMMask/CalculatedSize, assigned
in Init and at both SRAMMask computation sites). Savestates are
unaffected: SnapST010 binds struct SST010 by field name and the
layouts are identical (bool8 -> uint8_t of the same size); S-DD1
state lives in FillRAM and is remapped by the existing
S9xSDD1PostLoadState path.
OBC1 was audited as part of the same pass and differs from
snes9x2010 only in the license header text: parity, retained as is.
Sanitizer sweep: ASan+UBSan+LSan full lifecycle probe clean, no
leaks; TSan separately, 0 warnings.
The Capcom CX4 (Hitachi HG51B169) is an integer fixed-point DSP with
no FPU; the old HLE used libm doubles (sin/cos/tan/atan/sqrt) for the
wireframe transforms, Op0D normalise, Op1F arctangent and the
distance op, which is unfaithful to the chip and non-deterministic
across platforms and compilers (breaking savestate/movie portability).
snes9x2010's conversion carries the math in Q30/Q16 integer fixed
point with table-based trigonometry, exceeding the chip's own Q23
data-ROM precision; c4emu.c no longer references any libm symbol.
c4.cpp and c4emu.cpp are replaced by the single c4emu.c, compiled as
C and bridged to the memory system through C4RAMBase/C4ROMBase
pointers assigned next to the existing C4RAM setup in Init(). c4.h
gains extern "C" for the C++ callers in getset.h.
One regression found during the audit is fixed in transit: the op
0x54 square's sign-extend refactor had a typo (the second SAR reused
the pre-shift value), squaring the input pre-shifted left by 30 bits
-- a signed overflow whose written 48-bit result was always zero. The
24-bit sign extension is now done manually, which also avoids
shifting a set bit into the sign position. The same fix has been
submitted to snes9x2010 (46ed141 there).
Sanitizer sweep: ASan+UBSan+LSan full lifecycle probe clean, no
leaks; TSan separately, 0 warnings.
libretro.cpp: the RAM randomize loop compared a signed int against
sizeof(Memory.RAM), triggering -Wsign-compare. Use size_t.
memmap.cpp: close_ret in CheckForAnyPatch is only consumed by
assert(), which compiles out under -DNDEBUG and leaves the variable
unused. Cast it to void; the assert stays effective in debug builds.
Sanitizer sweep: ASan+UBSan+LSan full lifecycle probe clean, no
leaks; TSan separately, 0 warnings. Release build warning-free
apart from the lto-wrapper serial-compilation note.
(1 << (buf[0xd7] - 7)) is evaluated with a negative shift count when
the ROM size byte is less than 7. That is undefined behavior and the
result is architecture-dependent in practice: x86 masks the shift
count to 5 bits and applies the score penalty, ARM does not, so the
same image can score differently across platforms.
A size byte below 7 claims a ROM smaller than 16KB, which no valid
header does, so penalize it explicitly. This matches the previous
de-facto x86 scoring and makes it consistent everywhere. ScoreHiROM
already uses a plain comparison and is unaffected.
minizip-ng dynamically includes
zlib-ng.h or zlib.h depending on
what was included prior
snes9x.h includes zlib.h
which needs to happen first or the
minizip include tries to include zlib-ng.h by default
resulting in an error because you can't
include both in a single translation unit
(cherry picked from commit 7967d5222d4f1478845a2cf2d661dd670a3e7b82)
This code adds support for SuperFX ROMS which use the extended 6MB CPU ROM region specified by the official SNES documentation. It's not super well-tested though.