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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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]>
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]>