@tmustier/pi-nes 0.2.18 → 0.2.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,32 @@
1
1
  export function nativeVersion(): string;
2
2
 
3
+ export interface CpuDebugState {
4
+ pc: number;
5
+ a: number;
6
+ x: number;
7
+ y: number;
8
+ sp: number;
9
+ p: number;
10
+ lastPc: number;
11
+ lastOpcode: number;
12
+ }
13
+
14
+ export interface MapperDebugState {
15
+ mapperNum: number;
16
+ control: number;
17
+ prg: number;
18
+ chr0: number;
19
+ chr1: number;
20
+ prgMode: number;
21
+ chrMode: number;
22
+ outerPrg: number;
23
+ }
24
+
25
+ export interface NesDebugState {
26
+ cpu: CpuDebugState;
27
+ mapper: MapperDebugState;
28
+ }
29
+
3
30
  export class NativeNes {
4
31
  constructor();
5
32
  setRom(data: Uint8Array): void;
@@ -14,6 +41,7 @@ export class NativeNes {
14
41
  setSram(data: Uint8Array): void;
15
42
  isSramDirty(): boolean;
16
43
  markSramSaved(): void;
44
+ getDebugState(): NesDebugState;
17
45
  getFramebuffer(): Uint8Array;
18
46
  }
19
47
 
@@ -4,6 +4,30 @@
4
4
  /* auto-generated by NAPI-RS */
5
5
 
6
6
  export declare function nativeVersion(): string
7
+ export interface CpuDebugState {
8
+ pc: number
9
+ a: number
10
+ x: number
11
+ y: number
12
+ sp: number
13
+ p: number
14
+ lastPc: number
15
+ lastOpcode: number
16
+ }
17
+ export interface MapperDebugState {
18
+ mapperNum: number
19
+ control: number
20
+ prg: number
21
+ chr0: number
22
+ chr1: number
23
+ prgMode: number
24
+ chrMode: number
25
+ outerPrg: number
26
+ }
27
+ export interface NesDebugState {
28
+ cpu: CpuDebugState
29
+ mapper: MapperDebugState
30
+ }
7
31
  export declare class NativeNes {
8
32
  constructor()
9
33
  setRom(data: Uint8Array): void
@@ -18,5 +42,6 @@ export declare class NativeNes {
18
42
  setSram(data: Uint8Array): void
19
43
  isSramDirty(): boolean
20
44
  markSramSaved(): void
45
+ getDebugState(): NesDebugState
21
46
  getFramebuffer(): Uint8Array
22
47
  }
@@ -48,6 +48,36 @@ impl Display for NativeDisplay {
48
48
  }
49
49
  }
50
50
 
51
+ #[napi(object)]
52
+ pub struct CpuDebugState {
53
+ pub pc: u16,
54
+ pub a: u8,
55
+ pub x: u8,
56
+ pub y: u8,
57
+ pub sp: u8,
58
+ pub p: u8,
59
+ pub last_pc: u16,
60
+ pub last_opcode: u8,
61
+ }
62
+
63
+ #[napi(object)]
64
+ pub struct MapperDebugState {
65
+ pub mapper_num: u8,
66
+ pub control: u8,
67
+ pub prg: u8,
68
+ pub chr0: u8,
69
+ pub chr1: u8,
70
+ pub prg_mode: u8,
71
+ pub chr_mode: u8,
72
+ pub outer_prg: u8,
73
+ }
74
+
75
+ #[napi(object)]
76
+ pub struct NesDebugState {
77
+ pub cpu: CpuDebugState,
78
+ pub mapper: MapperDebugState,
79
+ }
80
+
51
81
  #[napi]
52
82
  pub struct NativeNes {
53
83
  nes: Nes,
@@ -133,6 +163,33 @@ impl NativeNes {
133
163
  self.nes.mark_sram_saved();
134
164
  }
135
165
 
166
+ #[napi]
167
+ pub fn get_debug_state(&self) -> NesDebugState {
168
+ let state = self.nes.debug_state();
169
+ NesDebugState {
170
+ cpu: CpuDebugState {
171
+ pc: state.cpu.pc,
172
+ a: state.cpu.a,
173
+ x: state.cpu.x,
174
+ y: state.cpu.y,
175
+ sp: state.cpu.sp,
176
+ p: state.cpu.p,
177
+ last_pc: state.cpu.last_pc,
178
+ last_opcode: state.cpu.last_opcode,
179
+ },
180
+ mapper: MapperDebugState {
181
+ mapper_num: state.mapper.mapper_num,
182
+ control: state.mapper.control,
183
+ prg: state.mapper.prg,
184
+ chr0: state.mapper.chr0,
185
+ chr1: state.mapper.chr1,
186
+ prg_mode: state.mapper.prg_mode,
187
+ chr_mode: state.mapper.chr_mode,
188
+ outer_prg: state.mapper.outer_prg,
189
+ },
190
+ }
191
+ }
192
+
136
193
  #[napi]
137
194
  pub fn get_framebuffer(&mut self) -> Uint8Array {
138
195
  let ptr = self.framebuffer.as_mut_ptr();
@@ -14,6 +14,17 @@ const SRAM_START: usize = 0x6000;
14
14
  const SRAM_END: usize = 0x8000;
15
15
  const SRAM_SIZE: usize = SRAM_END - SRAM_START;
16
16
 
17
+ pub struct CpuDebugState {
18
+ pub pc: u16,
19
+ pub a: u8,
20
+ pub x: u8,
21
+ pub y: u8,
22
+ pub sp: u8,
23
+ pub p: u8,
24
+ pub last_pc: u16,
25
+ pub last_opcode: u8,
26
+ }
27
+
17
28
  fn to_joypad_button(button: button::Button) -> joypad::Button {
18
29
  match button {
19
30
  button::Button::Joypad1A |
@@ -46,6 +57,8 @@ pub struct Cpu {
46
57
  x: Register<u8>,
47
58
  y: Register<u8>,
48
59
  p: CpuStatusRegister,
60
+ last_pc: u16,
61
+ last_opcode: u8,
49
62
 
50
63
  // CPU inside RAM
51
64
  ram: Memory,
@@ -1106,6 +1119,8 @@ impl Cpu {
1106
1119
  x: Register::<u8>::new(),
1107
1120
  y: Register::<u8>::new(),
1108
1121
  p: CpuStatusRegister::new(),
1122
+ last_pc: 0,
1123
+ last_opcode: 0,
1109
1124
  ram: Memory::new(vec![0; 64 * 1024]), // 64KB
1110
1125
  sram_dirty: false,
1111
1126
  stall_cycles: 0,
@@ -1159,6 +1174,23 @@ impl Cpu {
1159
1174
  self.p.set_i();
1160
1175
  }
1161
1176
 
1177
+ pub fn debug_state(&self) -> CpuDebugState {
1178
+ CpuDebugState {
1179
+ pc: self.pc.load(),
1180
+ a: self.a.load(),
1181
+ x: self.x.load(),
1182
+ y: self.y.load(),
1183
+ sp: self.sp.load(),
1184
+ p: self.p.load(),
1185
+ last_pc: self.last_pc,
1186
+ last_opcode: self.last_opcode,
1187
+ }
1188
+ }
1189
+
1190
+ pub fn mapper_debug_state(&self) -> crate::mapper::MapperDebugState {
1191
+ self.rom.mapper_debug_state()
1192
+ }
1193
+
1162
1194
  pub fn get_ppu(&self) -> &Ppu {
1163
1195
  &self.ppu
1164
1196
  }
@@ -1301,7 +1333,10 @@ impl Cpu {
1301
1333
  }
1302
1334
 
1303
1335
  fn fetch(&mut self) -> u8 {
1304
- let opc = self.load(self.pc.load());
1336
+ let pc = self.pc.load();
1337
+ let opc = self.load(pc);
1338
+ self.last_pc = pc;
1339
+ self.last_opcode = opc;
1305
1340
  self.pc.increment();
1306
1341
  opc
1307
1342
  }
@@ -14,7 +14,8 @@ pub mod default_input;
14
14
  pub mod default_audio;
15
15
  pub mod default_display;
16
16
 
17
- use cpu::Cpu;
17
+ use cpu::{Cpu, CpuDebugState};
18
+ use mapper::MapperDebugState;
18
19
  use rom::Rom;
19
20
  use button::Button;
20
21
  use input::Input;
@@ -62,6 +63,11 @@ pub struct Nes {
62
63
  cpu: Cpu
63
64
  }
64
65
 
66
+ pub struct NesDebugState {
67
+ pub cpu: CpuDebugState,
68
+ pub mapper: MapperDebugState,
69
+ }
70
+
65
71
  impl Nes {
66
72
  /// Creates a new `Nes`.
67
73
  /// You need to pass [`input::Input`](./input/trait.Input.html),
@@ -165,4 +171,11 @@ impl Nes {
165
171
  pub fn mark_sram_saved(&mut self) {
166
172
  self.cpu.mark_sram_saved();
167
173
  }
174
+
175
+ pub fn debug_state(&self) -> NesDebugState {
176
+ NesDebugState {
177
+ cpu: self.cpu.debug_state(),
178
+ mapper: self.cpu.mapper_debug_state(),
179
+ }
180
+ }
168
181
  }
@@ -3,6 +3,18 @@ use rom::Mirrorings;
3
3
  use rom::RomHeader;
4
4
  use register::Register;
5
5
 
6
+ #[derive(Clone, Copy, Default)]
7
+ pub struct MapperDebugState {
8
+ pub mapper_num: u8,
9
+ pub control: u8,
10
+ pub prg: u8,
11
+ pub chr0: u8,
12
+ pub chr1: u8,
13
+ pub prg_mode: u8,
14
+ pub chr_mode: u8,
15
+ pub outer_prg: u8,
16
+ }
17
+
6
18
  impl MapperFactory {
7
19
  pub fn create(header: &RomHeader) -> Box<dyn Mapper> {
8
20
  match header.mapper_num() {
@@ -32,6 +44,10 @@ pub trait Mapper {
32
44
 
33
45
  // @TODO: MMC3Mapper specific. Should this method be here?
34
46
  fn drive_irq_counter(&mut self) -> bool;
47
+
48
+ fn debug_state(&self) -> MapperDebugState {
49
+ MapperDebugState::default()
50
+ }
35
51
  }
36
52
 
37
53
  pub struct NRomMapper {
@@ -196,6 +212,23 @@ impl Mapper for MMC1Mapper {
196
212
  }
197
213
  }
198
214
 
215
+ fn debug_state(&self) -> MapperDebugState {
216
+ MapperDebugState {
217
+ mapper_num: 1,
218
+ control: self.control_register.load(),
219
+ prg: self.prg_bank_register.load(),
220
+ chr0: self.chr_bank0_register.load(),
221
+ chr1: self.chr_bank1_register.load(),
222
+ prg_mode: self.control_register.load_bits(2, 2),
223
+ chr_mode: self.control_register.load_bit(4),
224
+ outer_prg: if self.program_bank_num > 16 {
225
+ (self.chr_bank0_register.load() >> 4) & 1
226
+ } else {
227
+ 0
228
+ },
229
+ }
230
+ }
231
+
199
232
  fn has_mirroring_type(&self) -> bool {
200
233
  true
201
234
  }
@@ -1,5 +1,5 @@
1
1
  use memory::Memory;
2
- use mapper::{Mapper, MapperFactory};
2
+ use mapper::{Mapper, MapperFactory, MapperDebugState};
3
3
 
4
4
  pub struct Rom {
5
5
  header: RomHeader,
@@ -132,6 +132,10 @@ impl Rom {
132
132
  }
133
133
  }
134
134
 
135
+ pub fn mapper_debug_state(&self) -> MapperDebugState {
136
+ self.mapper.debug_state()
137
+ }
138
+
135
139
  // @TODO: MMC3Mapper specific. Should this method be here?
136
140
  pub fn irq_interrupted(&mut self) -> bool {
137
141
  self.mapper.drive_irq_counter()
@@ -148,8 +148,8 @@ export class NesOverlayComponent implements Component {
148
148
 
149
149
  const footer = " NES | Ctrl+Q=Detach | Q=Quit | WASD/Arrows=Move | Z/X=A/B | Enter/Space=Start | Tab=Select";
150
150
  const frameBuffer = this.core.getFrameBuffer();
151
- const debugLine = this.debug ? this.buildDebugLine() : null;
152
- const footerRows = this.debug ? 2 : 1;
151
+ const debugLines = this.debug ? this.buildDebugLines() : [];
152
+ const footerRows = this.debug ? 1 + debugLines.length : 1;
153
153
  if (this.rendererMode === "image") {
154
154
  const lines = this.imageRenderer.render(
155
155
  frameBuffer,
@@ -159,8 +159,8 @@ export class NesOverlayComponent implements Component {
159
159
  this.pixelScale,
160
160
  !this.windowed,
161
161
  );
162
- if (debugLine) {
163
- lines.push(truncateToWidth(debugLine, width));
162
+ for (const line of debugLines) {
163
+ lines.push(truncateToWidth(line, width));
164
164
  }
165
165
  lines.push(truncateToWidth(`\x1b[2m${footer}\x1b[0m`, width));
166
166
  return lines;
@@ -176,8 +176,8 @@ export class NesOverlayComponent implements Component {
176
176
 
177
177
  const rawLines = renderHalfBlock(frameBuffer, targetCols, targetRows, scaleX, scaleY);
178
178
  const lines = rawLines.map((line) => truncateToWidth(`${padPrefix}${line}`, width));
179
- if (debugLine) {
180
- lines.push(truncateToWidth(`${padPrefix}${debugLine}`, width));
179
+ for (const line of debugLines) {
180
+ lines.push(truncateToWidth(`${padPrefix}${line}`, width));
181
181
  }
182
182
  lines.push(truncateToWidth(`\x1b[2m${padPrefix}${footer}\x1b[0m`, width));
183
183
  return lines;
@@ -201,19 +201,40 @@ export class NesOverlayComponent implements Component {
201
201
  this.imageCleared = true;
202
202
  }
203
203
 
204
- private buildDebugLine(): string | null {
204
+ private buildDebugLines(): string[] {
205
205
  const stats = this.statsProvider?.();
206
206
  if (!stats) {
207
- return null;
207
+ return [];
208
208
  }
209
209
  const label = this.debugLabel ? ` core=${this.debugLabel}` : "";
210
210
  const mem = stats.memory;
211
- const line = `DEBUG${label} fps=${stats.tickFps.toFixed(1)} render=${stats.renderFps.toFixed(1)} `
211
+ const lines: string[] = [];
212
+ const statsLine = `DEBUG${label} fps=${stats.tickFps.toFixed(1)} render=${stats.renderFps.toFixed(1)} `
212
213
  + `frames/tick=${stats.avgFramesPerTick.toFixed(2)} dropped=${stats.droppedFrames} `
213
214
  + `catch=${stats.lastCatchUpFrames}/${stats.maxCatchUpFrames} `
214
215
  + `eld=${stats.eventLoopDelayMs.toFixed(2)}ms `
215
216
  + `mem=${mem.heapUsedMb.toFixed(1)}/${mem.rssMb.toFixed(1)}MB ext=${mem.externalMb.toFixed(1)}MB ab=${mem.arrayBuffersMb.toFixed(1)}MB`;
216
- return `\x1b[33m${line}\x1b[0m`;
217
+ lines.push(`\x1b[33m${statsLine}\x1b[0m`);
218
+
219
+ const debugState = this.core.getDebugState();
220
+ if (debugState) {
221
+ const cpu = debugState.cpu;
222
+ const mapper = debugState.mapper;
223
+ const cpuLine = `CPU pc=${this.formatHex(cpu.pc, 4)} op=${this.formatHex(cpu.lastOpcode, 2)} `
224
+ + `a=${this.formatHex(cpu.a, 2)} x=${this.formatHex(cpu.x, 2)} y=${this.formatHex(cpu.y, 2)} `
225
+ + `sp=${this.formatHex(cpu.sp, 2)} p=${this.formatHex(cpu.p, 2)} `
226
+ + `last=${this.formatHex(cpu.lastPc, 4)}`;
227
+ const mapperLine = `MMC1 ctrl=${this.formatHex(mapper.control, 2)} prg=${this.formatHex(mapper.prg, 2)} `
228
+ + `chr0=${this.formatHex(mapper.chr0, 2)} chr1=${this.formatHex(mapper.chr1, 2)} `
229
+ + `prgMode=${mapper.prgMode} chrMode=${mapper.chrMode} outer=${mapper.outerPrg}`;
230
+ lines.push(`\x1b[36m${cpuLine}\x1b[0m`);
231
+ lines.push(`\x1b[36m${mapperLine}\x1b[0m`);
232
+ }
233
+ return lines;
234
+ }
235
+
236
+ private formatHex(value: number, width: number): string {
237
+ return value.toString(16).padStart(width, "0");
217
238
  }
218
239
 
219
240
  private tapButton(button: "start" | "select"): void {
@@ -8,6 +8,33 @@ export interface FrameBuffer {
8
8
  data: Uint8Array;
9
9
  }
10
10
 
11
+ export interface NesCpuDebugState {
12
+ pc: number;
13
+ a: number;
14
+ x: number;
15
+ y: number;
16
+ sp: number;
17
+ p: number;
18
+ lastPc: number;
19
+ lastOpcode: number;
20
+ }
21
+
22
+ export interface NesMapperDebugState {
23
+ mapperNum: number;
24
+ control: number;
25
+ prg: number;
26
+ chr0: number;
27
+ chr1: number;
28
+ prgMode: number;
29
+ chrMode: number;
30
+ outerPrg: number;
31
+ }
32
+
33
+ export interface NesDebugState {
34
+ cpu: NesCpuDebugState;
35
+ mapper: NesMapperDebugState;
36
+ }
37
+
11
38
  export interface NesCore {
12
39
  loadRom(rom: Uint8Array): void;
13
40
  tick(): void;
@@ -18,6 +45,7 @@ export interface NesCore {
18
45
  isSramDirty(): boolean;
19
46
  markSramSaved(): void;
20
47
  getAudioWarning(): string | null;
48
+ getDebugState(): NesDebugState | null;
21
49
  reset(): void;
22
50
  dispose(): void;
23
51
  }
@@ -39,6 +67,7 @@ interface NativeNesInstance {
39
67
  setSram(data: Uint8Array): void;
40
68
  isSramDirty(): boolean;
41
69
  markSramSaved(): void;
70
+ getDebugState(): NesDebugState;
42
71
  getFramebuffer(): Uint8Array;
43
72
  }
44
73
 
@@ -148,6 +177,10 @@ class NativeNesCore implements NesCore {
148
177
  return this.audioWarning;
149
178
  }
150
179
 
180
+ getDebugState(): NesDebugState | null {
181
+ return this.nes.getDebugState();
182
+ }
183
+
151
184
  reset(): void {
152
185
  this.nes.reset();
153
186
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tmustier/pi-nes",
3
- "version": "0.2.18",
3
+ "version": "0.2.19",
4
4
  "description": "NES emulator extension for pi",
5
5
  "keywords": [
6
6
  "pi-package",