diff --git a/src/controller.d.ts b/src/controller.d.ts index 120db09e..6b7690e3 100644 --- a/src/controller.d.ts +++ b/src/controller.d.ts @@ -2,9 +2,31 @@ export type ButtonKey = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; export class Controller { state: number[]; + baseA: number; + baseB: number; + turboA: boolean; + turboB: boolean; + turboToggle: boolean; + buttonDown: (key: ButtonKey) => void; buttonUp: (key: ButtonKey) => void; clock: () => void; + toJSON(): { + state: number[]; + baseA: number; + baseB: number; + turboA: boolean; + turboB: boolean; + turboToggle: boolean; + }; + fromJSON(state: { + state: number[]; + baseA: number; + baseB: number; + turboA: boolean; + turboB: boolean; + turboToggle: boolean; + }): void; static readonly BUTTON_A = 0; static readonly BUTTON_B = 1; @@ -16,4 +38,12 @@ export class Controller { static readonly BUTTON_RIGHT = 7; static readonly BUTTON_TURBO_A = 8; static readonly BUTTON_TURBO_B = 9; + static readonly JSON_PROPERTIES: readonly [ + "state", + "baseA", + "baseB", + "turboA", + "turboB", + "turboToggle", + ]; } diff --git a/src/controller.js b/src/controller.js index 7fe9f732..c6fb34af 100644 --- a/src/controller.js +++ b/src/controller.js @@ -1,3 +1,5 @@ +import { fromJSON, toJSON } from "./utils.js"; + class Controller { static BUTTON_A = 0; static BUTTON_B = 1; @@ -65,6 +67,23 @@ class Controller { this.state[Controller.BUTTON_B] = this.turboToggle ? 0x41 : 0x40; } } + + static JSON_PROPERTIES = [ + "state", + "baseA", + "baseB", + "turboA", + "turboB", + "turboToggle", + ]; + + toJSON() { + return toJSON(this); + } + + fromJSON(s) { + fromJSON(this, s); + } } export default Controller; diff --git a/src/cpu.js b/src/cpu.js index 81703856..cf6751e7 100644 --- a/src/cpu.js +++ b/src/cpu.js @@ -2022,6 +2022,7 @@ class CPU { "F_BRK", "F_BRK_NEW", "_cpuCycleBase", + "dataBus", ]; toJSON() { diff --git a/src/nes.js b/src/nes.js index 3605eae1..49225ec9 100644 --- a/src/nes.js +++ b/src/nes.js @@ -182,6 +182,10 @@ class NES { mmap: this.mmap.toJSON(), ppu: this.ppu.toJSON(), papu: this.papu.toJSON(), + controllers: { + 1: this.controllers[1].toJSON(), + 2: this.controllers[2].toJSON(), + }, }; } @@ -192,6 +196,11 @@ class NES { this.mmap.fromJSON(s.mmap); this.ppu.fromJSON(s.ppu); this.papu.fromJSON(s.papu); + + if (s.controllers) { + this.controllers[1].fromJSON(s.controllers[1]); + this.controllers[2].fromJSON(s.controllers[2]); + } } } diff --git a/test/nes.spec.js b/test/nes.spec.js index 8336c1b1..ecd768d3 100644 --- a/test/nes.spec.js +++ b/test/nes.spec.js @@ -151,6 +151,34 @@ describe("NES", function () { }); }); + describe("save state serialization", function () { + it("preserves cpu dataBus and controller state", function () { + let nes = new NES(); + let data = fs.readFileSync("roms/croom/croom.nes"); + nes.loadROM(data.toString("binary")); + + nes.cpu.dataBus = 0xab; + nes.buttonDown(1, 0); // A + nes.buttonDown(1, 8); // turbo A + nes.controllers[1].clock(); + + const state = nes.toJSON(); + + let restored = new NES(); + restored.loadROM(data.toString("binary")); + restored.fromJSON(state); + + assert.strictEqual(restored.cpu.dataBus, 0xab); + assert.deepStrictEqual(restored.controllers[1].state, nes.controllers[1].state); + assert.strictEqual(restored.controllers[1].baseA, nes.controllers[1].baseA); + assert.strictEqual(restored.controllers[1].turboA, nes.controllers[1].turboA); + assert.strictEqual( + restored.controllers[1].turboToggle, + nes.controllers[1].turboToggle, + ); + }); + }); + describe("#getFPS()", function () { let nes = new NES(); before(function () {