Skip to content

Fix sound Channel{1-4}.loadState slot index typo causing OOB on second loadState#377

Open
fsegouin wants to merge 1 commit into
torch2424:masterfrom
fsegouin:fix-channel-loadstate-slot-typo
Open

Fix sound Channel{1-4}.loadState slot index typo causing OOB on second loadState#377
fsegouin wants to merge 1 commit into
torch2424:masterfrom
fsegouin:fix-channel-loadstate-slot-typo

Conversation

@fsegouin
Copy link
Copy Markdown

Summary

A copy-paste typo in each sound channel's static loadState() method passes the runtime cycleCounter value to getSaveStateMemoryOffset() as the save-state slot index, instead of the constant saveStateSlot.

For example, in core/sound/channel1.ts:

// Before (buggy)
Channel1.cycleCounter = load<i32>(getSaveStateMemoryOffset(0x00, Channel1.cycleCounter));

// After (fixed)
Channel1.cycleCounter = load<i32>(getSaveStateMemoryOffset(0x00, Channel1.saveStateSlot));

The matching saveState() in each file already uses saveStateSlot (e.g. store<i32>(getSaveStateMemoryOffset(0x00, Channel1.saveStateSlot), Channel1.cycleCounter);), so this PR restores symmetry between save and load — it is not a behavior change, just a fix to a slot index that should always have been the constant.

All four sound channels have the same typo and all four are fixed:

  • core/sound/channel1.tsChannel1.saveStateSlot = 7
  • core/sound/channel2.tsChannel2.saveStateSlot = 8
  • core/sound/channel3.tsChannel3.saveStateSlot = 9
  • core/sound/channel4.tsChannel4.saveStateSlot = 10

Impact

After the first call to the WASM loadState(), each channel's cycleCounter holds whatever 32-bit value happened to be at offset 0x00 of an unrelated save-state region (in practice, near the start of the CPU register data). On the next loadState(), getSaveStateMemoryOffset(0x00, cycleCounter) evaluates to 1024 + 50 * cycleCounter, which can land far past the bounds of WASM linear memory and trap with:

RuntimeError: memory access out of bounds

This blocks any caller that loads more than one save state in the same WasmBoy session — for instance, an automated tool that loops loadState → run input macro → loadState → run input macro to brute-force RNG outcomes. The first load works; the second crashes the WASM module.

Diff

Four single-line changes, all of the same form (ChannelN.cycleCounterChannelN.saveStateSlot in the second argument of getSaveStateMemoryOffset).

Test plan

  • Existing save-state tests should continue to pass (the fix only changes which slot is read; the load symmetry with saveState() was already broken, not tested).
  • Verify in a host harness that two consecutive loadState() calls in the same session no longer trap.

Each sound channel's static loadState() passed the runtime
cycleCounter value to getSaveStateMemoryOffset() as the save-state
slot index, instead of the constant saveStateSlot. After the first
loadState() the cycleCounter holds an arbitrary 32-bit value read
from a wrong offset, and the next loadState() computes
1024 + 50 * cycleCounter, which can land far past WASM linear memory
and trap with "memory access out of bounds".

This blocks any caller that loads more than one save state in the
same WasmBoy session (e.g. an automated tool looping load-state +
input macro to brute-force RNG outcomes).

The matching saveState() in each file already uses saveStateSlot, so
this restores symmetry between save and load. All four channels had
the same copy-paste typo and all four are fixed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant