web-a2e 1.0.0

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.
Files changed (295) hide show
  1. package/.clangd +5 -0
  2. package/.mcp.json +12 -0
  3. package/CLAUDE.md +362 -0
  4. package/CMakeLists.txt +774 -0
  5. package/LICENSE +21 -0
  6. package/README.md +392 -0
  7. package/build-wasm/generated/roms.cpp +2447 -0
  8. package/docker-compose.staging.yml +9 -0
  9. package/docs/basic-rom-disassembly.md +6663 -0
  10. package/docs/softswitch-comparison.md +273 -0
  11. package/docs/thunderclock-debug.md +89 -0
  12. package/examples/cube.bas +72 -0
  13. package/examples/hello.s +55 -0
  14. package/examples/scroll.s +140 -0
  15. package/package.json +18 -0
  16. package/public/assets/apple-logo-old.png +0 -0
  17. package/public/assets/apple-logo.png +0 -0
  18. package/public/assets/drive-closed-light-on.png +0 -0
  19. package/public/assets/drive-closed.png +0 -0
  20. package/public/assets/drive-open-light-on.png +0 -0
  21. package/public/assets/drive-open.png +0 -0
  22. package/public/audio-worklet.js +82 -0
  23. package/public/disks/Apple DOS 3.3 January 1983.dsk +0 -0
  24. package/public/disks/ProDOS 2.4.3.po +0 -0
  25. package/public/disks/h32mb.2mg +0 -0
  26. package/public/disks/library.json +26 -0
  27. package/public/docs/llms/llm-assembler.txt +90 -0
  28. package/public/docs/llms/llm-basic-program.txt +256 -0
  29. package/public/docs/llms/llm-disk-drives.txt +72 -0
  30. package/public/docs/llms/llm-file-explorer.txt +50 -0
  31. package/public/docs/llms/llm-hard-drives.txt +80 -0
  32. package/public/docs/llms/llm-main.txt +51 -0
  33. package/public/docs/llms/llm-slot-configuration.txt +66 -0
  34. package/public/icons/icon-192.svg +4 -0
  35. package/public/icons/icon-512.svg +4 -0
  36. package/public/index.html +661 -0
  37. package/public/llms.txt +49 -0
  38. package/public/manifest.json +29 -0
  39. package/public/shaders/burnin.glsl +22 -0
  40. package/public/shaders/crt.glsl +706 -0
  41. package/public/shaders/edge.glsl +109 -0
  42. package/public/shaders/vertex.glsl +8 -0
  43. package/public/sw.js +186 -0
  44. package/roms/341-0027.bin +0 -0
  45. package/roms/341-0160-A-US-UK.bin +0 -0
  46. package/roms/341-0160-A.bin +0 -0
  47. package/roms/342-0273-A-US-UK.bin +0 -0
  48. package/roms/342-0349-B-C0-FF.bin +0 -0
  49. package/roms/Apple Mouse Interface Card ROM - 342-0270-C.bin +0 -0
  50. package/roms/Thunderclock Plus ROM.bin +0 -0
  51. package/scripts/generate_roms.sh +69 -0
  52. package/src/bindings/wasm_interface.cpp +1940 -0
  53. package/src/core/assembler/assembler.cpp +1239 -0
  54. package/src/core/assembler/assembler.hpp +115 -0
  55. package/src/core/audio/audio.cpp +160 -0
  56. package/src/core/audio/audio.hpp +81 -0
  57. package/src/core/basic/basic_detokenizer.cpp +436 -0
  58. package/src/core/basic/basic_detokenizer.hpp +41 -0
  59. package/src/core/basic/basic_tokenizer.cpp +286 -0
  60. package/src/core/basic/basic_tokenizer.hpp +26 -0
  61. package/src/core/basic/basic_tokens.hpp +295 -0
  62. package/src/core/cards/disk2_card.cpp +568 -0
  63. package/src/core/cards/disk2_card.hpp +316 -0
  64. package/src/core/cards/expansion_card.hpp +185 -0
  65. package/src/core/cards/mockingboard/ay8910.cpp +616 -0
  66. package/src/core/cards/mockingboard/ay8910.hpp +159 -0
  67. package/src/core/cards/mockingboard/via6522.cpp +530 -0
  68. package/src/core/cards/mockingboard/via6522.hpp +163 -0
  69. package/src/core/cards/mockingboard_card.cpp +312 -0
  70. package/src/core/cards/mockingboard_card.hpp +159 -0
  71. package/src/core/cards/mouse_card.cpp +654 -0
  72. package/src/core/cards/mouse_card.hpp +190 -0
  73. package/src/core/cards/smartport/block_device.cpp +202 -0
  74. package/src/core/cards/smartport/block_device.hpp +60 -0
  75. package/src/core/cards/smartport/smartport_card.cpp +603 -0
  76. package/src/core/cards/smartport/smartport_card.hpp +120 -0
  77. package/src/core/cards/thunderclock_card.cpp +237 -0
  78. package/src/core/cards/thunderclock_card.hpp +122 -0
  79. package/src/core/cpu/cpu6502.cpp +1609 -0
  80. package/src/core/cpu/cpu6502.hpp +203 -0
  81. package/src/core/debug/condition_evaluator.cpp +470 -0
  82. package/src/core/debug/condition_evaluator.hpp +87 -0
  83. package/src/core/disassembler/disassembler.cpp +552 -0
  84. package/src/core/disassembler/disassembler.hpp +171 -0
  85. package/src/core/disk-image/disk_image.hpp +267 -0
  86. package/src/core/disk-image/dsk_disk_image.cpp +827 -0
  87. package/src/core/disk-image/dsk_disk_image.hpp +204 -0
  88. package/src/core/disk-image/gcr_encoding.cpp +147 -0
  89. package/src/core/disk-image/gcr_encoding.hpp +78 -0
  90. package/src/core/disk-image/woz_disk_image.cpp +1049 -0
  91. package/src/core/disk-image/woz_disk_image.hpp +343 -0
  92. package/src/core/emulator.cpp +2126 -0
  93. package/src/core/emulator.hpp +434 -0
  94. package/src/core/filesystem/dos33.cpp +178 -0
  95. package/src/core/filesystem/dos33.hpp +66 -0
  96. package/src/core/filesystem/pascal.cpp +262 -0
  97. package/src/core/filesystem/pascal.hpp +87 -0
  98. package/src/core/filesystem/prodos.cpp +369 -0
  99. package/src/core/filesystem/prodos.hpp +119 -0
  100. package/src/core/input/keyboard.cpp +227 -0
  101. package/src/core/input/keyboard.hpp +111 -0
  102. package/src/core/mmu/mmu.cpp +1387 -0
  103. package/src/core/mmu/mmu.hpp +236 -0
  104. package/src/core/types.hpp +196 -0
  105. package/src/core/video/video.cpp +680 -0
  106. package/src/core/video/video.hpp +156 -0
  107. package/src/css/assembler-editor.css +1617 -0
  108. package/src/css/base.css +470 -0
  109. package/src/css/basic-debugger.css +791 -0
  110. package/src/css/basic-editor.css +792 -0
  111. package/src/css/controls.css +783 -0
  112. package/src/css/cpu-debugger.css +1413 -0
  113. package/src/css/debug-base.css +160 -0
  114. package/src/css/debug-windows.css +6455 -0
  115. package/src/css/disk-drives.css +406 -0
  116. package/src/css/documentation.css +392 -0
  117. package/src/css/file-explorer.css +867 -0
  118. package/src/css/hard-drive.css +180 -0
  119. package/src/css/layout.css +217 -0
  120. package/src/css/memory-windows.css +798 -0
  121. package/src/css/modals.css +510 -0
  122. package/src/css/monitor.css +425 -0
  123. package/src/css/release-notes.css +101 -0
  124. package/src/css/responsive.css +400 -0
  125. package/src/css/rule-builder.css +340 -0
  126. package/src/css/save-states.css +201 -0
  127. package/src/css/settings-windows.css +1231 -0
  128. package/src/css/window-switcher.css +150 -0
  129. package/src/js/agent/agent-manager.js +643 -0
  130. package/src/js/agent/agent-tools.js +293 -0
  131. package/src/js/agent/agent-version-tools.js +131 -0
  132. package/src/js/agent/assembler-tools.js +357 -0
  133. package/src/js/agent/basic-program-tools.js +894 -0
  134. package/src/js/agent/disk-tools.js +417 -0
  135. package/src/js/agent/file-explorer-tools.js +269 -0
  136. package/src/js/agent/index.js +13 -0
  137. package/src/js/agent/main-tools.js +222 -0
  138. package/src/js/agent/slot-tools.js +303 -0
  139. package/src/js/agent/smartport-tools.js +257 -0
  140. package/src/js/agent/window-tools.js +80 -0
  141. package/src/js/audio/audio-driver.js +417 -0
  142. package/src/js/audio/audio-worklet.js +85 -0
  143. package/src/js/audio/index.js +8 -0
  144. package/src/js/config/default-layout.js +34 -0
  145. package/src/js/config/version.js +8 -0
  146. package/src/js/data/apple2-rom-routines.js +577 -0
  147. package/src/js/debug/assembler-editor-window.js +2993 -0
  148. package/src/js/debug/basic-breakpoint-manager.js +529 -0
  149. package/src/js/debug/basic-program-parser.js +436 -0
  150. package/src/js/debug/basic-program-window.js +2594 -0
  151. package/src/js/debug/basic-variable-inspector.js +447 -0
  152. package/src/js/debug/breakpoint-manager.js +472 -0
  153. package/src/js/debug/cpu-debugger-window.js +2396 -0
  154. package/src/js/debug/index.js +22 -0
  155. package/src/js/debug/label-manager.js +238 -0
  156. package/src/js/debug/memory-browser-window.js +416 -0
  157. package/src/js/debug/memory-heat-map-window.js +481 -0
  158. package/src/js/debug/memory-map-window.js +206 -0
  159. package/src/js/debug/mockingboard-window.js +882 -0
  160. package/src/js/debug/mouse-card-window.js +355 -0
  161. package/src/js/debug/rule-builder-window.js +648 -0
  162. package/src/js/debug/soft-switch-window.js +458 -0
  163. package/src/js/debug/stack-viewer-window.js +221 -0
  164. package/src/js/debug/symbols.js +416 -0
  165. package/src/js/debug/trace-panel.js +291 -0
  166. package/src/js/debug/zero-page-watch-window.js +297 -0
  167. package/src/js/disk-manager/disk-drives-window.js +212 -0
  168. package/src/js/disk-manager/disk-operations.js +284 -0
  169. package/src/js/disk-manager/disk-persistence.js +301 -0
  170. package/src/js/disk-manager/disk-surface-renderer.js +388 -0
  171. package/src/js/disk-manager/drive-sounds.js +139 -0
  172. package/src/js/disk-manager/hard-drive-manager.js +481 -0
  173. package/src/js/disk-manager/hard-drive-persistence.js +187 -0
  174. package/src/js/disk-manager/hard-drive-window.js +57 -0
  175. package/src/js/disk-manager/index.js +890 -0
  176. package/src/js/display/display-settings-window.js +383 -0
  177. package/src/js/display/index.js +10 -0
  178. package/src/js/display/screen-window.js +342 -0
  179. package/src/js/display/webgl-renderer.js +705 -0
  180. package/src/js/file-explorer/disassembler.js +574 -0
  181. package/src/js/file-explorer/dos33.js +266 -0
  182. package/src/js/file-explorer/file-viewer.js +359 -0
  183. package/src/js/file-explorer/index.js +1261 -0
  184. package/src/js/file-explorer/prodos.js +549 -0
  185. package/src/js/file-explorer/utils.js +67 -0
  186. package/src/js/help/documentation-window.js +1096 -0
  187. package/src/js/help/index.js +10 -0
  188. package/src/js/help/release-notes-window.js +85 -0
  189. package/src/js/help/release-notes.js +612 -0
  190. package/src/js/input/gamepad-handler.js +176 -0
  191. package/src/js/input/index.js +12 -0
  192. package/src/js/input/input-handler.js +396 -0
  193. package/src/js/input/joystick-window.js +404 -0
  194. package/src/js/input/mouse-handler.js +99 -0
  195. package/src/js/input/text-selection.js +462 -0
  196. package/src/js/main.js +653 -0
  197. package/src/js/state/index.js +15 -0
  198. package/src/js/state/save-states-window.js +393 -0
  199. package/src/js/state/state-manager.js +409 -0
  200. package/src/js/state/state-persistence.js +218 -0
  201. package/src/js/ui/confirm.js +43 -0
  202. package/src/js/ui/disk-drive-positioner.js +347 -0
  203. package/src/js/ui/reminder-controller.js +129 -0
  204. package/src/js/ui/slot-configuration-window.js +560 -0
  205. package/src/js/ui/theme-manager.js +61 -0
  206. package/src/js/ui/toast.js +44 -0
  207. package/src/js/ui/ui-controller.js +897 -0
  208. package/src/js/ui/window-switcher.js +275 -0
  209. package/src/js/utils/basic-autocomplete.js +832 -0
  210. package/src/js/utils/basic-highlighting.js +473 -0
  211. package/src/js/utils/basic-tokenizer.js +153 -0
  212. package/src/js/utils/basic-tokens.js +117 -0
  213. package/src/js/utils/constants.js +28 -0
  214. package/src/js/utils/indexeddb-helper.js +225 -0
  215. package/src/js/utils/merlin-editor-support.js +905 -0
  216. package/src/js/utils/merlin-highlighting.js +551 -0
  217. package/src/js/utils/storage.js +125 -0
  218. package/src/js/utils/string-utils.js +19 -0
  219. package/src/js/utils/wasm-memory.js +54 -0
  220. package/src/js/windows/base-window.js +690 -0
  221. package/src/js/windows/index.js +9 -0
  222. package/src/js/windows/window-manager.js +375 -0
  223. package/tests/catch2/catch.hpp +17976 -0
  224. package/tests/common/basic_program_builder.cpp +119 -0
  225. package/tests/common/basic_program_builder.hpp +209 -0
  226. package/tests/common/disk_image_builder.cpp +444 -0
  227. package/tests/common/disk_image_builder.hpp +141 -0
  228. package/tests/common/test_helpers.hpp +118 -0
  229. package/tests/gcr/gcr-test.cpp +142 -0
  230. package/tests/integration/check-rom.js +70 -0
  231. package/tests/integration/compare-boot.js +239 -0
  232. package/tests/integration/crash-trace.js +102 -0
  233. package/tests/integration/disk-boot-test.js +264 -0
  234. package/tests/integration/memory-crash.js +108 -0
  235. package/tests/integration/nibble-read-test.js +249 -0
  236. package/tests/integration/phase-test.js +159 -0
  237. package/tests/integration/test_emulator.cpp +291 -0
  238. package/tests/integration/test_emulator_basic.cpp +91 -0
  239. package/tests/integration/test_emulator_debug.cpp +344 -0
  240. package/tests/integration/test_emulator_disk.cpp +153 -0
  241. package/tests/integration/test_emulator_state.cpp +163 -0
  242. package/tests/klaus/6502_functional_test.bin +0 -0
  243. package/tests/klaus/65C02_extended_opcodes_test.bin +0 -0
  244. package/tests/klaus/klaus_6502_test.cpp +184 -0
  245. package/tests/klaus/klaus_65c02_test.cpp +197 -0
  246. package/tests/thunderclock/thunderclock_mmu_test.cpp +304 -0
  247. package/tests/thunderclock/thunderclock_test.cpp +550 -0
  248. package/tests/unit/test_assembler.cpp +521 -0
  249. package/tests/unit/test_audio.cpp +196 -0
  250. package/tests/unit/test_ay8910.cpp +311 -0
  251. package/tests/unit/test_basic_detokenizer.cpp +265 -0
  252. package/tests/unit/test_basic_tokenizer.cpp +382 -0
  253. package/tests/unit/test_block_device.cpp +259 -0
  254. package/tests/unit/test_condition_evaluator.cpp +219 -0
  255. package/tests/unit/test_cpu6502.cpp +1301 -0
  256. package/tests/unit/test_cpu_addressing.cpp +361 -0
  257. package/tests/unit/test_cpu_cycle_counts.cpp +409 -0
  258. package/tests/unit/test_cpu_decimal.cpp +166 -0
  259. package/tests/unit/test_cpu_interrupts.cpp +285 -0
  260. package/tests/unit/test_disassembler.cpp +323 -0
  261. package/tests/unit/test_disk2_card.cpp +330 -0
  262. package/tests/unit/test_dos33.cpp +273 -0
  263. package/tests/unit/test_dsk_disk_image.cpp +315 -0
  264. package/tests/unit/test_expansion_card.cpp +178 -0
  265. package/tests/unit/test_gcr_encoding.cpp +232 -0
  266. package/tests/unit/test_keyboard.cpp +262 -0
  267. package/tests/unit/test_mmu.cpp +555 -0
  268. package/tests/unit/test_mmu_slots.cpp +323 -0
  269. package/tests/unit/test_mockingboard.cpp +352 -0
  270. package/tests/unit/test_mouse_card.cpp +386 -0
  271. package/tests/unit/test_pascal.cpp +248 -0
  272. package/tests/unit/test_prodos.cpp +259 -0
  273. package/tests/unit/test_smartport_card.cpp +321 -0
  274. package/tests/unit/test_thunderclock.cpp +354 -0
  275. package/tests/unit/test_via6522.cpp +323 -0
  276. package/tests/unit/test_video.cpp +319 -0
  277. package/tests/unit/test_woz_disk_image.cpp +257 -0
  278. package/vite.config.js +96 -0
  279. package/wiki/AI-Agent.md +372 -0
  280. package/wiki/Architecture-Overview.md +303 -0
  281. package/wiki/Audio-System.md +449 -0
  282. package/wiki/CPU-Emulation.md +477 -0
  283. package/wiki/Debugger.md +516 -0
  284. package/wiki/Disk-Drives.md +161 -0
  285. package/wiki/Disk-System-Internals.md +547 -0
  286. package/wiki/Display-Settings.md +88 -0
  287. package/wiki/Expansion-Slots.md +187 -0
  288. package/wiki/File-Explorer.md +259 -0
  289. package/wiki/Getting-Started.md +156 -0
  290. package/wiki/Home.md +69 -0
  291. package/wiki/Input-Devices.md +183 -0
  292. package/wiki/Keyboard-Shortcuts.md +158 -0
  293. package/wiki/Memory-System.md +364 -0
  294. package/wiki/Save-States.md +172 -0
  295. package/wiki/Video-Rendering.md +658 -0
@@ -0,0 +1,1940 @@
1
+ /*
2
+ * wasm_interface.cpp - WebAssembly binding layer exposing the emulator API to JavaScript
3
+ *
4
+ * Written by
5
+ * Mike Daley <michael_daley@icloud.com>
6
+ */
7
+
8
+ #include "../core/emulator.hpp"
9
+ #include "../core/disassembler/disassembler.hpp"
10
+ #include "../core/assembler/assembler.hpp"
11
+ #include "../core/debug/condition_evaluator.hpp"
12
+ #include "../core/filesystem/dos33.hpp"
13
+ #include "../core/filesystem/prodos.hpp"
14
+ #include "../core/filesystem/pascal.hpp"
15
+ #include "../core/basic/basic_detokenizer.hpp"
16
+ #include "../core/basic/basic_tokenizer.hpp"
17
+ #include "../core/input/keyboard.hpp"
18
+ #include <cstdlib>
19
+ #include <cstring>
20
+ #include <emscripten.h>
21
+
22
+ // Global emulator instance
23
+ static a2e::Emulator *g_emulator = nullptr;
24
+
25
+ // Helper macros to reduce repetitive null checks
26
+ #define REQUIRE_EMULATOR() do { if (!g_emulator) return; } while(0)
27
+ #define REQUIRE_EMULATOR_OR(default_val) do { if (!g_emulator) return (default_val); } while(0)
28
+
29
+ extern "C" {
30
+
31
+ EMSCRIPTEN_KEEPALIVE
32
+ void init() {
33
+ if (!g_emulator) {
34
+ g_emulator = new a2e::Emulator();
35
+ g_emulator->init();
36
+ }
37
+ }
38
+
39
+ EMSCRIPTEN_KEEPALIVE
40
+ void reset() {
41
+ REQUIRE_EMULATOR();
42
+ g_emulator->reset();
43
+ }
44
+
45
+ EMSCRIPTEN_KEEPALIVE
46
+ void warmReset() {
47
+ REQUIRE_EMULATOR();
48
+ g_emulator->warmReset();
49
+ }
50
+
51
+ EMSCRIPTEN_KEEPALIVE
52
+ void runCycles(int cycles) {
53
+ REQUIRE_EMULATOR();
54
+ g_emulator->runCycles(cycles);
55
+ }
56
+
57
+ EMSCRIPTEN_KEEPALIVE
58
+ int generateStereoAudioSamples(float *buffer, int sampleCount) {
59
+ REQUIRE_EMULATOR_OR(0);
60
+ return g_emulator->generateStereoAudioSamples(buffer, sampleCount);
61
+ }
62
+
63
+ EMSCRIPTEN_KEEPALIVE
64
+ void setAudioVolume(float volume) {
65
+ REQUIRE_EMULATOR();
66
+ g_emulator->getAudio().setVolume(volume);
67
+ }
68
+
69
+ EMSCRIPTEN_KEEPALIVE
70
+ void setAudioMuted(bool muted) {
71
+ REQUIRE_EMULATOR();
72
+ g_emulator->getAudio().setMuted(muted);
73
+ }
74
+
75
+ EMSCRIPTEN_KEEPALIVE
76
+ int consumeFrameSamples() {
77
+ REQUIRE_EMULATOR_OR(0);
78
+ return g_emulator->consumeFrameSamples();
79
+ }
80
+
81
+ EMSCRIPTEN_KEEPALIVE
82
+ uint8_t *getFramebuffer() {
83
+ REQUIRE_EMULATOR_OR(nullptr);
84
+ return const_cast<uint8_t *>(g_emulator->getFramebuffer());
85
+ }
86
+
87
+ EMSCRIPTEN_KEEPALIVE
88
+ int getFramebufferSize() { return a2e::FRAMEBUFFER_SIZE; }
89
+
90
+ EMSCRIPTEN_KEEPALIVE
91
+ void forceRenderFrame() {
92
+ REQUIRE_EMULATOR();
93
+ g_emulator->getVideo().forceRenderFrame();
94
+ }
95
+
96
+ EMSCRIPTEN_KEEPALIVE
97
+ bool isFrameReady() {
98
+ REQUIRE_EMULATOR_OR(false);
99
+ bool ready = g_emulator->isFrameReady();
100
+ if (ready) {
101
+ g_emulator->clearFrameReady();
102
+ }
103
+ return ready;
104
+ }
105
+
106
+ EMSCRIPTEN_KEEPALIVE
107
+ void keyDown(int keycode) {
108
+ REQUIRE_EMULATOR();
109
+ g_emulator->keyDown(keycode);
110
+ }
111
+
112
+ EMSCRIPTEN_KEEPALIVE
113
+ void keyUp(int keycode) {
114
+ REQUIRE_EMULATOR();
115
+ g_emulator->keyUp(keycode);
116
+ }
117
+
118
+ EMSCRIPTEN_KEEPALIVE
119
+ int handleRawKeyDown(int browserKeycode, bool shift, bool ctrl, bool alt,
120
+ bool meta, bool capsLock) {
121
+ REQUIRE_EMULATOR_OR(-1);
122
+ return g_emulator->handleRawKeyDown(browserKeycode, shift, ctrl, alt, meta,
123
+ capsLock);
124
+ }
125
+
126
+ EMSCRIPTEN_KEEPALIVE
127
+ void handleRawKeyUp(int browserKeycode, bool shift, bool ctrl, bool alt,
128
+ bool meta) {
129
+ REQUIRE_EMULATOR();
130
+ g_emulator->handleRawKeyUp(browserKeycode, shift, ctrl, alt, meta);
131
+ }
132
+
133
+ EMSCRIPTEN_KEEPALIVE
134
+ int charToAppleKey(int charCode) {
135
+ return a2e::charToAppleKey(charCode);
136
+ }
137
+
138
+ EMSCRIPTEN_KEEPALIVE
139
+ void setButton(int button, bool pressed) {
140
+ REQUIRE_EMULATOR();
141
+ g_emulator->setButton(button, pressed);
142
+ }
143
+
144
+ EMSCRIPTEN_KEEPALIVE
145
+ void setPaddleValue(int paddle, int value) {
146
+ REQUIRE_EMULATOR();
147
+ g_emulator->setPaddleValue(paddle, value);
148
+ }
149
+
150
+ EMSCRIPTEN_KEEPALIVE
151
+ int getPaddleValue(int paddle) {
152
+ REQUIRE_EMULATOR_OR(128);
153
+ return g_emulator->getPaddleValue(paddle);
154
+ }
155
+
156
+ EMSCRIPTEN_KEEPALIVE
157
+ bool isKeyboardReady() {
158
+ REQUIRE_EMULATOR_OR(true);
159
+ return g_emulator->isKeyboardReady();
160
+ }
161
+
162
+ EMSCRIPTEN_KEEPALIVE
163
+ void setSpeedMultiplier(int multiplier) {
164
+ REQUIRE_EMULATOR();
165
+ g_emulator->setSpeedMultiplier(multiplier);
166
+ }
167
+
168
+ EMSCRIPTEN_KEEPALIVE
169
+ int getSpeedMultiplier() {
170
+ REQUIRE_EMULATOR_OR(1);
171
+ return g_emulator->getSpeedMultiplier();
172
+ }
173
+
174
+ EMSCRIPTEN_KEEPALIVE
175
+ bool insertDisk(int drive, uint8_t *data, int size, const char *filename) {
176
+ REQUIRE_EMULATOR_OR(false);
177
+ return g_emulator->insertDisk(drive, data, size, filename);
178
+ }
179
+
180
+ EMSCRIPTEN_KEEPALIVE
181
+ bool insertBlankDisk(int drive) {
182
+ REQUIRE_EMULATOR_OR(false);
183
+ return g_emulator->insertBlankDisk(drive);
184
+ }
185
+
186
+ EMSCRIPTEN_KEEPALIVE
187
+ void ejectDisk(int drive) {
188
+ REQUIRE_EMULATOR();
189
+ g_emulator->ejectDisk(drive);
190
+ }
191
+
192
+ EMSCRIPTEN_KEEPALIVE
193
+ uint8_t *getDiskData(int drive, size_t *size) {
194
+ if (!g_emulator) { *size = 0; return nullptr; }
195
+ return const_cast<uint8_t *>(g_emulator->exportDiskData(drive, size));
196
+ }
197
+
198
+ EMSCRIPTEN_KEEPALIVE
199
+ const uint8_t *getDiskSectorData(int drive, size_t *size) {
200
+ if (!g_emulator) { *size = 0; return nullptr; }
201
+ return g_emulator->getDiskData(drive, size);
202
+ }
203
+
204
+ // ============================================================================
205
+ // Beam Position
206
+ // ============================================================================
207
+
208
+ EMSCRIPTEN_KEEPALIVE
209
+ int getFrameCycle() {
210
+ REQUIRE_EMULATOR_OR(0);
211
+ return g_emulator->getFrameCycle();
212
+ }
213
+
214
+ EMSCRIPTEN_KEEPALIVE
215
+ int getBeamScanline() {
216
+ REQUIRE_EMULATOR_OR(0);
217
+ return g_emulator->getBeamScanline();
218
+ }
219
+
220
+ EMSCRIPTEN_KEEPALIVE
221
+ int getBeamHPos() {
222
+ REQUIRE_EMULATOR_OR(0);
223
+ return g_emulator->getBeamHPos();
224
+ }
225
+
226
+ EMSCRIPTEN_KEEPALIVE
227
+ int getBeamColumn() {
228
+ REQUIRE_EMULATOR_OR(-1);
229
+ return g_emulator->getBeamColumn();
230
+ }
231
+
232
+ EMSCRIPTEN_KEEPALIVE
233
+ bool isInVBL() {
234
+ REQUIRE_EMULATOR_OR(false);
235
+ return g_emulator->isInVBL();
236
+ }
237
+
238
+ EMSCRIPTEN_KEEPALIVE
239
+ bool isInHBLANK() {
240
+ REQUIRE_EMULATOR_OR(false);
241
+ return g_emulator->isInHBLANK();
242
+ }
243
+
244
+ // ============================================================================
245
+ // Step Over / Step Out
246
+ // ============================================================================
247
+
248
+ EMSCRIPTEN_KEEPALIVE
249
+ uint16_t stepOver() {
250
+ REQUIRE_EMULATOR_OR(0);
251
+ return g_emulator->stepOver();
252
+ }
253
+
254
+ EMSCRIPTEN_KEEPALIVE
255
+ uint16_t stepOut() {
256
+ REQUIRE_EMULATOR_OR(0);
257
+ return g_emulator->stepOut();
258
+ }
259
+
260
+ EMSCRIPTEN_KEEPALIVE
261
+ void clearTempBreakpoint() {
262
+ REQUIRE_EMULATOR();
263
+ g_emulator->clearTempBreakpoint();
264
+ }
265
+
266
+ EMSCRIPTEN_KEEPALIVE
267
+ bool isTempBreakpointHit() {
268
+ REQUIRE_EMULATOR_OR(false);
269
+ return g_emulator->isTempBreakpointHit();
270
+ }
271
+
272
+ EMSCRIPTEN_KEEPALIVE
273
+ void addBreakpoint(uint16_t address) {
274
+ REQUIRE_EMULATOR();
275
+ g_emulator->addBreakpoint(address);
276
+ }
277
+
278
+ EMSCRIPTEN_KEEPALIVE
279
+ void removeBreakpoint(uint16_t address) {
280
+ REQUIRE_EMULATOR();
281
+ g_emulator->removeBreakpoint(address);
282
+ }
283
+
284
+ EMSCRIPTEN_KEEPALIVE
285
+ void enableBreakpoint(uint16_t address, bool enabled) {
286
+ REQUIRE_EMULATOR();
287
+ g_emulator->enableBreakpoint(address, enabled);
288
+ }
289
+
290
+ EMSCRIPTEN_KEEPALIVE
291
+ bool isBreakpointHit() {
292
+ REQUIRE_EMULATOR_OR(false);
293
+ return g_emulator->isBreakpointHit();
294
+ }
295
+
296
+ EMSCRIPTEN_KEEPALIVE
297
+ uint16_t getBreakpointAddress() {
298
+ REQUIRE_EMULATOR_OR(0);
299
+ return g_emulator->getBreakpointAddress();
300
+ }
301
+
302
+ // ============================================================================
303
+ // BASIC Breakpoints
304
+ // ============================================================================
305
+
306
+ EMSCRIPTEN_KEEPALIVE
307
+ void addBasicBreakpoint(uint16_t lineNumber, int statementIndex) {
308
+ REQUIRE_EMULATOR();
309
+ g_emulator->addBasicBreakpoint(lineNumber, statementIndex);
310
+ }
311
+
312
+ EMSCRIPTEN_KEEPALIVE
313
+ void removeBasicBreakpoint(uint16_t lineNumber, int statementIndex) {
314
+ REQUIRE_EMULATOR();
315
+ g_emulator->removeBasicBreakpoint(lineNumber, statementIndex);
316
+ }
317
+
318
+ EMSCRIPTEN_KEEPALIVE
319
+ void clearBasicBreakpoints() {
320
+ REQUIRE_EMULATOR();
321
+ g_emulator->clearBasicBreakpoints();
322
+ }
323
+
324
+ EMSCRIPTEN_KEEPALIVE
325
+ void clearBasicBreakpointHit() {
326
+ REQUIRE_EMULATOR();
327
+ g_emulator->clearBasicBreakpointHit();
328
+ }
329
+
330
+ EMSCRIPTEN_KEEPALIVE
331
+ void addBasicConditionRule(int id, const char* expression) {
332
+ REQUIRE_EMULATOR();
333
+ g_emulator->addBasicConditionRule(id, expression);
334
+ }
335
+
336
+ EMSCRIPTEN_KEEPALIVE
337
+ void removeBasicConditionRule(int id) {
338
+ REQUIRE_EMULATOR();
339
+ g_emulator->removeBasicConditionRule(id);
340
+ }
341
+
342
+ EMSCRIPTEN_KEEPALIVE
343
+ void clearBasicConditionRules() {
344
+ REQUIRE_EMULATOR();
345
+ g_emulator->clearBasicConditionRules();
346
+ }
347
+
348
+ EMSCRIPTEN_KEEPALIVE
349
+ int getBasicConditionRuleHitId() {
350
+ REQUIRE_EMULATOR_OR(-1);
351
+ return g_emulator->getBasicConditionRuleHitId();
352
+ }
353
+
354
+ EMSCRIPTEN_KEEPALIVE
355
+ bool hasBasicBreakpoints() {
356
+ REQUIRE_EMULATOR_OR(false);
357
+ return g_emulator->hasBasicBreakpoints();
358
+ }
359
+
360
+ EMSCRIPTEN_KEEPALIVE
361
+ bool isBasicBreakpointHit() {
362
+ REQUIRE_EMULATOR_OR(false);
363
+ return g_emulator->isBasicBreakpointHit();
364
+ }
365
+
366
+ EMSCRIPTEN_KEEPALIVE
367
+ uint16_t getBasicBreakLine() {
368
+ REQUIRE_EMULATOR_OR(0);
369
+ return g_emulator->getBasicBreakLine();
370
+ }
371
+
372
+ EMSCRIPTEN_KEEPALIVE
373
+ bool isBasicProgramRunning() {
374
+ REQUIRE_EMULATOR_OR(false);
375
+ return g_emulator->isBasicProgramRunning();
376
+ }
377
+
378
+ EMSCRIPTEN_KEEPALIVE
379
+ bool isBasicErrorHit() {
380
+ REQUIRE_EMULATOR_OR(false);
381
+ return g_emulator->isBasicErrorHit();
382
+ }
383
+
384
+ EMSCRIPTEN_KEEPALIVE
385
+ uint16_t getBasicErrorLine() {
386
+ REQUIRE_EMULATOR_OR(0);
387
+ return g_emulator->getBasicErrorLine();
388
+ }
389
+
390
+ EMSCRIPTEN_KEEPALIVE
391
+ uint16_t getBasicErrorTxtptr() {
392
+ REQUIRE_EMULATOR_OR(0);
393
+ return g_emulator->getBasicErrorTxtptr();
394
+ }
395
+
396
+ EMSCRIPTEN_KEEPALIVE
397
+ uint8_t getBasicErrorCode() {
398
+ REQUIRE_EMULATOR_OR(0);
399
+ return g_emulator->getBasicErrorCode();
400
+ }
401
+
402
+ EMSCRIPTEN_KEEPALIVE
403
+ void clearBasicError() {
404
+ REQUIRE_EMULATOR();
405
+ g_emulator->clearBasicError();
406
+ }
407
+
408
+ EMSCRIPTEN_KEEPALIVE
409
+ void stepBasicLine() {
410
+ REQUIRE_EMULATOR();
411
+ g_emulator->stepBasicLine();
412
+ }
413
+
414
+ EMSCRIPTEN_KEEPALIVE
415
+ void stepBasicStatement() {
416
+ REQUIRE_EMULATOR();
417
+ g_emulator->stepBasicStatement();
418
+ }
419
+
420
+ EMSCRIPTEN_KEEPALIVE
421
+ uint16_t getBasicTxtptr() {
422
+ REQUIRE_EMULATOR_OR(0);
423
+ return g_emulator->getBasicTxtptr();
424
+ }
425
+
426
+ EMSCRIPTEN_KEEPALIVE
427
+ int getBasicStatementIndex() {
428
+ REQUIRE_EMULATOR_OR(0);
429
+ return g_emulator->getBasicStatementIndex();
430
+ }
431
+
432
+ // Debug function to get BASIC memory state with detailed line info
433
+ // Uses readRAM to bypass ALTZP - BASIC always uses main RAM for zero page
434
+ EMSCRIPTEN_KEEPALIVE
435
+ void getBasicDebugInfo(uint16_t* txttab, uint16_t* vartab, uint16_t* curlin, uint16_t* txtptr) {
436
+ if (!g_emulator) return;
437
+ auto& mmu = g_emulator->getMMU();
438
+ *txttab = mmu.readRAM(0x67, false) | (mmu.readRAM(0x68, false) << 8);
439
+ *vartab = mmu.readRAM(0x69, false) | (mmu.readRAM(0x6A, false) << 8);
440
+ *curlin = mmu.readRAM(0x75, false) | (mmu.readRAM(0x76, false) << 8);
441
+ *txtptr = mmu.readRAM(0xB8, false) | (mmu.readRAM(0xB9, false) << 8);
442
+ }
443
+
444
+ // BASIC line heat map
445
+ EMSCRIPTEN_KEEPALIVE
446
+ void setBasicHeatMapEnabled(bool enabled) {
447
+ REQUIRE_EMULATOR();
448
+ g_emulator->setBasicHeatMapEnabled(enabled);
449
+ }
450
+
451
+ EMSCRIPTEN_KEEPALIVE
452
+ void clearBasicHeatMap() {
453
+ REQUIRE_EMULATOR();
454
+ g_emulator->clearBasicHeatMap();
455
+ }
456
+
457
+ EMSCRIPTEN_KEEPALIVE
458
+ int getBasicHeatMapSize() {
459
+ REQUIRE_EMULATOR_OR(0);
460
+ return g_emulator->getBasicHeatMapSize();
461
+ }
462
+
463
+ EMSCRIPTEN_KEEPALIVE
464
+ int getBasicHeatMapData(uint16_t* lines, uint32_t* counts, int maxEntries) {
465
+ REQUIRE_EMULATOR_OR(0);
466
+ return g_emulator->getBasicHeatMapData(lines, counts, maxEntries);
467
+ }
468
+
469
+ // Debug function to dump bytes around TXTPTR to see what's there
470
+ EMSCRIPTEN_KEEPALIVE
471
+ void getBasicLineBytes(uint8_t* buffer, int* lineStart, int* colonCount) {
472
+ if (!g_emulator) return;
473
+ auto& mmu = g_emulator->getMMU();
474
+
475
+ uint16_t txttab = mmu.readRAM(0x67, false) | (mmu.readRAM(0x68, false) << 8);
476
+ uint16_t curlin = mmu.readRAM(0x75, false) | (mmu.readRAM(0x76, false) << 8);
477
+ uint16_t txtptr = mmu.readRAM(0xB8, false) | (mmu.readRAM(0xB9, false) << 8);
478
+
479
+ // Find current line
480
+ uint16_t addr = txttab;
481
+ uint16_t foundLineStart = 0;
482
+
483
+ while (addr < 0xC000) {
484
+ uint16_t nextPtr = mmu.readRAM(addr, false) | (mmu.readRAM(addr + 1, false) << 8);
485
+ if (nextPtr == 0) break;
486
+
487
+ uint16_t lineNum = mmu.readRAM(addr + 2, false) | (mmu.readRAM(addr + 3, false) << 8);
488
+ if (lineNum == curlin) {
489
+ foundLineStart = addr + 4;
490
+ break;
491
+ }
492
+ addr = nextPtr;
493
+ }
494
+
495
+ *lineStart = foundLineStart;
496
+
497
+ // Count colons from line start to TXTPTR
498
+ int count = 0;
499
+ if (foundLineStart > 0 && txtptr > foundLineStart) {
500
+ for (uint16_t a = foundLineStart; a < txtptr && a < foundLineStart + 64; a++) {
501
+ uint8_t byte = mmu.readRAM(a, false);
502
+ if (byte == 0) break;
503
+ if (byte == 0x3A) count++; // Colon
504
+ }
505
+ }
506
+ *colonCount = count;
507
+
508
+ // Copy 32 bytes starting from line start (or txtptr if lineStart is 0)
509
+ uint16_t dumpStart = foundLineStart > 0 ? foundLineStart : txtptr;
510
+ for (int i = 0; i < 32; i++) {
511
+ buffer[i] = mmu.readRAM(dumpStart + i, false);
512
+ }
513
+ }
514
+
515
+ EMSCRIPTEN_KEEPALIVE
516
+ uint16_t getPC() {
517
+ REQUIRE_EMULATOR_OR(0);
518
+ return g_emulator->getPC();
519
+ }
520
+
521
+ EMSCRIPTEN_KEEPALIVE
522
+ uint8_t getSP() {
523
+ REQUIRE_EMULATOR_OR(0);
524
+ return g_emulator->getSP();
525
+ }
526
+
527
+ EMSCRIPTEN_KEEPALIVE
528
+ uint8_t getA() {
529
+ REQUIRE_EMULATOR_OR(0);
530
+ return g_emulator->getA();
531
+ }
532
+
533
+ EMSCRIPTEN_KEEPALIVE
534
+ uint8_t getX() {
535
+ REQUIRE_EMULATOR_OR(0);
536
+ return g_emulator->getX();
537
+ }
538
+
539
+ EMSCRIPTEN_KEEPALIVE
540
+ uint8_t getY() {
541
+ REQUIRE_EMULATOR_OR(0);
542
+ return g_emulator->getY();
543
+ }
544
+
545
+ EMSCRIPTEN_KEEPALIVE
546
+ uint8_t getP() {
547
+ REQUIRE_EMULATOR_OR(0);
548
+ return g_emulator->getP();
549
+ }
550
+
551
+ EMSCRIPTEN_KEEPALIVE
552
+ uint64_t getTotalCycles() {
553
+ REQUIRE_EMULATOR_OR(0);
554
+ return g_emulator->getTotalCycles();
555
+ }
556
+
557
+ EMSCRIPTEN_KEEPALIVE
558
+ bool isIRQPending() {
559
+ REQUIRE_EMULATOR_OR(false);
560
+ return g_emulator->isIRQPending();
561
+ }
562
+
563
+ EMSCRIPTEN_KEEPALIVE
564
+ bool isNMIPending() {
565
+ REQUIRE_EMULATOR_OR(false);
566
+ return g_emulator->isNMIPending();
567
+ }
568
+
569
+ EMSCRIPTEN_KEEPALIVE
570
+ bool isNMIEdge() {
571
+ REQUIRE_EMULATOR_OR(false);
572
+ return g_emulator->isNMIEdge();
573
+ }
574
+
575
+ // CPU register setters (for debugger editing)
576
+ EMSCRIPTEN_KEEPALIVE
577
+ void setRegA(uint8_t value) {
578
+ REQUIRE_EMULATOR();
579
+ g_emulator->setA(value);
580
+ }
581
+
582
+ EMSCRIPTEN_KEEPALIVE
583
+ void setRegX(uint8_t value) {
584
+ REQUIRE_EMULATOR();
585
+ g_emulator->setX(value);
586
+ }
587
+
588
+ EMSCRIPTEN_KEEPALIVE
589
+ void setRegY(uint8_t value) {
590
+ REQUIRE_EMULATOR();
591
+ g_emulator->setY(value);
592
+ }
593
+
594
+ EMSCRIPTEN_KEEPALIVE
595
+ void setRegSP(uint8_t value) {
596
+ REQUIRE_EMULATOR();
597
+ g_emulator->setSP(value);
598
+ }
599
+
600
+ EMSCRIPTEN_KEEPALIVE
601
+ void setRegPC(uint16_t value) {
602
+ REQUIRE_EMULATOR();
603
+ g_emulator->setPC(value);
604
+ }
605
+
606
+ EMSCRIPTEN_KEEPALIVE
607
+ void setRegP(uint8_t value) {
608
+ REQUIRE_EMULATOR();
609
+ g_emulator->setP(value);
610
+ }
611
+
612
+ EMSCRIPTEN_KEEPALIVE
613
+ bool isPaused() {
614
+ REQUIRE_EMULATOR_OR(false);
615
+ return g_emulator->isPaused();
616
+ }
617
+
618
+ EMSCRIPTEN_KEEPALIVE
619
+ void setPaused(bool paused) {
620
+ REQUIRE_EMULATOR();
621
+ g_emulator->setPaused(paused);
622
+ }
623
+
624
+ EMSCRIPTEN_KEEPALIVE
625
+ void stepInstruction() {
626
+ REQUIRE_EMULATOR();
627
+ g_emulator->stepInstruction();
628
+ }
629
+
630
+ EMSCRIPTEN_KEEPALIVE
631
+ uint8_t readMemory(uint16_t address) {
632
+ REQUIRE_EMULATOR_OR(0);
633
+ return g_emulator->readMemory(address);
634
+ }
635
+
636
+ EMSCRIPTEN_KEEPALIVE
637
+ uint8_t peekMemory(uint16_t address) {
638
+ REQUIRE_EMULATOR_OR(0);
639
+ return g_emulator->peekMemory(address);
640
+ }
641
+
642
+ EMSCRIPTEN_KEEPALIVE
643
+ uint8_t readMainRAM(uint16_t address) {
644
+ // Read directly from main RAM, bypassing ALTZP and other switches
645
+ // Useful for reading BASIC zero page variables which are always in main RAM
646
+ REQUIRE_EMULATOR_OR(0);
647
+ return g_emulator->getMMU().readRAM(address, false);
648
+ }
649
+
650
+ EMSCRIPTEN_KEEPALIVE
651
+ void writeMemory(uint16_t address, uint8_t value) {
652
+ REQUIRE_EMULATOR();
653
+ g_emulator->writeMemory(address, value);
654
+ }
655
+
656
+ EMSCRIPTEN_KEEPALIVE
657
+ const char *disassembleAt(uint16_t address) {
658
+ REQUIRE_EMULATOR_OR("");
659
+ return g_emulator->disassembleAt(address);
660
+ }
661
+
662
+ EMSCRIPTEN_KEEPALIVE
663
+ uint32_t getSoftSwitchState() {
664
+ REQUIRE_EMULATOR_OR(0);
665
+ return static_cast<uint32_t>(g_emulator->getSoftSwitchState() & 0xFFFFFFFF);
666
+ }
667
+
668
+ EMSCRIPTEN_KEEPALIVE
669
+ uint32_t getSoftSwitchStateHigh() {
670
+ REQUIRE_EMULATOR_OR(0);
671
+ return static_cast<uint32_t>(g_emulator->getSoftSwitchState() >> 32);
672
+ }
673
+
674
+ // Screen text extraction
675
+ EMSCRIPTEN_KEEPALIVE
676
+ int screenCodeToAscii(uint8_t code) {
677
+ return a2e::Emulator::screenCodeToAscii(code);
678
+ }
679
+
680
+ EMSCRIPTEN_KEEPALIVE
681
+ const char* readScreenText(int startRow, int startCol, int endRow, int endCol) {
682
+ REQUIRE_EMULATOR_OR("");
683
+ return g_emulator->readScreenText(startRow, startCol, endRow, endCol);
684
+ }
685
+
686
+ // Disk controller state for debugging
687
+ EMSCRIPTEN_KEEPALIVE
688
+ int getDiskTrack(int drive) {
689
+ REQUIRE_EMULATOR_OR(0);
690
+ auto &disk = g_emulator->getDisk();
691
+ if (disk.hasDisk(drive)) {
692
+ const auto *image = disk.getDiskImage(drive);
693
+ if (image) {
694
+ return image->getTrack();
695
+ }
696
+ }
697
+ return 0;
698
+ }
699
+
700
+ EMSCRIPTEN_KEEPALIVE
701
+ int getDiskPhase(int drive) {
702
+ REQUIRE_EMULATOR_OR(0);
703
+ (void)drive; // Phase states are controller-wide
704
+ return g_emulator->getDisk().getPhaseStates();
705
+ }
706
+
707
+ EMSCRIPTEN_KEEPALIVE
708
+ bool getDiskMotorOn(int drive) {
709
+ REQUIRE_EMULATOR_OR(false);
710
+ (void)drive; // Motor state is controller-wide
711
+ return g_emulator->getDisk().isMotorOn();
712
+ }
713
+
714
+ EMSCRIPTEN_KEEPALIVE
715
+ void stopDiskMotor() {
716
+ REQUIRE_EMULATOR();
717
+ g_emulator->getDisk().stopMotor();
718
+ }
719
+
720
+ EMSCRIPTEN_KEEPALIVE
721
+ bool getDiskWriteMode(int drive) {
722
+ REQUIRE_EMULATOR_OR(false);
723
+ (void)drive; // Write mode (Q7) is controller-wide
724
+ return g_emulator->getDisk().getQ7();
725
+ }
726
+
727
+ EMSCRIPTEN_KEEPALIVE
728
+ int getDiskHeadPosition(int drive) {
729
+ REQUIRE_EMULATOR_OR(0);
730
+ auto &disk = g_emulator->getDisk();
731
+ if (disk.hasDisk(drive)) {
732
+ const auto *image = disk.getDiskImage(drive);
733
+ if (image) {
734
+ return image->getQuarterTrack();
735
+ }
736
+ }
737
+ return 0;
738
+ }
739
+
740
+ EMSCRIPTEN_KEEPALIVE
741
+ int getSelectedDrive() {
742
+ REQUIRE_EMULATOR_OR(0);
743
+ return g_emulator->getDisk().getSelectedDrive();
744
+ }
745
+
746
+ EMSCRIPTEN_KEEPALIVE
747
+ bool isDiskInserted(int drive) {
748
+ REQUIRE_EMULATOR_OR(false);
749
+ return g_emulator->getDisk().hasDisk(drive);
750
+ }
751
+
752
+ EMSCRIPTEN_KEEPALIVE
753
+ uint8_t getLastDiskByte() {
754
+ REQUIRE_EMULATOR_OR(0);
755
+ return g_emulator->getDisk().getDataLatch();
756
+ }
757
+
758
+ EMSCRIPTEN_KEEPALIVE
759
+ uint8_t getTrackNibble(int drive, int track, int position) {
760
+ REQUIRE_EMULATOR_OR(0);
761
+ if (g_emulator->getDisk().hasDisk(drive)) {
762
+ const auto *image = g_emulator->getDisk().getDiskImage(drive);
763
+ if (image) {
764
+ return image->getNibbleAt(track, position);
765
+ }
766
+ }
767
+ return 0;
768
+ }
769
+
770
+ EMSCRIPTEN_KEEPALIVE
771
+ int getTrackNibbleCount(int drive, int track) {
772
+ REQUIRE_EMULATOR_OR(0);
773
+ if (g_emulator->getDisk().hasDisk(drive)) {
774
+ const auto *image = g_emulator->getDisk().getDiskImage(drive);
775
+ if (image) {
776
+ return image->getTrackNibbleCount(track);
777
+ }
778
+ }
779
+ return 0;
780
+ }
781
+
782
+ EMSCRIPTEN_KEEPALIVE
783
+ size_t getCurrentNibblePosition(int drive) {
784
+ REQUIRE_EMULATOR_OR(0);
785
+ if (g_emulator->getDisk().hasDisk(drive)) {
786
+ const auto *image = g_emulator->getDisk().getDiskImage(drive);
787
+ if (image) {
788
+ return image->getCurrentNibblePosition();
789
+ }
790
+ }
791
+ return 0;
792
+ }
793
+
794
+ EMSCRIPTEN_KEEPALIVE
795
+ bool isDiskModified(int drive) {
796
+ REQUIRE_EMULATOR_OR(false);
797
+ if (g_emulator->getDisk().hasDisk(drive)) {
798
+ const auto *image = g_emulator->getDisk().getDiskImage(drive);
799
+ if (image) {
800
+ return image->isModified();
801
+ }
802
+ }
803
+ return false;
804
+ }
805
+
806
+ EMSCRIPTEN_KEEPALIVE
807
+ const char *getDiskFilename(int drive) {
808
+ REQUIRE_EMULATOR_OR(nullptr);
809
+ return g_emulator->getDiskFilename(drive);
810
+ }
811
+
812
+ // Memory tracking for debugger heat map
813
+ EMSCRIPTEN_KEEPALIVE
814
+ void enableMemoryTracking(bool enable) {
815
+ REQUIRE_EMULATOR();
816
+ g_emulator->getMMU().enableTracking(enable);
817
+ }
818
+
819
+ EMSCRIPTEN_KEEPALIVE
820
+ void clearMemoryTracking() {
821
+ REQUIRE_EMULATOR();
822
+ g_emulator->getMMU().clearTracking();
823
+ }
824
+
825
+ EMSCRIPTEN_KEEPALIVE
826
+ void decayMemoryTracking(uint8_t amount) {
827
+ REQUIRE_EMULATOR();
828
+ g_emulator->getMMU().decayTracking(amount);
829
+ }
830
+
831
+ EMSCRIPTEN_KEEPALIVE
832
+ const uint8_t* getMemoryReadCounts() {
833
+ REQUIRE_EMULATOR_OR(nullptr);
834
+ return g_emulator->getMMU().getReadCounts();
835
+ }
836
+
837
+ EMSCRIPTEN_KEEPALIVE
838
+ const uint8_t* getMemoryWriteCounts() {
839
+ REQUIRE_EMULATOR_OR(nullptr);
840
+ return g_emulator->getMMU().getWriteCounts();
841
+ }
842
+
843
+ // Direct memory array access for heat map visualization
844
+ EMSCRIPTEN_KEEPALIVE
845
+ const uint8_t* getMainRAM() {
846
+ REQUIRE_EMULATOR_OR(nullptr);
847
+ return g_emulator->getMMU().getMainRAM();
848
+ }
849
+
850
+ EMSCRIPTEN_KEEPALIVE
851
+ const uint8_t* getAuxRAM() {
852
+ REQUIRE_EMULATOR_OR(nullptr);
853
+ return g_emulator->getMMU().getAuxRAM();
854
+ }
855
+
856
+ EMSCRIPTEN_KEEPALIVE
857
+ const uint8_t* getSystemROM() {
858
+ REQUIRE_EMULATOR_OR(nullptr);
859
+ return g_emulator->getMMU().getSystemROM();
860
+ }
861
+
862
+ // Read auxiliary memory directly (for 80-column text selection)
863
+ EMSCRIPTEN_KEEPALIVE
864
+ uint8_t peekAuxMemory(uint16_t address) {
865
+ REQUIRE_EMULATOR_OR(0);
866
+ return g_emulator->getMMU().peekAux(address);
867
+ }
868
+
869
+ // UK/US character set switch (like the physical switch on UK Apple IIe)
870
+ EMSCRIPTEN_KEEPALIVE
871
+ void setUKCharacterSet(bool uk) {
872
+ REQUIRE_EMULATOR();
873
+ g_emulator->getVideo().setUKCharacterSet(uk);
874
+ }
875
+
876
+ EMSCRIPTEN_KEEPALIVE
877
+ bool isUKCharacterSet() {
878
+ REQUIRE_EMULATOR_OR(false);
879
+ return g_emulator->getVideo().isUKCharacterSet();
880
+ }
881
+
882
+ // Monochrome display mode (bypasses NTSC artifact coloring)
883
+ EMSCRIPTEN_KEEPALIVE
884
+ void setMonochrome(bool mono) {
885
+ REQUIRE_EMULATOR();
886
+ g_emulator->getVideo().setMonochrome(mono);
887
+ }
888
+
889
+ EMSCRIPTEN_KEEPALIVE
890
+ bool isMonochrome() {
891
+ REQUIRE_EMULATOR_OR(false);
892
+ return g_emulator->getVideo().isMonochrome();
893
+ }
894
+
895
+ // ============================================================================
896
+ // State Serialization
897
+ // ============================================================================
898
+
899
+ EMSCRIPTEN_KEEPALIVE
900
+ uint8_t *exportState(size_t *size) {
901
+ if (!g_emulator) { *size = 0; return nullptr; }
902
+ return const_cast<uint8_t *>(g_emulator->exportState(size));
903
+ }
904
+
905
+ EMSCRIPTEN_KEEPALIVE
906
+ bool importState(const uint8_t *data, size_t size) {
907
+ REQUIRE_EMULATOR_OR(false);
908
+ return g_emulator->importState(data, size);
909
+ }
910
+
911
+ // ============================================================================
912
+ // Standalone Disassembler (for file browser, external tools)
913
+ // ============================================================================
914
+
915
+ // Static buffer for disassembly result
916
+ static a2e::DisasmResult g_disasmResult;
917
+
918
+ EMSCRIPTEN_KEEPALIVE
919
+ uint32_t disassembleRawData(const uint8_t *data, size_t size,
920
+ uint16_t baseAddress) {
921
+ g_disasmResult = a2e::disassembleBlock(data, size, baseAddress);
922
+ return static_cast<uint32_t>(g_disasmResult.instructions.size());
923
+ }
924
+
925
+ EMSCRIPTEN_KEEPALIVE
926
+ const a2e::DisasmInstruction *getDisasmInstructions() {
927
+ if (g_disasmResult.instructions.empty()) {
928
+ return nullptr;
929
+ }
930
+ return g_disasmResult.instructions.data();
931
+ }
932
+
933
+ EMSCRIPTEN_KEEPALIVE
934
+ int getDisasmInstructionLength(uint8_t opcode) {
935
+ return a2e::getInstructionLength(opcode);
936
+ }
937
+
938
+ EMSCRIPTEN_KEEPALIVE
939
+ uint32_t disassembleWithFlowAnalysis(const uint8_t *data, size_t size,
940
+ uint16_t baseAddress) {
941
+ g_disasmResult = a2e::disassembleWithFlowAnalysis(data, size, baseAddress);
942
+ return static_cast<uint32_t>(g_disasmResult.instructions.size());
943
+ }
944
+
945
+ EMSCRIPTEN_KEEPALIVE
946
+ uint32_t disassembleWithFlowAnalysisMultiEntry(const uint8_t *data, size_t size,
947
+ uint16_t baseAddress,
948
+ const uint16_t *entryPoints,
949
+ size_t entryCount) {
950
+ std::vector<uint16_t> entries(entryPoints, entryPoints + entryCount);
951
+ g_disasmResult = a2e::disassembleWithFlowAnalysis(data, size, baseAddress, entries);
952
+ return static_cast<uint32_t>(g_disasmResult.instructions.size());
953
+ }
954
+
955
+ // ============================================================================
956
+ // Mockingboard Debug State
957
+ // ============================================================================
958
+
959
+ EMSCRIPTEN_KEEPALIVE
960
+ bool isMockingboardEnabled() {
961
+ REQUIRE_EMULATOR_OR(false);
962
+ return g_emulator->getMockingboard().isEnabled();
963
+ }
964
+
965
+ EMSCRIPTEN_KEEPALIVE
966
+ uint8_t getMockingboardPSGRegister(int psg, int reg) {
967
+ REQUIRE_EMULATOR_OR(0);
968
+ if (reg < 0 || reg >= 16) return 0;
969
+ if (psg == 0) {
970
+ return g_emulator->getMockingboard().getPSG1().getRegister(reg);
971
+ } else if (psg == 1) {
972
+ return g_emulator->getMockingboard().getPSG2().getRegister(reg);
973
+ }
974
+ return 0;
975
+ }
976
+
977
+ // Get all 16 PSG registers as a packed structure for efficiency
978
+ // Returns pointer to static buffer with 16 bytes
979
+ static uint8_t g_psgRegisters[16];
980
+
981
+ EMSCRIPTEN_KEEPALIVE
982
+ const uint8_t* getMockingboardPSGRegisters(int psg) {
983
+ REQUIRE_EMULATOR_OR(nullptr);
984
+ const auto& psgChip = (psg == 0)
985
+ ? g_emulator->getMockingboard().getPSG1()
986
+ : g_emulator->getMockingboard().getPSG2();
987
+ for (int i = 0; i < 16; i++) {
988
+ g_psgRegisters[i] = psgChip.getRegister(i);
989
+ }
990
+ return g_psgRegisters;
991
+ }
992
+
993
+ EMSCRIPTEN_KEEPALIVE
994
+ bool getMockingboardVIAIRQ(int via) {
995
+ REQUIRE_EMULATOR_OR(false);
996
+ if (via == 0) {
997
+ return g_emulator->getMockingboard().getVIA1().isIRQActive();
998
+ } else if (via == 1) {
999
+ return g_emulator->getMockingboard().getVIA2().isIRQActive();
1000
+ }
1001
+ return false;
1002
+ }
1003
+
1004
+ // Get VIA port registers for debugging
1005
+ // reg: 0=ORA, 1=ORB, 2=DDRA, 3=DDRB
1006
+ EMSCRIPTEN_KEEPALIVE
1007
+ uint8_t getMockingboardVIAPort(int via, int reg) {
1008
+ REQUIRE_EMULATOR_OR(0);
1009
+ const auto& viaChip = (via == 0)
1010
+ ? g_emulator->getMockingboard().getVIA1()
1011
+ : g_emulator->getMockingboard().getVIA2();
1012
+ switch (reg) {
1013
+ case 0: return viaChip.getORA();
1014
+ case 1: return viaChip.getORB();
1015
+ case 2: return viaChip.getDDRA();
1016
+ case 3: return viaChip.getDDRB();
1017
+ }
1018
+ return 0;
1019
+ }
1020
+
1021
+ // Get PSG write debug info
1022
+ // info: 0=writeCount, 1=lastWriteReg, 2=lastWriteVal, 3=currentRegister
1023
+ EMSCRIPTEN_KEEPALIVE
1024
+ uint32_t getMockingboardPSGWriteInfo(int psg, int info) {
1025
+ REQUIRE_EMULATOR_OR(0);
1026
+ const auto& psgChip = (psg == 0)
1027
+ ? g_emulator->getMockingboard().getPSG1()
1028
+ : g_emulator->getMockingboard().getPSG2();
1029
+ switch (info) {
1030
+ case 0: return psgChip.getWriteCount();
1031
+ case 1: return psgChip.getLastWriteReg();
1032
+ case 2: return psgChip.getLastWriteVal();
1033
+ case 3: return psgChip.getCurrentRegister();
1034
+ }
1035
+ return 0;
1036
+ }
1037
+
1038
+ // Get VIA timer debug info
1039
+ // info: 0=T1Counter, 1=T1Latch, 2=T1Running, 3=T1Fired, 4=ACR, 5=IFR, 6=IER
1040
+ EMSCRIPTEN_KEEPALIVE
1041
+ uint32_t getMockingboardVIATimerInfo(int via, int info) {
1042
+ REQUIRE_EMULATOR_OR(0);
1043
+ const auto& viaChip = (via == 0)
1044
+ ? g_emulator->getMockingboard().getVIA1()
1045
+ : g_emulator->getMockingboard().getVIA2();
1046
+ switch (info) {
1047
+ case 0: return viaChip.getT1Counter();
1048
+ case 1: return viaChip.getT1Latch();
1049
+ case 2: return viaChip.isT1Running() ? 1 : 0;
1050
+ case 3: return viaChip.hasT1Fired() ? 1 : 0;
1051
+ case 4: return viaChip.getACR();
1052
+ case 5: return viaChip.getIFR();
1053
+ case 6: return viaChip.getIER();
1054
+ }
1055
+ return 0;
1056
+ }
1057
+
1058
+ // Enable/disable console debug logging for Mockingboard PSG writes
1059
+ EMSCRIPTEN_KEEPALIVE
1060
+ void setMockingboardDebugLogging(bool enabled) {
1061
+ REQUIRE_EMULATOR();
1062
+ g_emulator->getMockingboard().setDebugLogging(enabled);
1063
+ }
1064
+
1065
+ // Mute/unmute a specific channel on a PSG
1066
+ // psg: 0 or 1 (PSG1 or PSG2)
1067
+ // channel: 0, 1, or 2 (A, B, C)
1068
+ // muted: true to mute, false to unmute
1069
+ EMSCRIPTEN_KEEPALIVE
1070
+ void setMockingboardChannelMute(int psg, int channel, bool muted) {
1071
+ REQUIRE_EMULATOR();
1072
+ auto& psgChip = (psg == 0)
1073
+ ? g_emulator->getMockingboard().getPSG1()
1074
+ : g_emulator->getMockingboard().getPSG2();
1075
+ psgChip.setChannelMute(channel, muted);
1076
+ }
1077
+
1078
+ // Check if a channel is muted
1079
+ EMSCRIPTEN_KEEPALIVE
1080
+ bool getMockingboardChannelMute(int psg, int channel) {
1081
+ REQUIRE_EMULATOR_OR(false);
1082
+ const auto& psgChip = (psg == 0)
1083
+ ? g_emulator->getMockingboard().getPSG1()
1084
+ : g_emulator->getMockingboard().getPSG2();
1085
+ return psgChip.isChannelMuted(channel);
1086
+ }
1087
+
1088
+ // Generate waveform samples from a PSG channel for visualization
1089
+ // psg: 0 or 1 (PSG1 or PSG2)
1090
+ // channel: 0, 1, or 2 (A, B, C) - use -1 for combined output
1091
+ // buffer: float array to fill with samples
1092
+ // count: number of samples to generate
1093
+ // Returns actual number of samples generated
1094
+ EMSCRIPTEN_KEEPALIVE
1095
+ int getMockingboardWaveform(int psg, int channel, float* buffer, int count) {
1096
+ REQUIRE_EMULATOR_OR(0);
1097
+ if (!buffer || count <= 0 || count > 1024) return 0;
1098
+
1099
+ const int SAMPLE_RATE = 48000;
1100
+ auto& psgChip = (psg == 0)
1101
+ ? g_emulator->getMockingboard().getPSG1()
1102
+ : g_emulator->getMockingboard().getPSG2();
1103
+
1104
+ // Create a copy of the PSG to generate visualization samples
1105
+ // without affecting the actual audio state
1106
+ a2e::AY8910 psgCopy = psgChip;
1107
+
1108
+ if (channel >= 0 && channel < 3) {
1109
+ psgCopy.generateChannelSamples(buffer, count, SAMPLE_RATE, channel);
1110
+ } else {
1111
+ psgCopy.generateSamples(buffer, count, SAMPLE_RATE);
1112
+ }
1113
+
1114
+ return count;
1115
+ }
1116
+
1117
+ // ============================================================================
1118
+ // Mouse Input
1119
+ // ============================================================================
1120
+
1121
+ EMSCRIPTEN_KEEPALIVE
1122
+ void mouseMove(int dx, int dy) {
1123
+ REQUIRE_EMULATOR();
1124
+ g_emulator->mouseMove(dx, dy);
1125
+ }
1126
+
1127
+ EMSCRIPTEN_KEEPALIVE
1128
+ void mouseButton(bool pressed) {
1129
+ REQUIRE_EMULATOR();
1130
+ g_emulator->mouseButton(pressed);
1131
+ }
1132
+
1133
+ // ============================================================================
1134
+ // Mouse Card Debug
1135
+ // ============================================================================
1136
+
1137
+ // Returns whether a mouse card is currently installed
1138
+ EMSCRIPTEN_KEEPALIVE
1139
+ bool isMouseCardInstalled() {
1140
+ REQUIRE_EMULATOR_OR(false);
1141
+ return g_emulator->getMouseCard() != nullptr;
1142
+ }
1143
+
1144
+ // Get mouse card state field
1145
+ // field: 0=slotNum, 1=mouseX, 2=mouseY, 3=button, 4=moved, 5=buttonChanged,
1146
+ // 6=clampMinX, 7=clampMaxX, 8=clampMinY, 9=clampMaxY,
1147
+ // 10=irqActive, 11=vblPending, 12=movePending, 13=buttonPending,
1148
+ // 14=wasInVBL, 15=mode, 16=lastCommand, 17=responseState
1149
+ EMSCRIPTEN_KEEPALIVE
1150
+ int32_t getMouseCardState(int field) {
1151
+ REQUIRE_EMULATOR_OR(0);
1152
+ auto* mouse = g_emulator->getMouseCard();
1153
+ if (!mouse) return 0;
1154
+ switch (field) {
1155
+ case 0: return mouse->getSlotNumber();
1156
+ case 1: return mouse->getMouseX();
1157
+ case 2: return mouse->getMouseY();
1158
+ case 3: return mouse->getMouseButton() ? 1 : 0;
1159
+ case 4: return mouse->getMoved() ? 1 : 0;
1160
+ case 5: return mouse->getButtonChanged() ? 1 : 0;
1161
+ case 6: return mouse->getClampMinX();
1162
+ case 7: return mouse->getClampMaxX();
1163
+ case 8: return mouse->getClampMinY();
1164
+ case 9: return mouse->getClampMaxY();
1165
+ case 10: return mouse->isIRQActive() ? 1 : 0;
1166
+ case 11: return mouse->getVBLInterruptPending() ? 1 : 0;
1167
+ case 12: return mouse->getMoveInterruptPending() ? 1 : 0;
1168
+ case 13: return mouse->getButtonInterruptPending() ? 1 : 0;
1169
+ case 14: return mouse->getWasInVBL() ? 1 : 0;
1170
+ case 15: return mouse->getMode();
1171
+ case 16: return mouse->getLastCommand();
1172
+ case 17: return mouse->getResponseState();
1173
+ }
1174
+ return 0;
1175
+ }
1176
+
1177
+ // Get mouse card PIA register
1178
+ // reg: 0=DDRA, 1=DDRB, 2=ORA, 3=ORB, 4=IRA, 5=IRB, 6=CRA, 7=CRB
1179
+ EMSCRIPTEN_KEEPALIVE
1180
+ uint32_t getMouseCardPIARegister(int reg) {
1181
+ REQUIRE_EMULATOR_OR(0);
1182
+ auto* mouse = g_emulator->getMouseCard();
1183
+ if (!mouse) return 0;
1184
+ switch (reg) {
1185
+ case 0: return mouse->getDDRA();
1186
+ case 1: return mouse->getDDRB();
1187
+ case 2: return mouse->getORA();
1188
+ case 3: return mouse->getORB();
1189
+ case 4: return mouse->getIRA();
1190
+ case 5: return mouse->getIRB();
1191
+ case 6: return mouse->getCRA();
1192
+ case 7: return mouse->getCRB();
1193
+ }
1194
+ return 0;
1195
+ }
1196
+
1197
+ // ============================================================================
1198
+ // SmartPort Hard Drive
1199
+ // ============================================================================
1200
+
1201
+ EMSCRIPTEN_KEEPALIVE
1202
+ bool insertSmartPortImage(int device, uint8_t* data, int size, const char* filename) {
1203
+ REQUIRE_EMULATOR_OR(false);
1204
+ return g_emulator->insertSmartPortImage(device, data, size, filename);
1205
+ }
1206
+
1207
+ EMSCRIPTEN_KEEPALIVE
1208
+ void ejectSmartPortImage(int device) {
1209
+ REQUIRE_EMULATOR();
1210
+ g_emulator->ejectSmartPortImage(device);
1211
+ }
1212
+
1213
+ EMSCRIPTEN_KEEPALIVE
1214
+ bool isSmartPortImageInserted(int device) {
1215
+ REQUIRE_EMULATOR_OR(false);
1216
+ return g_emulator->isSmartPortImageInserted(device);
1217
+ }
1218
+
1219
+ EMSCRIPTEN_KEEPALIVE
1220
+ const char* getSmartPortImageFilename(int device) {
1221
+ REQUIRE_EMULATOR_OR(nullptr);
1222
+ return g_emulator->getSmartPortImageFilename(device);
1223
+ }
1224
+
1225
+ EMSCRIPTEN_KEEPALIVE
1226
+ bool isSmartPortImageModified(int device) {
1227
+ REQUIRE_EMULATOR_OR(false);
1228
+ return g_emulator->isSmartPortImageModified(device);
1229
+ }
1230
+
1231
+ EMSCRIPTEN_KEEPALIVE
1232
+ uint8_t* getSmartPortImageData(int device, size_t* size) {
1233
+ if (!g_emulator) { *size = 0; return nullptr; }
1234
+ return const_cast<uint8_t*>(g_emulator->exportSmartPortImageData(device, size));
1235
+ }
1236
+
1237
+ EMSCRIPTEN_KEEPALIVE
1238
+ const uint8_t* getSmartPortBlockData(int device, size_t* size) {
1239
+ if (!g_emulator) { *size = 0; return nullptr; }
1240
+ return g_emulator->getSmartPortBlockData(device, size);
1241
+ }
1242
+
1243
+ EMSCRIPTEN_KEEPALIVE
1244
+ bool isSmartPortCardInstalled() {
1245
+ REQUIRE_EMULATOR_OR(false);
1246
+ return g_emulator->isSmartPortCardInstalled();
1247
+ }
1248
+
1249
+ EMSCRIPTEN_KEEPALIVE
1250
+ bool getSmartPortActivity(int device) {
1251
+ REQUIRE_EMULATOR_OR(false);
1252
+ auto* card = g_emulator->getSmartPortCard();
1253
+ if (!card) return false;
1254
+ return card->hasActivity();
1255
+ }
1256
+
1257
+ EMSCRIPTEN_KEEPALIVE
1258
+ bool getSmartPortActivityWrite(int device) {
1259
+ REQUIRE_EMULATOR_OR(false);
1260
+ auto* card = g_emulator->getSmartPortCard();
1261
+ if (!card) return false;
1262
+ return card->isActivityWrite();
1263
+ }
1264
+
1265
+ EMSCRIPTEN_KEEPALIVE
1266
+ void clearSmartPortActivity() {
1267
+ REQUIRE_EMULATOR();
1268
+ auto* card = g_emulator->getSmartPortCard();
1269
+ if (card) card->clearActivity();
1270
+ }
1271
+
1272
+ // ============================================================================
1273
+ // Expansion Slot Management
1274
+ // ============================================================================
1275
+
1276
+ EMSCRIPTEN_KEEPALIVE
1277
+ const char* getSlotCard(int slot) {
1278
+ if (g_emulator) {
1279
+ return g_emulator->getSlotCardName(static_cast<uint8_t>(slot));
1280
+ }
1281
+ return "invalid";
1282
+ }
1283
+
1284
+ EMSCRIPTEN_KEEPALIVE
1285
+ bool setSlotCard(int slot, const char* cardId) {
1286
+ if (g_emulator) {
1287
+ return g_emulator->setSlotCard(static_cast<uint8_t>(slot), cardId);
1288
+ }
1289
+ return false;
1290
+ }
1291
+
1292
+ EMSCRIPTEN_KEEPALIVE
1293
+ bool isSlotEmpty(int slot) {
1294
+ if (g_emulator) {
1295
+ return g_emulator->isSlotEmpty(static_cast<uint8_t>(slot));
1296
+ }
1297
+ return true;
1298
+ }
1299
+
1300
+ // ============================================================================
1301
+ // Watchpoints
1302
+ // ============================================================================
1303
+
1304
+ EMSCRIPTEN_KEEPALIVE
1305
+ void addWatchpoint(uint16_t startAddr, uint16_t endAddr, uint8_t type) {
1306
+ REQUIRE_EMULATOR();
1307
+ g_emulator->addWatchpoint(startAddr, endAddr,
1308
+ static_cast<a2e::Emulator::WatchpointType>(type));
1309
+ }
1310
+
1311
+ EMSCRIPTEN_KEEPALIVE
1312
+ void removeWatchpoint(uint16_t startAddr) {
1313
+ REQUIRE_EMULATOR();
1314
+ g_emulator->removeWatchpoint(startAddr);
1315
+ }
1316
+
1317
+ EMSCRIPTEN_KEEPALIVE
1318
+ void clearWatchpoints() {
1319
+ REQUIRE_EMULATOR();
1320
+ g_emulator->clearWatchpoints();
1321
+ }
1322
+
1323
+ EMSCRIPTEN_KEEPALIVE
1324
+ bool isWatchpointHit() {
1325
+ REQUIRE_EMULATOR_OR(false);
1326
+ return g_emulator->isWatchpointHit();
1327
+ }
1328
+
1329
+ EMSCRIPTEN_KEEPALIVE
1330
+ uint16_t getWatchpointAddress() {
1331
+ REQUIRE_EMULATOR_OR(0);
1332
+ return g_emulator->getWatchpointAddress();
1333
+ }
1334
+
1335
+ EMSCRIPTEN_KEEPALIVE
1336
+ uint8_t getWatchpointValue() {
1337
+ REQUIRE_EMULATOR_OR(0);
1338
+ return g_emulator->getWatchpointValue();
1339
+ }
1340
+
1341
+ EMSCRIPTEN_KEEPALIVE
1342
+ bool isWatchpointWrite() {
1343
+ REQUIRE_EMULATOR_OR(false);
1344
+ return g_emulator->isWatchpointWrite();
1345
+ }
1346
+
1347
+ // ============================================================================
1348
+ // Instruction Trace
1349
+ // ============================================================================
1350
+
1351
+ EMSCRIPTEN_KEEPALIVE
1352
+ void setTraceEnabled(bool enabled) {
1353
+ REQUIRE_EMULATOR();
1354
+ g_emulator->setTraceEnabled(enabled);
1355
+ }
1356
+
1357
+ EMSCRIPTEN_KEEPALIVE
1358
+ void clearTrace() {
1359
+ REQUIRE_EMULATOR();
1360
+ g_emulator->clearTrace();
1361
+ }
1362
+
1363
+ EMSCRIPTEN_KEEPALIVE
1364
+ uint32_t getTraceCount() {
1365
+ REQUIRE_EMULATOR_OR(0);
1366
+ return static_cast<uint32_t>(g_emulator->getTraceCount());
1367
+ }
1368
+
1369
+ EMSCRIPTEN_KEEPALIVE
1370
+ uint32_t getTraceHead() {
1371
+ REQUIRE_EMULATOR_OR(0);
1372
+ return static_cast<uint32_t>(g_emulator->getTraceHead());
1373
+ }
1374
+
1375
+ EMSCRIPTEN_KEEPALIVE
1376
+ const void* getTraceBuffer() {
1377
+ REQUIRE_EMULATOR_OR(nullptr);
1378
+ return g_emulator->getTraceBuffer();
1379
+ }
1380
+
1381
+ EMSCRIPTEN_KEEPALIVE
1382
+ uint32_t getTraceCapacity() {
1383
+ REQUIRE_EMULATOR_OR(0);
1384
+ return static_cast<uint32_t>(g_emulator->getTraceCapacity());
1385
+ }
1386
+
1387
+ // ============================================================================
1388
+ // Cycle Profiling
1389
+ // ============================================================================
1390
+
1391
+ EMSCRIPTEN_KEEPALIVE
1392
+ void setProfileEnabled(bool enabled) {
1393
+ REQUIRE_EMULATOR();
1394
+ g_emulator->setProfileEnabled(enabled);
1395
+ }
1396
+
1397
+ EMSCRIPTEN_KEEPALIVE
1398
+ void clearProfile() {
1399
+ REQUIRE_EMULATOR();
1400
+ g_emulator->clearProfile();
1401
+ }
1402
+
1403
+ EMSCRIPTEN_KEEPALIVE
1404
+ const uint32_t* getProfileCycles() {
1405
+ REQUIRE_EMULATOR_OR(nullptr);
1406
+ return g_emulator->getProfileCycles();
1407
+ }
1408
+
1409
+ // ============================================================================
1410
+ // Beam Breakpoints
1411
+ // ============================================================================
1412
+
1413
+ EMSCRIPTEN_KEEPALIVE
1414
+ int32_t addBeamBreakpoint(int16_t scanline, int16_t hPos) {
1415
+ REQUIRE_EMULATOR_OR(-1);
1416
+ return g_emulator->addBeamBreakpoint(scanline, hPos);
1417
+ }
1418
+
1419
+ EMSCRIPTEN_KEEPALIVE
1420
+ void removeBeamBreakpoint(int32_t id) {
1421
+ REQUIRE_EMULATOR();
1422
+ g_emulator->removeBeamBreakpoint(id);
1423
+ }
1424
+
1425
+ EMSCRIPTEN_KEEPALIVE
1426
+ void enableBeamBreakpoint(int32_t id, bool enabled) {
1427
+ REQUIRE_EMULATOR();
1428
+ g_emulator->enableBeamBreakpoint(id, enabled);
1429
+ }
1430
+
1431
+ EMSCRIPTEN_KEEPALIVE
1432
+ void clearAllBeamBreakpoints() {
1433
+ REQUIRE_EMULATOR();
1434
+ g_emulator->clearAllBeamBreakpoints();
1435
+ }
1436
+
1437
+ EMSCRIPTEN_KEEPALIVE
1438
+ bool isBeamBreakpointHit() {
1439
+ REQUIRE_EMULATOR_OR(false);
1440
+ return g_emulator->isBeamBreakpointHit();
1441
+ }
1442
+
1443
+ EMSCRIPTEN_KEEPALIVE
1444
+ int32_t getBeamBreakpointHitId() {
1445
+ REQUIRE_EMULATOR_OR(-1);
1446
+ return g_emulator->getBeamBreakpointHitId();
1447
+ }
1448
+
1449
+ EMSCRIPTEN_KEEPALIVE
1450
+ int16_t getBeamBreakScanline() {
1451
+ REQUIRE_EMULATOR_OR(-1);
1452
+ return g_emulator->getBeamBreakScanline();
1453
+ }
1454
+
1455
+ EMSCRIPTEN_KEEPALIVE
1456
+ int16_t getBeamBreakHPos() {
1457
+ REQUIRE_EMULATOR_OR(-1);
1458
+ return g_emulator->getBeamBreakHPos();
1459
+ }
1460
+
1461
+ // ============================================================================
1462
+ // Condition Evaluator
1463
+ // ============================================================================
1464
+
1465
+ EMSCRIPTEN_KEEPALIVE
1466
+ bool evaluateCondition(const char* expr) {
1467
+ REQUIRE_EMULATOR_OR(false);
1468
+ return a2e::ConditionEvaluator::evaluate(expr, *g_emulator);
1469
+ }
1470
+
1471
+ EMSCRIPTEN_KEEPALIVE
1472
+ int32_t evaluateExpression(const char* expr) {
1473
+ REQUIRE_EMULATOR_OR(0);
1474
+ return a2e::ConditionEvaluator::evaluateNumeric(expr, *g_emulator);
1475
+ }
1476
+
1477
+ EMSCRIPTEN_KEEPALIVE
1478
+ const char* getConditionError() {
1479
+ return a2e::ConditionEvaluator::getLastError();
1480
+ }
1481
+
1482
+ // ============================================================================
1483
+ // Opcode Mnemonic Lookup
1484
+ // ============================================================================
1485
+
1486
+ EMSCRIPTEN_KEEPALIVE
1487
+ const char* getOpcodeMnemonic(uint8_t opcode) {
1488
+ return a2e::getMnemonic(opcode);
1489
+ }
1490
+
1491
+ EMSCRIPTEN_KEEPALIVE
1492
+ uint8_t getOpcodeAddressingMode(uint8_t opcode) {
1493
+ return static_cast<uint8_t>(a2e::getAddressingMode(opcode));
1494
+ }
1495
+
1496
+ // ============================================================================
1497
+ // Call Stack Analysis
1498
+ // ============================================================================
1499
+
1500
+ struct CallStackEntry {
1501
+ uint16_t returnAddr;
1502
+ uint16_t jsrTarget;
1503
+ };
1504
+
1505
+ static CallStackEntry g_callStack[64];
1506
+ static int g_callStackCount = 0;
1507
+
1508
+ EMSCRIPTEN_KEEPALIVE
1509
+ int getCallStack() {
1510
+ REQUIRE_EMULATOR_OR(0);
1511
+ g_callStackCount = 0;
1512
+
1513
+ uint8_t sp = g_emulator->getSP();
1514
+ int i = sp + 1;
1515
+
1516
+ while (i < 0xFF && g_callStackCount < 64) {
1517
+ uint8_t low = g_emulator->peekMemory(0x100 + i);
1518
+ uint8_t high = g_emulator->peekMemory(0x100 + i + 1);
1519
+ uint16_t retAddr = ((high << 8) | low) + 1;
1520
+
1521
+ // Validate: check if instruction before retAddr was a JSR
1522
+ if (retAddr >= 3 && retAddr <= 0xFFFF) {
1523
+ uint8_t possibleJSR = g_emulator->peekMemory(retAddr - 3);
1524
+ if (possibleJSR == 0x20) {
1525
+ // JSR target
1526
+ uint8_t jsrLo = g_emulator->peekMemory(retAddr - 2);
1527
+ uint8_t jsrHi = g_emulator->peekMemory(retAddr - 1);
1528
+ g_callStack[g_callStackCount].returnAddr = retAddr;
1529
+ g_callStack[g_callStackCount].jsrTarget = (jsrHi << 8) | jsrLo;
1530
+ g_callStackCount++;
1531
+ i += 2;
1532
+ continue;
1533
+ }
1534
+ }
1535
+ i++;
1536
+ }
1537
+
1538
+ return g_callStackCount;
1539
+ }
1540
+
1541
+ EMSCRIPTEN_KEEPALIVE
1542
+ const void* getCallStackBuffer() {
1543
+ return g_callStack;
1544
+ }
1545
+
1546
+ EMSCRIPTEN_KEEPALIVE
1547
+ bool isLikelyReturnAddress(uint16_t addr) {
1548
+ REQUIRE_EMULATOR_OR(false);
1549
+ // Check if it points to code-like regions
1550
+ return (addr >= 0x0800 && addr < 0xC000) || // Main RAM (program code)
1551
+ (addr >= 0xD000 && addr <= 0xFFFF); // ROM
1552
+ }
1553
+
1554
+ // ============================================================================
1555
+ // DOS 3.3 Filesystem
1556
+ // ============================================================================
1557
+
1558
+ static a2e::DOS33CatalogEntry g_dos33Catalog[128];
1559
+ static int g_dos33CatalogCount = 0;
1560
+ static uint8_t g_dos33FileBuffer[256 * 256]; // 64KB max file
1561
+
1562
+ EMSCRIPTEN_KEEPALIVE
1563
+ bool isDOS33Format(const uint8_t* data, int size) {
1564
+ return a2e::DOS33::isDOS33(data, static_cast<size_t>(size));
1565
+ }
1566
+
1567
+ EMSCRIPTEN_KEEPALIVE
1568
+ int getDOS33Catalog(const uint8_t* data, int size) {
1569
+ g_dos33CatalogCount = a2e::DOS33::readCatalog(data, static_cast<size_t>(size),
1570
+ g_dos33Catalog, 128);
1571
+ return g_dos33CatalogCount;
1572
+ }
1573
+
1574
+ EMSCRIPTEN_KEEPALIVE
1575
+ const void* getDOS33CatalogBuffer() {
1576
+ return g_dos33Catalog;
1577
+ }
1578
+
1579
+ EMSCRIPTEN_KEEPALIVE
1580
+ int getDOS33CatalogEntrySize() {
1581
+ return static_cast<int>(sizeof(a2e::DOS33CatalogEntry));
1582
+ }
1583
+
1584
+ EMSCRIPTEN_KEEPALIVE
1585
+ const char* getDOS33EntryFilename(int index) {
1586
+ if (index < 0 || index >= g_dos33CatalogCount) return "";
1587
+ return g_dos33Catalog[index].filename;
1588
+ }
1589
+
1590
+ EMSCRIPTEN_KEEPALIVE
1591
+ uint8_t getDOS33EntryFileType(int index) {
1592
+ if (index < 0 || index >= g_dos33CatalogCount) return 0;
1593
+ return g_dos33Catalog[index].fileType;
1594
+ }
1595
+
1596
+ EMSCRIPTEN_KEEPALIVE
1597
+ const char* getDOS33EntryFileTypeName(int index) {
1598
+ if (index < 0 || index >= g_dos33CatalogCount) return "?";
1599
+ return g_dos33Catalog[index].fileTypeName;
1600
+ }
1601
+
1602
+ EMSCRIPTEN_KEEPALIVE
1603
+ bool getDOS33EntryIsLocked(int index) {
1604
+ if (index < 0 || index >= g_dos33CatalogCount) return false;
1605
+ return g_dos33Catalog[index].isLocked;
1606
+ }
1607
+
1608
+ EMSCRIPTEN_KEEPALIVE
1609
+ int getDOS33EntrySectorCount(int index) {
1610
+ if (index < 0 || index >= g_dos33CatalogCount) return 0;
1611
+ return g_dos33Catalog[index].sectorCount;
1612
+ }
1613
+
1614
+ EMSCRIPTEN_KEEPALIVE
1615
+ int readDOS33File(const uint8_t* data, int size, int index) {
1616
+ if (index < 0 || index >= g_dos33CatalogCount) return 0;
1617
+ const auto& entry = g_dos33Catalog[index];
1618
+ return a2e::DOS33::readFile(data, static_cast<size_t>(size),
1619
+ entry.firstTrack, entry.firstSector,
1620
+ g_dos33FileBuffer, sizeof(g_dos33FileBuffer));
1621
+ }
1622
+
1623
+ EMSCRIPTEN_KEEPALIVE
1624
+ const uint8_t* getDOS33FileBuffer() {
1625
+ return g_dos33FileBuffer;
1626
+ }
1627
+
1628
+ // ============================================================================
1629
+ // ProDOS Filesystem
1630
+ // ============================================================================
1631
+
1632
+ static a2e::ProDOSCatalogEntry g_prodosCatalog[2048];
1633
+ static int g_prodosCatalogCount = 0;
1634
+ static a2e::ProDOSVolumeInfo g_prodosVolumeInfo;
1635
+ static uint8_t g_prodosFileBuffer[128 * 1024]; // 128KB max file
1636
+
1637
+ EMSCRIPTEN_KEEPALIVE
1638
+ bool isProDOSFormat(const uint8_t* data, int size) {
1639
+ return a2e::ProDOS::isProDOS(data, static_cast<size_t>(size));
1640
+ }
1641
+
1642
+ EMSCRIPTEN_KEEPALIVE
1643
+ bool getProDOSVolumeInfo(const uint8_t* data, int size) {
1644
+ return a2e::ProDOS::parseVolumeInfo(data, static_cast<size_t>(size), &g_prodosVolumeInfo);
1645
+ }
1646
+
1647
+ EMSCRIPTEN_KEEPALIVE
1648
+ const char* getProDOSVolumeName() {
1649
+ return g_prodosVolumeInfo.volumeName;
1650
+ }
1651
+
1652
+ EMSCRIPTEN_KEEPALIVE
1653
+ int getProDOSTotalBlocks() {
1654
+ return g_prodosVolumeInfo.totalBlocks;
1655
+ }
1656
+
1657
+ EMSCRIPTEN_KEEPALIVE
1658
+ int getProDOSCatalog(const uint8_t* data, int size) {
1659
+ g_prodosCatalogCount = a2e::ProDOS::readCatalog(data, static_cast<size_t>(size),
1660
+ g_prodosCatalog, 2048);
1661
+ return g_prodosCatalogCount;
1662
+ }
1663
+
1664
+ EMSCRIPTEN_KEEPALIVE
1665
+ int getProDOSDirectory(const uint8_t* data, int size, int startBlock,
1666
+ const char* pathPrefix) {
1667
+ g_prodosCatalogCount = a2e::ProDOS::readDirectory(
1668
+ data, static_cast<size_t>(size), startBlock,
1669
+ pathPrefix ? pathPrefix : "", g_prodosCatalog, 2048);
1670
+ return g_prodosCatalogCount;
1671
+ }
1672
+
1673
+ EMSCRIPTEN_KEEPALIVE
1674
+ const char* getProDOSEntryFilename(int index) {
1675
+ if (index < 0 || index >= g_prodosCatalogCount) return "";
1676
+ return g_prodosCatalog[index].filename;
1677
+ }
1678
+
1679
+ EMSCRIPTEN_KEEPALIVE
1680
+ const char* getProDOSEntryPath(int index) {
1681
+ if (index < 0 || index >= g_prodosCatalogCount) return "";
1682
+ return g_prodosCatalog[index].path;
1683
+ }
1684
+
1685
+ EMSCRIPTEN_KEEPALIVE
1686
+ uint8_t getProDOSEntryFileType(int index) {
1687
+ if (index < 0 || index >= g_prodosCatalogCount) return 0;
1688
+ return g_prodosCatalog[index].fileType;
1689
+ }
1690
+
1691
+ EMSCRIPTEN_KEEPALIVE
1692
+ const char* getProDOSEntryFileTypeName(int index) {
1693
+ if (index < 0 || index >= g_prodosCatalogCount) return "???";
1694
+ return g_prodosCatalog[index].fileTypeName;
1695
+ }
1696
+
1697
+ EMSCRIPTEN_KEEPALIVE
1698
+ uint8_t getProDOSEntryStorageType(int index) {
1699
+ if (index < 0 || index >= g_prodosCatalogCount) return 0;
1700
+ return g_prodosCatalog[index].storageType;
1701
+ }
1702
+
1703
+ EMSCRIPTEN_KEEPALIVE
1704
+ uint32_t getProDOSEntryEOF(int index) {
1705
+ if (index < 0 || index >= g_prodosCatalogCount) return 0;
1706
+ return g_prodosCatalog[index].eof;
1707
+ }
1708
+
1709
+ EMSCRIPTEN_KEEPALIVE
1710
+ uint16_t getProDOSEntryAuxType(int index) {
1711
+ if (index < 0 || index >= g_prodosCatalogCount) return 0;
1712
+ return g_prodosCatalog[index].auxType;
1713
+ }
1714
+
1715
+ EMSCRIPTEN_KEEPALIVE
1716
+ bool getProDOSEntryIsLocked(int index) {
1717
+ if (index < 0 || index >= g_prodosCatalogCount) return false;
1718
+ return g_prodosCatalog[index].isLocked;
1719
+ }
1720
+
1721
+ EMSCRIPTEN_KEEPALIVE
1722
+ uint16_t getProDOSEntryBlocksUsed(int index) {
1723
+ if (index < 0 || index >= g_prodosCatalogCount) return 0;
1724
+ return g_prodosCatalog[index].blocksUsed;
1725
+ }
1726
+
1727
+ EMSCRIPTEN_KEEPALIVE
1728
+ bool getProDOSEntryIsDirectory(int index) {
1729
+ if (index < 0 || index >= g_prodosCatalogCount) return false;
1730
+ return g_prodosCatalog[index].isDirectory;
1731
+ }
1732
+
1733
+ EMSCRIPTEN_KEEPALIVE
1734
+ uint16_t getProDOSEntryKeyPointer(int index) {
1735
+ if (index < 0 || index >= g_prodosCatalogCount) return 0;
1736
+ return g_prodosCatalog[index].keyPointer;
1737
+ }
1738
+
1739
+ EMSCRIPTEN_KEEPALIVE
1740
+ int readProDOSFile(const uint8_t* data, int size, int index) {
1741
+ if (index < 0 || index >= g_prodosCatalogCount) return 0;
1742
+ return a2e::ProDOS::readFile(data, static_cast<size_t>(size),
1743
+ &g_prodosCatalog[index],
1744
+ g_prodosFileBuffer, sizeof(g_prodosFileBuffer));
1745
+ }
1746
+
1747
+ EMSCRIPTEN_KEEPALIVE
1748
+ const uint8_t* getProDOSFileBuffer() {
1749
+ return g_prodosFileBuffer;
1750
+ }
1751
+
1752
+ EMSCRIPTEN_KEEPALIVE
1753
+ int mapProDOSFileType(uint8_t prodosType) {
1754
+ return a2e::ProDOS::mapFileTypeForViewer(prodosType);
1755
+ }
1756
+
1757
+ // ============================================================================
1758
+ // Pascal Filesystem
1759
+ // ============================================================================
1760
+
1761
+ static a2e::PascalCatalogEntry g_pascalCatalog[77];
1762
+ static int g_pascalCatalogCount = 0;
1763
+ static a2e::PascalVolumeInfo g_pascalVolumeInfo;
1764
+ static uint8_t g_pascalFileBuffer[128 * 1024]; // 128KB max file
1765
+
1766
+ EMSCRIPTEN_KEEPALIVE
1767
+ bool isPascalFormat(const uint8_t* data, int size) {
1768
+ return a2e::Pascal::isPascal(data, static_cast<size_t>(size));
1769
+ }
1770
+
1771
+ EMSCRIPTEN_KEEPALIVE
1772
+ bool getPascalVolumeInfo(const uint8_t* data, int size) {
1773
+ return a2e::Pascal::parseVolumeInfo(data, static_cast<size_t>(size), &g_pascalVolumeInfo);
1774
+ }
1775
+
1776
+ EMSCRIPTEN_KEEPALIVE
1777
+ const char* getPascalVolumeName() {
1778
+ return g_pascalVolumeInfo.volumeName;
1779
+ }
1780
+
1781
+ EMSCRIPTEN_KEEPALIVE
1782
+ int getPascalTotalBlocks() {
1783
+ return g_pascalVolumeInfo.totalBlocks;
1784
+ }
1785
+
1786
+ EMSCRIPTEN_KEEPALIVE
1787
+ int getPascalCatalog(const uint8_t* data, int size) {
1788
+ g_pascalCatalogCount = a2e::Pascal::readCatalog(data, static_cast<size_t>(size),
1789
+ g_pascalCatalog, 77);
1790
+ return g_pascalCatalogCount;
1791
+ }
1792
+
1793
+ EMSCRIPTEN_KEEPALIVE
1794
+ const char* getPascalEntryFilename(int index) {
1795
+ if (index < 0 || index >= g_pascalCatalogCount) return "";
1796
+ return g_pascalCatalog[index].filename;
1797
+ }
1798
+
1799
+ EMSCRIPTEN_KEEPALIVE
1800
+ uint8_t getPascalEntryFileType(int index) {
1801
+ if (index < 0 || index >= g_pascalCatalogCount) return 0;
1802
+ return g_pascalCatalog[index].fileType;
1803
+ }
1804
+
1805
+ EMSCRIPTEN_KEEPALIVE
1806
+ const char* getPascalEntryFileTypeName(int index) {
1807
+ if (index < 0 || index >= g_pascalCatalogCount) return "???";
1808
+ return g_pascalCatalog[index].fileTypeName;
1809
+ }
1810
+
1811
+ EMSCRIPTEN_KEEPALIVE
1812
+ uint32_t getPascalEntryFileSize(int index) {
1813
+ if (index < 0 || index >= g_pascalCatalogCount) return 0;
1814
+ return g_pascalCatalog[index].fileSize;
1815
+ }
1816
+
1817
+ EMSCRIPTEN_KEEPALIVE
1818
+ uint16_t getPascalEntryBlocksUsed(int index) {
1819
+ if (index < 0 || index >= g_pascalCatalogCount) return 0;
1820
+ return g_pascalCatalog[index].nextBlock - g_pascalCatalog[index].startBlock;
1821
+ }
1822
+
1823
+ EMSCRIPTEN_KEEPALIVE
1824
+ int readPascalFile(const uint8_t* data, int size, int index) {
1825
+ if (index < 0 || index >= g_pascalCatalogCount) return 0;
1826
+ return a2e::Pascal::readFile(data, static_cast<size_t>(size),
1827
+ &g_pascalCatalog[index],
1828
+ g_pascalFileBuffer, sizeof(g_pascalFileBuffer));
1829
+ }
1830
+
1831
+ EMSCRIPTEN_KEEPALIVE
1832
+ const uint8_t* getPascalFileBuffer() {
1833
+ return g_pascalFileBuffer;
1834
+ }
1835
+
1836
+ EMSCRIPTEN_KEEPALIVE
1837
+ int mapPascalFileType(uint8_t pascalType) {
1838
+ return a2e::Pascal::mapFileTypeForViewer(pascalType);
1839
+ }
1840
+
1841
+ // ============================================================================
1842
+ // BASIC Detokenization
1843
+ // ============================================================================
1844
+
1845
+ EMSCRIPTEN_KEEPALIVE
1846
+ const char* detokenizeApplesoft(const uint8_t* data, int size, bool hasLengthHeader) {
1847
+ return a2e::BasicDetokenizer::detokenizeApplesoft(data, size, hasLengthHeader);
1848
+ }
1849
+
1850
+ EMSCRIPTEN_KEEPALIVE
1851
+ const char* detokenizeIntegerBasic(const uint8_t* data, int size, bool hasLengthHeader) {
1852
+ return a2e::BasicDetokenizer::detokenizeIntegerBasic(data, size, hasLengthHeader);
1853
+ }
1854
+
1855
+ // ============================================================================
1856
+ // Assembler
1857
+ // ============================================================================
1858
+
1859
+ static a2e::Assembler g_assembler;
1860
+ static a2e::AsmResult g_asmResult;
1861
+
1862
+ EMSCRIPTEN_KEEPALIVE
1863
+ bool assembleSource(const char* source) {
1864
+ g_asmResult = g_assembler.assemble(source);
1865
+ return g_asmResult.success;
1866
+ }
1867
+
1868
+ EMSCRIPTEN_KEEPALIVE
1869
+ int getAsmOutputSize() {
1870
+ return static_cast<int>(g_asmResult.output.size());
1871
+ }
1872
+
1873
+ EMSCRIPTEN_KEEPALIVE
1874
+ const uint8_t* getAsmOutputBuffer() {
1875
+ if (g_asmResult.output.empty()) return nullptr;
1876
+ return g_asmResult.output.data();
1877
+ }
1878
+
1879
+ EMSCRIPTEN_KEEPALIVE
1880
+ uint16_t getAsmOrigin() {
1881
+ return g_asmResult.origin;
1882
+ }
1883
+
1884
+ EMSCRIPTEN_KEEPALIVE
1885
+ int getAsmErrorCount() {
1886
+ return static_cast<int>(g_asmResult.errors.size());
1887
+ }
1888
+
1889
+ EMSCRIPTEN_KEEPALIVE
1890
+ int getAsmErrorLine(int index) {
1891
+ if (index < 0 || index >= static_cast<int>(g_asmResult.errors.size())) return 0;
1892
+ return g_asmResult.errors[index].lineNumber;
1893
+ }
1894
+
1895
+ EMSCRIPTEN_KEEPALIVE
1896
+ const char* getAsmErrorMessage(int index) {
1897
+ if (index < 0 || index >= static_cast<int>(g_asmResult.errors.size())) return "";
1898
+ return g_asmResult.errors[index].message;
1899
+ }
1900
+
1901
+ EMSCRIPTEN_KEEPALIVE
1902
+ int getAsmSymbolCount() {
1903
+ return static_cast<int>(g_asmResult.symbols.size());
1904
+ }
1905
+
1906
+ EMSCRIPTEN_KEEPALIVE
1907
+ const char* getAsmSymbolName(int index) {
1908
+ if (index < 0 || index >= static_cast<int>(g_asmResult.symbols.size())) return "";
1909
+ return g_asmResult.symbols[index].name;
1910
+ }
1911
+
1912
+ EMSCRIPTEN_KEEPALIVE
1913
+ int32_t getAsmSymbolValue(int index) {
1914
+ if (index < 0 || index >= static_cast<int>(g_asmResult.symbols.size())) return 0;
1915
+ return g_asmResult.symbols[index].value;
1916
+ }
1917
+
1918
+ EMSCRIPTEN_KEEPALIVE
1919
+ void loadAsmIntoMemory() {
1920
+ if (!g_emulator || g_asmResult.output.empty()) return;
1921
+ uint16_t addr = g_asmResult.origin;
1922
+ for (size_t i = 0; i < g_asmResult.output.size(); i++) {
1923
+ g_emulator->writeMemory(static_cast<uint16_t>(addr + i),
1924
+ g_asmResult.output[i]);
1925
+ }
1926
+ }
1927
+
1928
+ // ============================================================================
1929
+ // BASIC Tokenizer
1930
+ // ============================================================================
1931
+
1932
+ EMSCRIPTEN_KEEPALIVE
1933
+ int loadBasicProgram(const char* source) {
1934
+ REQUIRE_EMULATOR_OR(-1);
1935
+ auto read = [](uint16_t addr) -> uint8_t { return g_emulator->readMemory(addr); };
1936
+ auto write = [](uint16_t addr, uint8_t val) { g_emulator->writeMemory(addr, val); };
1937
+ return a2e::loadBasicProgram(source, read, write);
1938
+ }
1939
+
1940
+ } // extern "C"