diff --git a/extension/docs/emulator.md b/extension/docs/emulator.md index 1e1a50db..ed4e80bb 100644 --- a/extension/docs/emulator.md +++ b/extension/docs/emulator.md @@ -75,7 +75,7 @@ In a folder with a launch configuration, F5 does the same, and of memory, `#mem16[addr]` for a word. - **Memory:** the binary-data icon next to a register or symbol opens memory at that address in the Hex Editor (VS Code offers to install - it). On most platforms you can edit it there; NES, VCS and MAME-based + it). On most platforms you can edit it there; VCS and MAME-based platforms are read-only. - **Disassembly:** right-click in the Call Stack and choose **Open Disassembly View**. It shows instructions with their symbols and source diff --git a/extension/notes/PLAN.md b/extension/notes/PLAN.md index 3919d425..053e0e45 100644 --- a/extension/notes/PLAN.md +++ b/extension/notes/PLAN.md @@ -1871,8 +1871,9 @@ runs has to be a webview too. - **Memory writes.** `EmuCore.write()` goes through the machine's bus, so writing an I/O address does what a store there does. It restarts the timeline (the past can't be replayed into the edited state). It needs a - `Machine`, so NES (JSNES), VCS and the MAME platforms - can't write; `DebugCapabilities.write` says so and the adapter turns on + `Machine` with `write()`, so VCS and the MAME platforms can't write + (NES's `JSNESMachine` writes RAM or the mapper directly, skipping + `cpu.write()`'s bus cycle); `DebugCapabilities.write` says so and the adapter turns on `supportsWriteMemoryRequest` only when it can. After a write the adapter sends `invalidated` (variables); after every stop it sends a `memory` event for the whole space so the Hex Editor reads again. diff --git a/extension/test/emuworker.test.ts b/extension/test/emuworker.test.ts index ab4da878..0161c325 100644 --- a/extension/test/emuworker.test.ts +++ b/extension/test/emuworker.test.ts @@ -295,6 +295,31 @@ describe('extension emuworker', function () { await debug('setBreakpoints', []); }); + it('browses the NES machine state and symbols', async function () { + var dir = path.join(ROOT, 'presets/nes'); + var read = async (rel: string) => { + var p = path.join(dir, rel); + return fs.existsSync(p) ? new Uint8Array(fs.readFileSync(p)) : null; + }; + var nes = await new Builder(ROOT).build({ + platform: 'nes', mainPath: 'scroll.c', mainText: fs.readFileSync(path.join(dir, 'scroll.c'), 'utf-8'), + files: new ProjectFileProvider(read, ROOT, 'nes'), + }); + await rpc.call('start', 'nes', nes.output, nes.files, { paused: true }); + await debug('setBuild', { listings: nes.listings, symbols: nes.symbolmap, mainPath: 'scroll.c', paths: nes.paths }); + var caps = await debug('capabilities'); + assert.equal(caps.tree, true); + assert.equal(caps.write, true); + assert.deepEqual(await debug('debugTree', []), [{ name: 'state', value: '', expandable: true }]); + var cpu = await debug('debugTree', ['state', 'c']); + assert.ok(cpu.find((e: any) => e.name === 'PC')); + var syms = await debug('symbols'); + assert.ok(syms.find((s: any) => s.name === '_main')); + // zero page, through the machine's write + assert.equal(await debug('writeMemory', 0x10, [0x5a]), 1); + assert.deepEqual(await debug('readMemory', 0x10, 1), [0x5a]); + }); + it('drives the debug adapter through the worker, as the extension does', async function () { var backend = new WorkerDebugBackend((method, ...args) => rpc.call('debug', method, ...args), async () => { await rpc.call('start', 'mw8080bw', built.output, built.files, { paused: true }); diff --git a/src/common/emucore.ts b/src/common/emucore.ts index 05287b03..416cceab 100644 --- a/src/common/emucore.ts +++ b/src/common/emucore.ts @@ -305,7 +305,7 @@ export class EmuCore { throw new Error(`platform '${this.id}' cannot read memory`); } - get supportsWrite(): boolean { return !!this.machine; } + get supportsWrite(): boolean { return typeof this.machine?.write === 'function'; } /** * Write a byte through the machine's bus, as the CPU would (so an I/O diff --git a/src/platform/nes.ts b/src/platform/nes.ts index 589893f3..3d8290da 100644 --- a/src/platform/nes.ts +++ b/src/platform/nes.ts @@ -107,6 +107,11 @@ class JSNESMachine { loadState(s) { this.p.loadState(s); } read(a: number) { return this.p.readAddress(a); } readConst(a: number) { return this.p.readAddress(a); } + // for the debugger: RAM, or the mapper, without cpu.write()'s bus cycle + write(a: number, v: number) { + if (a < 0x2000) this.p.nes.cpu.mem[a & 0x7ff] = v; + else this.p.nes.mmap.write(a, v); + } connectProbe(probe: ProbeAll) { this.p.connectProbe(probe); } }