jsbeeb 1.12.0 → 1.13.1
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.
- package/README.md +16 -2
- package/package.json +8 -8
- package/public/roms/atom/ATMMC3E.rom +0 -0
- package/public/roms/atom/Atom_Basic.rom +0 -0
- package/public/roms/atom/Atom_DOS.rom +0 -0
- package/public/roms/atom/Atom_FloatingPoint.rom +0 -0
- package/public/roms/atom/Atom_Kernel.rom +0 -0
- package/public/roms/atom/Atom_Kernel_E.rom +0 -0
- package/public/roms/atom/PCHARME.ROM +0 -0
- package/public/roms/atom/gags.rom +0 -0
- package/public/roms/atom/werom.rom +0 -0
- package/src/6502.js +344 -44
- package/src/6847.js +724 -0
- package/src/6847_fontdata.js +124 -0
- package/src/app/app.js +2 -15
- package/src/app/args.js +26 -0
- package/src/disc.js +2 -20
- package/src/fake6502.js +3 -2
- package/src/jsbeeb.css +23 -0
- package/src/keyboard.js +45 -23
- package/src/machine-session.js +85 -59
- package/src/main.js +142 -41
- package/src/mmc.js +1053 -0
- package/src/models.js +42 -1
- package/src/ppia.js +477 -0
- package/src/soundchip.js +99 -1
- package/src/tapes.js +73 -16
- package/src/url-params.js +7 -2
- package/src/utils.js +74 -1
- package/src/utils_atom.js +508 -0
- package/src/video.js +12 -1
- package/src/web/audio-handler.js +8 -3
- package/tests/test-machine.js +133 -8
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// MC6847 internal character ROM data (PAL square pixel variant, 8x12).
|
|
4
|
+
// Derived from the MAME project's MC6847 VDG emulation:
|
|
5
|
+
// https://github.com/mamedev/mame/blob/master/src/devices/video/mc6847.cpp
|
|
6
|
+
// Copyright Nathan Woods, licensed under BSD-3-Clause.
|
|
7
|
+
// See https://github.com/mamedev/mame/blob/master/LICENSE.md
|
|
8
|
+
export function makeCharsAtom() {
|
|
9
|
+
return new Uint8Array([
|
|
10
|
+
0x00, 0x00, 0x00, 0x1c, 0x22, 0x02, 0x1a, 0x2a, 0x2a, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x14, 0x22,
|
|
11
|
+
0x22, 0x3e, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x12, 0x12, 0x1c, 0x12, 0x12, 0x3c, 0x00, 0x00,
|
|
12
|
+
0x00, 0x00, 0x00, 0x1c, 0x22, 0x20, 0x20, 0x20, 0x22, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x12, 0x12,
|
|
13
|
+
0x12, 0x12, 0x12, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x20, 0x20, 0x3c, 0x20, 0x20, 0x3e, 0x00, 0x00,
|
|
14
|
+
0x00, 0x00, 0x00, 0x3e, 0x20, 0x20, 0x3c, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x20, 0x20,
|
|
15
|
+
0x26, 0x22, 0x22, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x3e, 0x22, 0x22, 0x22, 0x00, 0x00,
|
|
16
|
+
0x00, 0x00, 0x00, 0x1c, 0x08, 0x08, 0x08, 0x08, 0x08, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x02,
|
|
17
|
+
0x02, 0x22, 0x22, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x24, 0x28, 0x30, 0x28, 0x24, 0x22, 0x00, 0x00,
|
|
18
|
+
0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x36, 0x2a,
|
|
19
|
+
0x2a, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x32, 0x2a, 0x26, 0x22, 0x22, 0x22, 0x00, 0x00,
|
|
20
|
+
0x00, 0x00, 0x00, 0x3e, 0x22, 0x22, 0x22, 0x22, 0x22, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x22, 0x22,
|
|
21
|
+
0x3c, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x22, 0x22, 0x22, 0x2a, 0x24, 0x1a, 0x00, 0x00,
|
|
22
|
+
0x00, 0x00, 0x00, 0x3c, 0x22, 0x22, 0x3c, 0x28, 0x24, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x22, 0x10,
|
|
23
|
+
0x08, 0x04, 0x22, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00,
|
|
24
|
+
0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22,
|
|
25
|
+
0x14, 0x14, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x2a, 0x2a, 0x36, 0x22, 0x00, 0x00,
|
|
26
|
+
0x00, 0x00, 0x00, 0x22, 0x22, 0x14, 0x08, 0x14, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x14,
|
|
27
|
+
0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x02, 0x04, 0x08, 0x10, 0x20, 0x3e, 0x00, 0x00,
|
|
28
|
+
0x00, 0x00, 0x00, 0x38, 0x20, 0x20, 0x20, 0x20, 0x20, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x10,
|
|
29
|
+
0x08, 0x04, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x02, 0x02, 0x02, 0x02, 0x02, 0x0e, 0x00, 0x00,
|
|
30
|
+
0x00, 0x00, 0x00, 0x08, 0x1c, 0x2a, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x10,
|
|
31
|
+
0x3e, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
32
|
+
0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x14, 0x14,
|
|
33
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x14, 0x36, 0x00, 0x36, 0x14, 0x14, 0x00, 0x00,
|
|
34
|
+
0x00, 0x00, 0x00, 0x08, 0x1e, 0x20, 0x1c, 0x02, 0x3c, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x32, 0x04,
|
|
35
|
+
0x08, 0x10, 0x26, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x28, 0x28, 0x10, 0x2a, 0x24, 0x1a, 0x00, 0x00,
|
|
36
|
+
0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x20,
|
|
37
|
+
0x20, 0x20, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08, 0x00, 0x00,
|
|
38
|
+
0x00, 0x00, 0x00, 0x00, 0x08, 0x1c, 0x3e, 0x1c, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08,
|
|
39
|
+
0x3e, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x10, 0x20, 0x00, 0x00,
|
|
40
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
41
|
+
0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x04, 0x08, 0x10, 0x20, 0x20, 0x00, 0x00,
|
|
42
|
+
0x00, 0x00, 0x00, 0x18, 0x24, 0x24, 0x24, 0x24, 0x24, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x18, 0x08,
|
|
43
|
+
0x08, 0x08, 0x08, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x22, 0x02, 0x1c, 0x20, 0x20, 0x3e, 0x00, 0x00,
|
|
44
|
+
0x00, 0x00, 0x00, 0x1c, 0x22, 0x02, 0x0c, 0x02, 0x22, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x0c, 0x14,
|
|
45
|
+
0x3e, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x20, 0x3c, 0x02, 0x02, 0x22, 0x1c, 0x00, 0x00,
|
|
46
|
+
0x00, 0x00, 0x00, 0x1c, 0x20, 0x20, 0x3c, 0x22, 0x22, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x02, 0x04,
|
|
47
|
+
0x08, 0x10, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x22, 0x22, 0x1c, 0x22, 0x22, 0x1c, 0x00, 0x00,
|
|
48
|
+
0x00, 0x00, 0x00, 0x1c, 0x22, 0x22, 0x1e, 0x02, 0x02, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18,
|
|
49
|
+
0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x08, 0x10, 0x00, 0x00,
|
|
50
|
+
0x00, 0x00, 0x00, 0x04, 0x08, 0x10, 0x20, 0x10, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e,
|
|
51
|
+
0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x08, 0x04, 0x02, 0x04, 0x08, 0x10, 0x00, 0x00,
|
|
52
|
+
0x00, 0x00, 0x00, 0x18, 0x24, 0x04, 0x08, 0x08, 0x00, 0x08, 0x00, 0x00,
|
|
53
|
+
|
|
54
|
+
// graphics
|
|
55
|
+
|
|
56
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
57
|
+
0x00, 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0,
|
|
58
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f,
|
|
59
|
+
0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
|
|
60
|
+
0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f,
|
|
61
|
+
0x0f, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00,
|
|
62
|
+
0x00, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xf0,
|
|
63
|
+
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0xff, 0xff, 0xff, 0xff,
|
|
64
|
+
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
|
|
65
|
+
0xff, 0xff, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0xf0,
|
|
66
|
+
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0x00,
|
|
67
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x0f, 0x0f,
|
|
68
|
+
0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0x00,
|
|
69
|
+
0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00,
|
|
70
|
+
0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
|
|
71
|
+
0x0f, 0x0f, 0xf0, 0xf0, 0xf0, 0xf0, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0xff, 0xff, 0xff, 0xff,
|
|
72
|
+
0x0f, 0x0f, 0x0f, 0x0f, 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0xf0, 0xf0,
|
|
73
|
+
0xf0, 0xf0, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
|
|
74
|
+
0x0f, 0x0f, 0x0f, 0x0f, 0xf0, 0xf0, 0xf0, 0xf0, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x0f, 0x0f, 0x0f, 0xff, 0xff,
|
|
75
|
+
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x0f, 0x0f, 0x0f,
|
|
76
|
+
0x0f, 0x0f, 0x0f, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0xf0, 0x0f, 0x0f, 0x0f, 0x0f, 0xff, 0xff,
|
|
77
|
+
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
78
|
+
0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0x00,
|
|
79
|
+
0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
|
|
80
|
+
0xf0, 0xf0, 0xf0, 0xf0, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0x0f, 0x0f,
|
|
81
|
+
0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0xf0, 0xf0, 0xf0, 0xf0, 0x0f, 0x0f, 0x0f, 0x0f, 0xf0, 0xf0, 0xf0, 0xf0,
|
|
82
|
+
0xf0, 0xf0, 0xf0, 0xf0, 0x0f, 0x0f, 0x0f, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
|
|
83
|
+
0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x0f, 0x0f, 0x0f, 0x0f,
|
|
84
|
+
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
|
|
85
|
+
0xf0, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0xf0, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
|
|
86
|
+
0xf0, 0xf0, 0xf0, 0xf0, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x0f, 0x0f, 0x0f, 0xf0, 0xf0, 0xf0, 0xf0, 0xff, 0xff,
|
|
87
|
+
0xff, 0xff, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
88
|
+
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
|
|
89
|
+
0x00, 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0,
|
|
90
|
+
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x0f,
|
|
91
|
+
0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
|
|
92
|
+
0xff, 0xff, 0xff, 0xff, 0x0f, 0x0f, 0x0f, 0x0f, 0xf0, 0xf0, 0xf0, 0xf0, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x0f,
|
|
93
|
+
0x0f, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00,
|
|
94
|
+
0xff, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0xf0, 0x0f, 0x0f, 0x0f, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xf0,
|
|
95
|
+
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0xf0, 0xff, 0xff, 0xff, 0xff,
|
|
96
|
+
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
97
|
+
0xff, 0xff, 0x0f, 0x0f, 0x0f, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xf0, 0xf0, 0xf0,
|
|
98
|
+
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
99
|
+
|
|
100
|
+
/* Lower case */
|
|
101
|
+
0x00, 0x00, 0x00, 0x0c, 0x12, 0x10, 0x38, 0x10, 0x12, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c,
|
|
102
|
+
0x02, 0x1e, 0x22, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x3c, 0x22, 0x22, 0x22, 0x3c, 0x00, 0x00,
|
|
103
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x20, 0x20, 0x20, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x1e,
|
|
104
|
+
0x22, 0x22, 0x22, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x22, 0x3e, 0x20, 0x1c, 0x00, 0x00,
|
|
105
|
+
0x00, 0x00, 0x00, 0x0c, 0x12, 0x10, 0x38, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e,
|
|
106
|
+
0x22, 0x22, 0x22, 0x1e, 0x02, 0x1c, 0x00, 0x00, 0x00, 0x20, 0x20, 0x3c, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00,
|
|
107
|
+
0x00, 0x00, 0x00, 0x08, 0x00, 0x18, 0x08, 0x08, 0x08, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x0c,
|
|
108
|
+
0x04, 0x04, 0x04, 0x04, 0x24, 0x18, 0x00, 0x00, 0x00, 0x20, 0x20, 0x24, 0x28, 0x38, 0x24, 0x22, 0x00, 0x00,
|
|
109
|
+
0x00, 0x00, 0x00, 0x18, 0x08, 0x08, 0x08, 0x08, 0x08, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c,
|
|
110
|
+
0x2a, 0x2a, 0x2a, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x32, 0x22, 0x22, 0x22, 0x00, 0x00,
|
|
111
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x22, 0x22, 0x22, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c,
|
|
112
|
+
0x22, 0x22, 0x22, 0x3c, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x22, 0x22, 0x22, 0x1e, 0x02, 0x03,
|
|
113
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x32, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e,
|
|
114
|
+
0x20, 0x1c, 0x02, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3c, 0x10, 0x10, 0x10, 0x12, 0x0c, 0x00, 0x00,
|
|
115
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x26, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22,
|
|
116
|
+
0x22, 0x14, 0x14, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x2a, 0x2a, 0x1c, 0x14, 0x00, 0x00,
|
|
117
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x14, 0x08, 0x14, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22,
|
|
118
|
+
0x22, 0x22, 0x22, 0x1e, 0x02, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x04, 0x08, 0x10, 0x3e, 0x00, 0x00,
|
|
119
|
+
0x00, 0x00, 0x00, 0x08, 0x10, 0x10, 0x20, 0x10, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08,
|
|
120
|
+
0x00, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x04, 0x04, 0x02, 0x04, 0x04, 0x08, 0x00, 0x00,
|
|
121
|
+
0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0x2a, 0x1c, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x04, 0x3e,
|
|
122
|
+
0x04, 0x08, 0x00, 0x00, 0x00, 0x00,
|
|
123
|
+
]);
|
|
124
|
+
}
|
package/src/app/app.js
CHANGED
|
@@ -5,6 +5,8 @@ import * as fs from "fs";
|
|
|
5
5
|
import * as path from "path";
|
|
6
6
|
import { ArgumentParser } from "argparse";
|
|
7
7
|
|
|
8
|
+
import { getArguments } from "./args.js";
|
|
9
|
+
|
|
8
10
|
const store = new Store();
|
|
9
11
|
|
|
10
12
|
ipcMain.on("set-title", (event, title) => {
|
|
@@ -20,21 +22,6 @@ ipcMain.on("save-settings", (event, settings) => {
|
|
|
20
22
|
|
|
21
23
|
const isMac = process.platform === "darwin";
|
|
22
24
|
|
|
23
|
-
function getArguments() {
|
|
24
|
-
// Heinous hack to get "built" versions working
|
|
25
|
-
let args;
|
|
26
|
-
if (path.basename(process.argv[0]) === "jsbeeb")
|
|
27
|
-
// Is this ia "built" version?
|
|
28
|
-
args = process.argv.slice(1);
|
|
29
|
-
else args = process.argv.slice(2);
|
|
30
|
-
|
|
31
|
-
// Filter out Chrome switches that appear in process.argv. The snap wrapper
|
|
32
|
-
// adds --no-sandbox for compatibility, and `--disable-gpu` might be useful.
|
|
33
|
-
// Note that we don't support snap any more, but these seemed useful to leave.
|
|
34
|
-
const ignoredChromeFlags = ["--no-sandbox", "--disable-gpu"];
|
|
35
|
-
return args.filter((arg) => !ignoredChromeFlags.includes(arg));
|
|
36
|
-
}
|
|
37
|
-
|
|
38
25
|
const parser = new ArgumentParser({
|
|
39
26
|
prog: "jsbeeb",
|
|
40
27
|
add_help: true,
|
package/src/app/args.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns the command-line arguments the user actually passed to jsbeeb, with
|
|
5
|
+
* the Electron binary path(s) and known runtime-injected flags stripped.
|
|
6
|
+
*
|
|
7
|
+
* @param {string[]} [argv=process.argv] Full argv array.
|
|
8
|
+
* @param {boolean} [defaultApp=process.defaultApp]
|
|
9
|
+
* True when running under `electron .` (dev). Electron only sets
|
|
10
|
+
* process.defaultApp in that case, so we use it instead of comparing
|
|
11
|
+
* basename(argv[0]) === "jsbeeb" (the previous check), which failed on
|
|
12
|
+
* Windows where the binary is jsbeeb.exe, silently dropping the first
|
|
13
|
+
* user argument. See issue #684.
|
|
14
|
+
* @returns {string[]}
|
|
15
|
+
*/
|
|
16
|
+
export function getArguments(argv = process.argv, defaultApp = process.defaultApp) {
|
|
17
|
+
// In a packaged Electron app argv is [appBinary, ...userArgs]; when running
|
|
18
|
+
// under `electron .` (dev) it is [electronBinary, appPath, ...userArgs].
|
|
19
|
+
const args = defaultApp ? argv.slice(2) : argv.slice(1);
|
|
20
|
+
|
|
21
|
+
// Filter out Chrome switches that appear in argv. The snap wrapper adds
|
|
22
|
+
// --no-sandbox for compatibility, and --disable-gpu might be useful. We
|
|
23
|
+
// don't support snap any more, but these seemed useful to leave.
|
|
24
|
+
const ignoredChromeFlags = ["--no-sandbox", "--disable-gpu"];
|
|
25
|
+
return args.filter((arg) => !ignoredChromeFlags.includes(arg));
|
|
26
|
+
}
|
package/src/disc.js
CHANGED
|
@@ -1,25 +1,7 @@
|
|
|
1
1
|
// Translated from beebjit by Chris Evans.
|
|
2
2
|
// https://github.com/scarybeasts/beebjit
|
|
3
3
|
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Compute CRC32 of a Uint8Array.
|
|
8
|
-
* @param {Uint8Array} data
|
|
9
|
-
* @returns {number} CRC32 as a signed 32-bit integer
|
|
10
|
-
*/
|
|
11
|
-
export function crc32(data) {
|
|
12
|
-
let crc = 0xffffffff;
|
|
13
|
-
for (let i = 0; i < data.length; ++i) {
|
|
14
|
-
crc ^= data[i];
|
|
15
|
-
for (let j = 0; j < 8; ++j) {
|
|
16
|
-
const doEor = crc & 1;
|
|
17
|
-
crc = crc >>> 1;
|
|
18
|
-
if (doEor) crc ^= 0xedb88320;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
return ~crc;
|
|
22
|
-
}
|
|
4
|
+
import { hexbyte, crc32 } from "./utils.js";
|
|
23
5
|
class TrackBuilder {
|
|
24
6
|
/**
|
|
25
7
|
* @param {Track} track
|
|
@@ -482,7 +464,7 @@ class Track {
|
|
|
482
464
|
}
|
|
483
465
|
break;
|
|
484
466
|
default:
|
|
485
|
-
console.log(`Unknown marker byte ${
|
|
467
|
+
console.log(`Unknown marker byte ${hexbyte(dataByte)} ${this.description}`);
|
|
486
468
|
}
|
|
487
469
|
}
|
|
488
470
|
return sectors;
|
package/src/fake6502.js
CHANGED
|
@@ -6,7 +6,7 @@ import { FakeSoundChip } from "./soundchip.js";
|
|
|
6
6
|
import { findModel, TEST_6502, TEST_65C02, TEST_65C12 } from "./models.js";
|
|
7
7
|
import { FakeDdNoise } from "./ddnoise.js";
|
|
8
8
|
import { FakeRelayNoise } from "./relaynoise.js";
|
|
9
|
-
import { Cpu6502 } from "./6502.js";
|
|
9
|
+
import { Cpu6502, AtomCpu6502 } from "./6502.js";
|
|
10
10
|
import { Cmos } from "./cmos.js";
|
|
11
11
|
import { FakeMusic5000 } from "./music5000.js";
|
|
12
12
|
|
|
@@ -20,7 +20,8 @@ export function fake6502(model, opts) {
|
|
|
20
20
|
opts = opts || {};
|
|
21
21
|
model = model || TEST_6502;
|
|
22
22
|
if (opts.tube) model.tube = findModel("Tube65c02");
|
|
23
|
-
|
|
23
|
+
const CpuClass = model.isAtom ? AtomCpu6502 : Cpu6502;
|
|
24
|
+
return new CpuClass(model, {
|
|
24
25
|
dbgr,
|
|
25
26
|
video: opts.video || fakeVideo,
|
|
26
27
|
soundChip: opts.soundChip || soundChip,
|
package/src/jsbeeb.css
CHANGED
|
@@ -188,6 +188,29 @@ div.led {
|
|
|
188
188
|
background-image: url(/images/yellow-on-16.png);
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
.tape-btn {
|
|
192
|
+
background: #333;
|
|
193
|
+
color: #ccc;
|
|
194
|
+
border: 1px solid #666;
|
|
195
|
+
border-radius: 3px;
|
|
196
|
+
font-size: 10px;
|
|
197
|
+
width: 20px;
|
|
198
|
+
height: 18px;
|
|
199
|
+
padding: 0;
|
|
200
|
+
line-height: 18px;
|
|
201
|
+
cursor: pointer;
|
|
202
|
+
text-align: center;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.tape-btn:hover {
|
|
206
|
+
background: #555;
|
|
207
|
+
color: #fff;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.tape-btn.playing {
|
|
211
|
+
color: #4c4;
|
|
212
|
+
}
|
|
213
|
+
|
|
191
214
|
#pages {
|
|
192
215
|
margin-top: 10px;
|
|
193
216
|
}
|
package/src/keyboard.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
import * as utils from "./utils.js";
|
|
3
|
+
import { ATOM } from "./utils_atom.js";
|
|
3
4
|
|
|
4
5
|
const isMac = typeof window !== "undefined" && /^Mac/i.test(window.navigator?.platform || "");
|
|
5
6
|
|
|
@@ -28,6 +29,14 @@ export class Keyboard extends EventTarget {
|
|
|
28
29
|
this.inputEnabledFunction = inputEnabledFunction;
|
|
29
30
|
this.dbgr = dbgr;
|
|
30
31
|
|
|
32
|
+
// Key interface: routes key events to SysVia (BBC) or PPIA (Atom).
|
|
33
|
+
// Both provide keyDown, keyUp, keyToggleRaw, setKeyLayout,
|
|
34
|
+
// clearKeys, disableKeyboard, enableKeyboard.
|
|
35
|
+
this.keyInterface = processor.model.isAtom ? processor.atomppia : processor.sysvia;
|
|
36
|
+
// The SHIFT key constant used by stringToMachineKeys in the paste key array.
|
|
37
|
+
// Compared by reference in _deliverPasteKey to avoid toggling shift off.
|
|
38
|
+
this._shiftKey = processor.model.isAtom ? ATOM.SHIFT : utils.BBC.SHIFT;
|
|
39
|
+
|
|
31
40
|
// State
|
|
32
41
|
this.emuKeyHandlers = {};
|
|
33
42
|
this.running = false;
|
|
@@ -133,7 +142,7 @@ export class Keyboard extends EventTarget {
|
|
|
133
142
|
*/
|
|
134
143
|
setKeyLayout(layout) {
|
|
135
144
|
this.keyLayout = layout;
|
|
136
|
-
this.
|
|
145
|
+
this.keyInterface.setKeyLayout(layout);
|
|
137
146
|
}
|
|
138
147
|
|
|
139
148
|
/**
|
|
@@ -225,7 +234,7 @@ export class Keyboard extends EventTarget {
|
|
|
225
234
|
const isSpecialHandled = this._handleSpecialKeys(code);
|
|
226
235
|
if (isSpecialHandled) return;
|
|
227
236
|
|
|
228
|
-
// Check for registered handlers first; if one fires, don't
|
|
237
|
+
// Check for registered handlers first; if one fires, don't pass to the emulator.
|
|
229
238
|
// This lets Alt+key and Ctrl+key handlers cleanly own their keys without the
|
|
230
239
|
// underlying key leaking through to the emulated machine.
|
|
231
240
|
const handler = this._findKeyHandler(code, evt.altKey, evt.ctrlKey);
|
|
@@ -234,8 +243,8 @@ export class Keyboard extends EventTarget {
|
|
|
234
243
|
return;
|
|
235
244
|
}
|
|
236
245
|
|
|
237
|
-
// No handler claimed the key
|
|
238
|
-
this.
|
|
246
|
+
// No handler claimed the key; pass it to the emulated machine.
|
|
247
|
+
this.keyInterface.keyDown(code, evt.shiftKey);
|
|
239
248
|
}
|
|
240
249
|
|
|
241
250
|
/**
|
|
@@ -268,7 +277,7 @@ export class Keyboard extends EventTarget {
|
|
|
268
277
|
|
|
269
278
|
// Always let the key ups come through to avoid sticky keys in the debugger
|
|
270
279
|
const code = this.keyCode(evt);
|
|
271
|
-
this.
|
|
280
|
+
this.keyInterface.keyUp(code);
|
|
272
281
|
|
|
273
282
|
// No further special handling needed if not running
|
|
274
283
|
if (!this.running) return;
|
|
@@ -301,10 +310,10 @@ export class Keyboard extends EventTarget {
|
|
|
301
310
|
|
|
302
311
|
// Mac browsers seem to model caps lock as a physical key that's down when capslock is on, and up when it's off.
|
|
303
312
|
// No event is generated when it is physically released on the keyboard. So, we simulate a "tap" here.
|
|
304
|
-
this.
|
|
313
|
+
this.keyInterface.keyDown(utils.keyCodes.CAPSLOCK);
|
|
305
314
|
|
|
306
315
|
// Simulate a key release after a short delay
|
|
307
|
-
setTimeout(() => this.
|
|
316
|
+
setTimeout(() => this.keyInterface.keyUp(utils.keyCodes.CAPSLOCK), CAPS_LOCK_DELAY);
|
|
308
317
|
|
|
309
318
|
if (isMac && window.localStorage && !window.localStorage.getItem("warnedAboutRubbishMacs")) {
|
|
310
319
|
this.dispatchEvent(
|
|
@@ -324,20 +333,21 @@ export class Keyboard extends EventTarget {
|
|
|
324
333
|
}
|
|
325
334
|
|
|
326
335
|
/**
|
|
327
|
-
* Send raw keyboard input to the
|
|
328
|
-
* @param {Array} keysToSend - Array of
|
|
336
|
+
* Send raw keyboard input to the emulated machine (for paste/autotype).
|
|
337
|
+
* @param {Array} keysToSend - Array of machine-specific key codes to send
|
|
329
338
|
* @param {boolean} checkCapsAndShiftLocks - Whether to check caps and shift locks
|
|
330
339
|
*/
|
|
331
|
-
|
|
340
|
+
sendRawKeyboard(keysToSend, checkCapsAndShiftLocks) {
|
|
332
341
|
if (this.isPasting) this.cancelPaste();
|
|
333
342
|
|
|
334
|
-
this.
|
|
335
|
-
this._pasteClocksPerMs =
|
|
343
|
+
this.keyInterface.disableKeyboard();
|
|
344
|
+
this._pasteClocksPerMs =
|
|
345
|
+
Math.floor(this.processor.cpuMultiplier * this.processor.peripheralCyclesPerSecond) / 1000;
|
|
336
346
|
|
|
337
347
|
if (checkCapsAndShiftLocks) {
|
|
338
348
|
let toggleKey = null;
|
|
339
|
-
if (!this.
|
|
340
|
-
else if (this.
|
|
349
|
+
if (!this.keyInterface.capsLockLight) toggleKey = utils.BBC.CAPSLOCK;
|
|
350
|
+
else if (this.keyInterface.shiftLockLight) toggleKey = utils.BBC.SHIFTLOCK;
|
|
341
351
|
if (toggleKey) {
|
|
342
352
|
keysToSend.unshift(toggleKey);
|
|
343
353
|
keysToSend.push(toggleKey);
|
|
@@ -356,13 +366,22 @@ export class Keyboard extends EventTarget {
|
|
|
356
366
|
* @private
|
|
357
367
|
*/
|
|
358
368
|
_deliverPasteKey() {
|
|
359
|
-
if (this._pasteLastChar && this._pasteLastChar !==
|
|
360
|
-
this.
|
|
369
|
+
if (this._pasteLastChar && this._pasteLastChar !== this._shiftKey) {
|
|
370
|
+
this.keyInterface.keyToggleRaw(this._pasteLastChar);
|
|
361
371
|
}
|
|
362
372
|
|
|
363
373
|
if (this._pasteKeys.length === 0) {
|
|
364
374
|
this._pasteLastChar = undefined;
|
|
365
|
-
this.
|
|
375
|
+
this.keyInterface.enableKeyboard();
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Atom's PPIA keyboard is polled, not interrupt-driven like the BBC's
|
|
380
|
+
// SysVIA. Insert a debounce gap after every key release so the ROM
|
|
381
|
+
// sees the key-up before the next key-down arrives.
|
|
382
|
+
if (this._pasteLastChar && this._pasteLastChar !== this._shiftKey && this.processor.model.isAtom) {
|
|
383
|
+
this._pasteLastChar = undefined;
|
|
384
|
+
this._pasteTask.schedule(30 * this._pasteClocksPerMs);
|
|
366
385
|
return;
|
|
367
386
|
}
|
|
368
387
|
|
|
@@ -375,12 +394,15 @@ export class Keyboard extends EventTarget {
|
|
|
375
394
|
return;
|
|
376
395
|
}
|
|
377
396
|
|
|
378
|
-
|
|
397
|
+
// Atom ROM polls the keyboard once per VSync (~16ms at 60 Hz).
|
|
398
|
+
// 80ms gives the ROM ~5 scan cycles to detect, debounce, and
|
|
399
|
+
// process each keypress.
|
|
400
|
+
let delayMs = this.processor.model.isAtom ? 80 : 50;
|
|
379
401
|
if (typeof this._pasteLastChar === "number") {
|
|
380
402
|
delayMs = this._pasteLastChar;
|
|
381
403
|
this._pasteLastChar = undefined;
|
|
382
404
|
} else {
|
|
383
|
-
this.
|
|
405
|
+
this.keyInterface.keyToggleRaw(this._pasteLastChar);
|
|
384
406
|
}
|
|
385
407
|
|
|
386
408
|
this._pasteKeys.shift();
|
|
@@ -393,12 +415,12 @@ export class Keyboard extends EventTarget {
|
|
|
393
415
|
cancelPaste() {
|
|
394
416
|
if (!this.isPasting) return;
|
|
395
417
|
this._pasteTask.cancel();
|
|
396
|
-
if (this._pasteLastChar && this._pasteLastChar !==
|
|
397
|
-
this.
|
|
418
|
+
if (this._pasteLastChar && this._pasteLastChar !== this._shiftKey) {
|
|
419
|
+
this.keyInterface.keyToggleRaw(this._pasteLastChar);
|
|
398
420
|
}
|
|
399
421
|
this._pasteLastChar = undefined;
|
|
400
422
|
this._pasteKeys = [];
|
|
401
|
-
this.
|
|
423
|
+
this.keyInterface.enableKeyboard();
|
|
402
424
|
}
|
|
403
425
|
|
|
404
426
|
/**
|
|
@@ -413,7 +435,7 @@ export class Keyboard extends EventTarget {
|
|
|
413
435
|
* Clears all pressed keys
|
|
414
436
|
*/
|
|
415
437
|
clearKeys() {
|
|
416
|
-
this.
|
|
438
|
+
this.keyInterface.clearKeys();
|
|
417
439
|
}
|
|
418
440
|
|
|
419
441
|
/**
|
package/src/machine-session.js
CHANGED
|
@@ -9,7 +9,7 @@ import { readFileSync } from "fs";
|
|
|
9
9
|
import { fileURLToPath } from "url";
|
|
10
10
|
import path from "path";
|
|
11
11
|
import { TestMachine } from "../tests/test-machine.js";
|
|
12
|
-
import { InstrumentedSoundChip } from "./soundchip.js";
|
|
12
|
+
import { InstrumentedSoundChip, FakeSoundChip } from "./soundchip.js";
|
|
13
13
|
|
|
14
14
|
// Resolve the jsbeeb package root from our own location (src/machine-session.js
|
|
15
15
|
// → go up one level). Passed to setNodeBasePath() so the ROM loader resolves
|
|
@@ -46,22 +46,31 @@ export class MachineSession {
|
|
|
46
46
|
|
|
47
47
|
// Create a real Video instance so we get pixel output
|
|
48
48
|
const modelObj = findModel(modelName);
|
|
49
|
-
this.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
49
|
+
this._isAtom = modelObj.isAtom;
|
|
50
|
+
this._video = new Video(
|
|
51
|
+
modelObj.isMaster,
|
|
52
|
+
this._fb32,
|
|
53
|
+
(minx, miny, maxx, maxy) => {
|
|
54
|
+
this._lastPaint = { minx, miny, maxx, maxy };
|
|
55
|
+
this._frameDirty = true;
|
|
56
|
+
// Snapshot the complete frame now, before clearPaintBuffer() wipes _fb32.
|
|
57
|
+
// This mirrors what the browser does: paint_ext fires → canvas updated → fb32 cleared.
|
|
58
|
+
this._completeFb8.set(this._fb8);
|
|
59
|
+
},
|
|
60
|
+
{ isAtom: modelObj.isAtom },
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// Use a real (instrumented) sound chip so we can read registers and capture writes.
|
|
64
|
+
// Atom models use AtomSoundChip which has a different interface (speakerGenerator,
|
|
65
|
+
// toneGenerator); FakeSoundChip provides compatible no-op stubs for headless mode.
|
|
66
|
+
this._soundChip = modelObj.isAtom ? new FakeSoundChip() : new InstrumentedSoundChip();
|
|
59
67
|
|
|
60
68
|
// TestMachine forwards opts.video and opts.soundChip to fake6502
|
|
61
69
|
this._machine = new TestMachine(modelName, { video: this._video, soundChip: this._soundChip });
|
|
62
70
|
|
|
63
71
|
// Accumulated VDU text output — drained by callers
|
|
64
72
|
this._pendingOutput = [];
|
|
73
|
+
this._flushCapture = () => {};
|
|
65
74
|
|
|
66
75
|
// Breakpoint management — persistent hooks that survive across run calls
|
|
67
76
|
this._breakpoints = new Map(); // id → { hook, type, address, hit }
|
|
@@ -90,8 +99,9 @@ export class MachineSession {
|
|
|
90
99
|
/**
|
|
91
100
|
* Install the VDU character-output capture hook.
|
|
92
101
|
*
|
|
93
|
-
* WRCHV discovery: RAM at
|
|
94
|
-
*
|
|
102
|
+
* WRCHV discovery: RAM at the OS write-character vector (0x20E on BBC,
|
|
103
|
+
* 0x208 on Atom) initialises to 0x0000 before the OS runs. We read
|
|
104
|
+
* directly from cpu.ramRomOs (two array lookups — no
|
|
95
105
|
* readmem() dispatch overhead) on every instruction, waiting for the
|
|
96
106
|
* value to change from its initial 0xFFFF. Once the OS installs a real
|
|
97
107
|
* handler we use that address for the lifetime of the session. Programs
|
|
@@ -105,7 +115,9 @@ export class MachineSession {
|
|
|
105
115
|
_installCaptureHook() {
|
|
106
116
|
const cpu = this._machine.processor;
|
|
107
117
|
const ram = cpu.ramRomOs; // direct Uint8Array — no dispatch overhead
|
|
108
|
-
|
|
118
|
+
// WRCHV vector: BBC at $020E, Atom at $0208.
|
|
119
|
+
const wrchvAddr = this._isAtom ? 0x208 : 0x20e;
|
|
120
|
+
const initialWrchv = ram[wrchvAddr] | (ram[wrchvAddr + 1] << 8); // 0xFFFF pre-boot
|
|
109
121
|
|
|
110
122
|
const attributes = { x: 0, y: 0, text: "", foreground: 7, background: 0, mode: 7 };
|
|
111
123
|
let currentText = "";
|
|
@@ -124,6 +136,12 @@ export class MachineSession {
|
|
|
124
136
|
currentText = "";
|
|
125
137
|
}
|
|
126
138
|
|
|
139
|
+
// Expose flush so drainOutput can capture trailing text
|
|
140
|
+
// that hasn't been terminated by a control character.
|
|
141
|
+
this._flushCapture = flush;
|
|
142
|
+
|
|
143
|
+
const isAtom = this._isAtom;
|
|
144
|
+
|
|
127
145
|
function onChar(c) {
|
|
128
146
|
if (nextN) {
|
|
129
147
|
params.push(c);
|
|
@@ -135,9 +153,6 @@ export class MachineSession {
|
|
|
135
153
|
return;
|
|
136
154
|
}
|
|
137
155
|
switch (c) {
|
|
138
|
-
case 1: // next char to printer only
|
|
139
|
-
nextN = 1;
|
|
140
|
-
break;
|
|
141
156
|
case 10: // LF
|
|
142
157
|
flush();
|
|
143
158
|
attributes.y++;
|
|
@@ -151,48 +166,58 @@ export class MachineSession {
|
|
|
151
166
|
flush();
|
|
152
167
|
attributes.x = 0;
|
|
153
168
|
break;
|
|
154
|
-
case 17: // COLOUR n
|
|
155
|
-
nextN = 1;
|
|
156
|
-
vduProc = (p) => {
|
|
157
|
-
if (p[0] & 0x80) attributes.background = p[0] & 0xf;
|
|
158
|
-
else attributes.foreground = p[0] & 0xf;
|
|
159
|
-
};
|
|
160
|
-
break;
|
|
161
|
-
case 18: // GCOL
|
|
162
|
-
nextN = 2;
|
|
163
|
-
break;
|
|
164
|
-
case 19: // define logical colour
|
|
165
|
-
nextN = 5;
|
|
166
|
-
break;
|
|
167
|
-
case 22: // MODE n
|
|
168
|
-
nextN = 1;
|
|
169
|
-
vduProc = (p) => {
|
|
170
|
-
flush();
|
|
171
|
-
attributes.mode = p[0];
|
|
172
|
-
attributes.x = 0;
|
|
173
|
-
attributes.y = 0;
|
|
174
|
-
attributes.foreground = 7;
|
|
175
|
-
attributes.background = 0;
|
|
176
|
-
};
|
|
177
|
-
break;
|
|
178
|
-
case 25: // PLOT
|
|
179
|
-
nextN = 5;
|
|
180
|
-
break;
|
|
181
|
-
case 28: // define text window
|
|
182
|
-
nextN = 4;
|
|
183
|
-
break;
|
|
184
|
-
case 29: // define graphics origin
|
|
185
|
-
nextN = 4;
|
|
186
|
-
break;
|
|
187
|
-
case 31: // TAB(x,y)
|
|
188
|
-
nextN = 2;
|
|
189
|
-
vduProc = (p) => {
|
|
190
|
-
flush();
|
|
191
|
-
attributes.x = p[0];
|
|
192
|
-
attributes.y = p[1];
|
|
193
|
-
};
|
|
194
|
-
break;
|
|
195
169
|
default:
|
|
170
|
+
// BBC VDU control codes (multi-byte sequences).
|
|
171
|
+
// The Atom doesn't use these; skip them to avoid
|
|
172
|
+
// swallowing printable characters.
|
|
173
|
+
if (!isAtom) {
|
|
174
|
+
switch (c) {
|
|
175
|
+
case 1: // next char to printer only
|
|
176
|
+
nextN = 1;
|
|
177
|
+
return;
|
|
178
|
+
case 17: // COLOUR n
|
|
179
|
+
nextN = 1;
|
|
180
|
+
vduProc = (p) => {
|
|
181
|
+
if (p[0] & 0x80) attributes.background = p[0] & 0xf;
|
|
182
|
+
else attributes.foreground = p[0] & 0xf;
|
|
183
|
+
};
|
|
184
|
+
return;
|
|
185
|
+
case 18: // GCOL
|
|
186
|
+
nextN = 2;
|
|
187
|
+
return;
|
|
188
|
+
case 19: // define logical colour
|
|
189
|
+
nextN = 5;
|
|
190
|
+
return;
|
|
191
|
+
case 22: // MODE n
|
|
192
|
+
nextN = 1;
|
|
193
|
+
vduProc = (p) => {
|
|
194
|
+
flush();
|
|
195
|
+
attributes.mode = p[0];
|
|
196
|
+
attributes.x = 0;
|
|
197
|
+
attributes.y = 0;
|
|
198
|
+
attributes.foreground = 7;
|
|
199
|
+
attributes.background = 0;
|
|
200
|
+
};
|
|
201
|
+
return;
|
|
202
|
+
case 25: // PLOT
|
|
203
|
+
nextN = 5;
|
|
204
|
+
return;
|
|
205
|
+
case 28: // define text window
|
|
206
|
+
nextN = 4;
|
|
207
|
+
return;
|
|
208
|
+
case 29: // define graphics origin
|
|
209
|
+
nextN = 4;
|
|
210
|
+
return;
|
|
211
|
+
case 31: // TAB(x,y)
|
|
212
|
+
nextN = 2;
|
|
213
|
+
vduProc = (p) => {
|
|
214
|
+
flush();
|
|
215
|
+
attributes.x = p[0];
|
|
216
|
+
attributes.y = p[1];
|
|
217
|
+
};
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
196
221
|
if (c >= 32 && c < 0x7f) {
|
|
197
222
|
currentText += String.fromCharCode(c);
|
|
198
223
|
} else {
|
|
@@ -207,7 +232,7 @@ export class MachineSession {
|
|
|
207
232
|
// Once the OS sets WRCHV (it changes from 0xFFFF), we start
|
|
208
233
|
// capturing. Programs that install a custom VDU driver mid-run
|
|
209
234
|
// are handled transparently because we re-read on every call.
|
|
210
|
-
const wrchv = ram[
|
|
235
|
+
const wrchv = ram[wrchvAddr] | (ram[wrchvAddr + 1] << 8);
|
|
211
236
|
if (wrchv !== initialWrchv && addr === wrchv) {
|
|
212
237
|
onChar(cpu.a);
|
|
213
238
|
}
|
|
@@ -391,6 +416,7 @@ export class MachineSession {
|
|
|
391
416
|
* Also includes a flat `screenText` reconstruction.
|
|
392
417
|
*/
|
|
393
418
|
drainOutput({ clear = true } = {}) {
|
|
419
|
+
this._flushCapture();
|
|
394
420
|
const elements = clear ? this._pendingOutput.splice(0) : [...this._pendingOutput];
|
|
395
421
|
return {
|
|
396
422
|
elements,
|