30 Commits
Author SHA1 Message Date
Rix fda54ec684 callGuest: keep the stack frame in RAM or the scratchpad
With more than four arguments the frame is written through the bus, so
an sp pointing at I/O would have stored the arguments into hardware
registers. Refuse it before anything is written.
2026-10-11 01:18:33 -07:00
Rix 457629d276 callGuest: retire a load in the return jump's delay slot
RunUntil stopped on the return address with that load still pending,
so the caller read the register before it landed, and handing back
the delayed-load state then dropped it.
2026-10-11 01:18:32 -07:00
Rix 6e52babe9a callGuest: check inputs first and take all of them back out
Stage and fetch ranges are now checked before anything is written, so
a bad fetch no longer errors out after the guest ran with nothing to
roll it back. Both isolation modes now undo the stack arguments, and
full isolation also undoes staging, by snapshotting before the inputs
go in, into its own slot rather than the one captureState() fills.
restoreState() refuses to run before a capture. RunUntil hands its
state back from a scope guard, and the history depth slider no longer
offers 0, which means unbounded.
2026-10-11 01:18:32 -07:00
Rix ac7e07a196 Defer Lua restoreState() off the emulation stack
Called from a listener inside Execute(), restoreState() swapped the
state under Counters::update() and branchTest(), the same way loads and
rewinds did before they were deferred. Queue it for the main loop like
them, and refuse captureState() while a restore is waiting, since it
would overwrite the snapshot about to come back.

The scratch snapshot was also a function-local static bound to the
first emulator's memory, so a second emulator in the same process
captured into freed RAM. Drop it with each new Lua state.
2026-10-11 01:18:32 -07:00
Rix f08a932514 Test PCSX.callGuest 2026-10-11 01:18:32 -07:00
Rix 9cb0647199 rewind: take the snapshot off the ring before restoring it
restore() signals SaveStateLoaded synchronously. When the rewind runs
from the main loop, a listener calling rewindState() steps back right
away, while rewindStateNow() still held the snapshot at the back of the
ring: the nested call landed on it again, and with a single snapshot
the outer call then popped an empty ring.

The test helpers resumed on the emulation stack after a vsync pause,
and runIntoCallStack() paused before the guest ran when called from an
event loop callback; both are fixed so the new test can sit mid-suite.
2026-10-10 17:10:27 -07:00
Rix cb89f0843a Merge remote-tracking branch 'upstream/main' into rewind-defer 2026-10-10 15:31:34 -07:00
Rix 780fd231a7 Defer rewinds from the emulation stack to the main loop
A rewind restores state like a load does, so it waits for Execute() to unwind the same way.
2026-10-10 14:15:12 -07:00
Rix d47af40815 main: shut down normally when a startup Lua script fails
A -dofile or -exec error took the emergency exit, which leaves the uv
thread unjoined and aborts the process with "terminate called without an
active exception" after the traceback is printed.
2026-10-09 03:06:51 -07:00
Rix c4b9424680 lua: name the event createEventListener doesn't know
The error read "createListener: unknown event name" with no name, so a
script using the C++ spelling GPU::VSync instead of GPU::Vsync got no hint.
2026-10-09 03:06:43 -07:00
Rix 93aeb5fee0 Merge pull request #2255 from rixnobis/mds-parse
cdrom: open the .mds descriptor in parsemds, read sectors from the .mdf
2026-10-09 02:31:54 -07:00
Rix 684098cb68 cdrom: open the .mds descriptor in parsemds, read sectors from the .mdf
parsemds changed the extension on the wrong variable, so it opened the path it was given as the descriptor. A .mdf never parsed and was read at a 2352 stride; a .mds parsed but every sector was then read out of the descriptor itself.
2026-10-08 02:05:45 -07:00
Rix ce702decf3 isobuilder: set the form 2 bit in writeSectorAt's M2_FORM2 subheader
Without it compute_edcecc took the Form 1 branch and wrote EDC and P/Q parity over the last 276 bytes of the payload. close() with a single thread goes through the same arm. Round-trip every writable mode through writeSectorAt and through close() with one and four threads.
2026-10-08 01:15:35 -07:00
Rix 28b05b0001 Clear the call stacks before capturing them into a reused message
CallStacks::serialize appended to the message's call stack list without
clearing it. save() always started from a fresh message, so it never
mattered, but capture() reuses one: every capture appended the live call
stacks to the ones already there, restore() rebuilt the live tree from
that, and the next capture appended it again, doubling the list on every
round trip. Repeated capture/restore on one slot reached 218MB of state
and 3.8s per round trip by the 22nd; with the clear it stays flat.

The new test records a call stack and does 32 capture/restore round
trips on recycled ring slots. It fails at round trip 2 without the fix.
2026-10-07 16:51:30 -07:00
Rix e138760e43 Back the rewind ring with capture() and restore()
The ring used to hold save() blobs and step back through load(), paying the
protobuf encode on every capture and a cpu reset on every rewind. Snapshots
are now captured messages, recycled once they fall off the ring.
2026-10-07 16:51:30 -07:00
Rix 0d7bc3fb54 tests: record the OpenBIOS shell, replay the dump, compare VRAM. 2026-09-26 13:54:23 -07:00
Rix 9bdf61bfa7 SPU: play only the first XA stream when no filter is given, and document sound and pointer lifetimes 2026-09-24 02:18:50 -07:00
Rix 98ae7c8430 Lua: fix the ADPCM encoder wrappers and File:readAtToSlice
processBlock passed scalar out-parameters and read them past the end,
processSPUBlock tested a misspelled variable, and processXABlock sized
its input padding in samples instead of bytes. readAtToSlice had its
size and position swapped.
2026-09-23 13:32:05 -07:00
Rix 7e839d02de ADPCM encoder: clamp input like encvag, and fix the XA packing
encvag.dll clamps its input to [-30720, 30719] before searching for the
shift. Without it, a block reaching 30720 overflows the shift search and
ends up with shift 12. With the clamp, SPU output matches encvag.dll
byte for byte, including full scale input.

The XA path no longer rounds already rounded samples a second time,
which wrapped full scale samples around, and 8-bit headers carry the
same shift as the data.
2026-09-23 12:47:40 -07:00
Rix 7beac44abd SPU: add PCSX.SPU.playAudio to play PCM, SPU-ADPCM or XA-ADPCM buffers from Lua
Sounds go through a second logical SDL device on the same physical output,
which stays running while emulation is paused. Each sound gets its own SDL
audio stream, so SDL does the resampling and mixing.
2026-09-23 11:31:50 -07:00
Rix b75233046e ADPCM: add a decoder for SPU blocks and XA sound groups, with a Lua API
Follows the psx-spx description of both formats. The 8-bit XA encoder
round-trips are skipped for now: the encoder quantizes on the 4-bit grid
and writes shift - 4 in the header, so its output decodes 16 times too loud.
2026-09-23 11:31:49 -07:00
Nicolas 'Pixel' Noble 5959387095 Binding the ISO builder's raw sector writers to Lua.
Signed-off-by: Nicolas 'Pixel' Noble <[email protected]>
2026-07-31 14:10:04 -07:00
Nicolas 'Pixel' Noble 3fcde99fb2 isobuilder: Fix M2_RAW path treating data as M2_FORM1 in the parallel writer.
The multi-threaded writeFiles pipeline had no M2_RAW case in its mode switch
and fell through to default, calling prepareM2F1FrameNoECC. That stamps its
own M2F1 subheader at frame[16..23] and treats the source as 2048 bytes of
form-1 user data, but M2_RAW callers supply 2336 bytes that already contain
the subheader and EDC/ECC tail. Result was an 8-byte shift of M2_RAW user
data with a doubled subheader.

Added prepareM2RawFrame that stamps sync + MSF + mode and copies the 2336
bytes verbatim, wired into the switch with needsECC=false since the caller-
supplied data already includes EDC/ECC. The single-threaded path in
writeSectorAt was already correct - this only affected the parallel EDC/ECC
path. Regression guard in tests/lua/isobuilder.lua round-trips a
deterministic 2-sector pattern.

Signed-off-by: Nicolas 'Pixel' Noble <[email protected]>
2026-05-25 20:01:16 -07:00
Nicolas 'Pixel' Noble 954dd9460a isobuilder: Anchor LBA and declared size overrides on DirTree.
A single ISO9660 entry can now shadow a much larger raw-LBA extent: anchor
the entry to a specific start LBA (with empty M2F1 zero-padding inserted
before it) via setAnchorLBA, and override the dir-entry Length field
independently of the actual content size via setDeclaredSize. close() throws
if the layout cursor is already past a requested anchor.

Combined with the existing setSkip, this lets other entries be placed at
their own LBAs inside the shadow extent and not show up in the directory
tree at all. Pattern matches the index-driven, raw-LBA-addressed file
system used by titles like Valkyrie Profile.

Signed-off-by: Nicolas 'Pixel' Noble <[email protected]>
2026-05-25 19:46:06 -07:00
Nicolas "Pixel" Noble c728921e14 Folding isobuilders. 2026-05-24 12:19:24 -07:00
Nicolas 'Pixel' Noble bfe6635225 Isobuilder. 2026-05-23 21:54:10 -07:00
Nicolas Pixel Noble bcdf44a3f4 Adding XA testing + files generation code. 2024-02-16 19:30:27 -08:00
Nicolas Pixel Noble 4ea756a441 Adding quick ADPCM test. 2024-02-14 23:15:12 -08:00
Nicolas Pixel Noble f840865222 Streamlining tests better. 2024-01-23 19:33:21 -08:00
Nicolas Pixel Noble 255c5f7bfc Adding Lua unit testing. 2024-01-21 23:08:16 -08:00