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.
- package/.clangd +5 -0
- package/.mcp.json +12 -0
- package/CLAUDE.md +362 -0
- package/CMakeLists.txt +774 -0
- package/LICENSE +21 -0
- package/README.md +392 -0
- package/build-wasm/generated/roms.cpp +2447 -0
- package/docker-compose.staging.yml +9 -0
- package/docs/basic-rom-disassembly.md +6663 -0
- package/docs/softswitch-comparison.md +273 -0
- package/docs/thunderclock-debug.md +89 -0
- package/examples/cube.bas +72 -0
- package/examples/hello.s +55 -0
- package/examples/scroll.s +140 -0
- package/package.json +18 -0
- package/public/assets/apple-logo-old.png +0 -0
- package/public/assets/apple-logo.png +0 -0
- package/public/assets/drive-closed-light-on.png +0 -0
- package/public/assets/drive-closed.png +0 -0
- package/public/assets/drive-open-light-on.png +0 -0
- package/public/assets/drive-open.png +0 -0
- package/public/audio-worklet.js +82 -0
- package/public/disks/Apple DOS 3.3 January 1983.dsk +0 -0
- package/public/disks/ProDOS 2.4.3.po +0 -0
- package/public/disks/h32mb.2mg +0 -0
- package/public/disks/library.json +26 -0
- package/public/docs/llms/llm-assembler.txt +90 -0
- package/public/docs/llms/llm-basic-program.txt +256 -0
- package/public/docs/llms/llm-disk-drives.txt +72 -0
- package/public/docs/llms/llm-file-explorer.txt +50 -0
- package/public/docs/llms/llm-hard-drives.txt +80 -0
- package/public/docs/llms/llm-main.txt +51 -0
- package/public/docs/llms/llm-slot-configuration.txt +66 -0
- package/public/icons/icon-192.svg +4 -0
- package/public/icons/icon-512.svg +4 -0
- package/public/index.html +661 -0
- package/public/llms.txt +49 -0
- package/public/manifest.json +29 -0
- package/public/shaders/burnin.glsl +22 -0
- package/public/shaders/crt.glsl +706 -0
- package/public/shaders/edge.glsl +109 -0
- package/public/shaders/vertex.glsl +8 -0
- package/public/sw.js +186 -0
- package/roms/341-0027.bin +0 -0
- package/roms/341-0160-A-US-UK.bin +0 -0
- package/roms/341-0160-A.bin +0 -0
- package/roms/342-0273-A-US-UK.bin +0 -0
- package/roms/342-0349-B-C0-FF.bin +0 -0
- package/roms/Apple Mouse Interface Card ROM - 342-0270-C.bin +0 -0
- package/roms/Thunderclock Plus ROM.bin +0 -0
- package/scripts/generate_roms.sh +69 -0
- package/src/bindings/wasm_interface.cpp +1940 -0
- package/src/core/assembler/assembler.cpp +1239 -0
- package/src/core/assembler/assembler.hpp +115 -0
- package/src/core/audio/audio.cpp +160 -0
- package/src/core/audio/audio.hpp +81 -0
- package/src/core/basic/basic_detokenizer.cpp +436 -0
- package/src/core/basic/basic_detokenizer.hpp +41 -0
- package/src/core/basic/basic_tokenizer.cpp +286 -0
- package/src/core/basic/basic_tokenizer.hpp +26 -0
- package/src/core/basic/basic_tokens.hpp +295 -0
- package/src/core/cards/disk2_card.cpp +568 -0
- package/src/core/cards/disk2_card.hpp +316 -0
- package/src/core/cards/expansion_card.hpp +185 -0
- package/src/core/cards/mockingboard/ay8910.cpp +616 -0
- package/src/core/cards/mockingboard/ay8910.hpp +159 -0
- package/src/core/cards/mockingboard/via6522.cpp +530 -0
- package/src/core/cards/mockingboard/via6522.hpp +163 -0
- package/src/core/cards/mockingboard_card.cpp +312 -0
- package/src/core/cards/mockingboard_card.hpp +159 -0
- package/src/core/cards/mouse_card.cpp +654 -0
- package/src/core/cards/mouse_card.hpp +190 -0
- package/src/core/cards/smartport/block_device.cpp +202 -0
- package/src/core/cards/smartport/block_device.hpp +60 -0
- package/src/core/cards/smartport/smartport_card.cpp +603 -0
- package/src/core/cards/smartport/smartport_card.hpp +120 -0
- package/src/core/cards/thunderclock_card.cpp +237 -0
- package/src/core/cards/thunderclock_card.hpp +122 -0
- package/src/core/cpu/cpu6502.cpp +1609 -0
- package/src/core/cpu/cpu6502.hpp +203 -0
- package/src/core/debug/condition_evaluator.cpp +470 -0
- package/src/core/debug/condition_evaluator.hpp +87 -0
- package/src/core/disassembler/disassembler.cpp +552 -0
- package/src/core/disassembler/disassembler.hpp +171 -0
- package/src/core/disk-image/disk_image.hpp +267 -0
- package/src/core/disk-image/dsk_disk_image.cpp +827 -0
- package/src/core/disk-image/dsk_disk_image.hpp +204 -0
- package/src/core/disk-image/gcr_encoding.cpp +147 -0
- package/src/core/disk-image/gcr_encoding.hpp +78 -0
- package/src/core/disk-image/woz_disk_image.cpp +1049 -0
- package/src/core/disk-image/woz_disk_image.hpp +343 -0
- package/src/core/emulator.cpp +2126 -0
- package/src/core/emulator.hpp +434 -0
- package/src/core/filesystem/dos33.cpp +178 -0
- package/src/core/filesystem/dos33.hpp +66 -0
- package/src/core/filesystem/pascal.cpp +262 -0
- package/src/core/filesystem/pascal.hpp +87 -0
- package/src/core/filesystem/prodos.cpp +369 -0
- package/src/core/filesystem/prodos.hpp +119 -0
- package/src/core/input/keyboard.cpp +227 -0
- package/src/core/input/keyboard.hpp +111 -0
- package/src/core/mmu/mmu.cpp +1387 -0
- package/src/core/mmu/mmu.hpp +236 -0
- package/src/core/types.hpp +196 -0
- package/src/core/video/video.cpp +680 -0
- package/src/core/video/video.hpp +156 -0
- package/src/css/assembler-editor.css +1617 -0
- package/src/css/base.css +470 -0
- package/src/css/basic-debugger.css +791 -0
- package/src/css/basic-editor.css +792 -0
- package/src/css/controls.css +783 -0
- package/src/css/cpu-debugger.css +1413 -0
- package/src/css/debug-base.css +160 -0
- package/src/css/debug-windows.css +6455 -0
- package/src/css/disk-drives.css +406 -0
- package/src/css/documentation.css +392 -0
- package/src/css/file-explorer.css +867 -0
- package/src/css/hard-drive.css +180 -0
- package/src/css/layout.css +217 -0
- package/src/css/memory-windows.css +798 -0
- package/src/css/modals.css +510 -0
- package/src/css/monitor.css +425 -0
- package/src/css/release-notes.css +101 -0
- package/src/css/responsive.css +400 -0
- package/src/css/rule-builder.css +340 -0
- package/src/css/save-states.css +201 -0
- package/src/css/settings-windows.css +1231 -0
- package/src/css/window-switcher.css +150 -0
- package/src/js/agent/agent-manager.js +643 -0
- package/src/js/agent/agent-tools.js +293 -0
- package/src/js/agent/agent-version-tools.js +131 -0
- package/src/js/agent/assembler-tools.js +357 -0
- package/src/js/agent/basic-program-tools.js +894 -0
- package/src/js/agent/disk-tools.js +417 -0
- package/src/js/agent/file-explorer-tools.js +269 -0
- package/src/js/agent/index.js +13 -0
- package/src/js/agent/main-tools.js +222 -0
- package/src/js/agent/slot-tools.js +303 -0
- package/src/js/agent/smartport-tools.js +257 -0
- package/src/js/agent/window-tools.js +80 -0
- package/src/js/audio/audio-driver.js +417 -0
- package/src/js/audio/audio-worklet.js +85 -0
- package/src/js/audio/index.js +8 -0
- package/src/js/config/default-layout.js +34 -0
- package/src/js/config/version.js +8 -0
- package/src/js/data/apple2-rom-routines.js +577 -0
- package/src/js/debug/assembler-editor-window.js +2993 -0
- package/src/js/debug/basic-breakpoint-manager.js +529 -0
- package/src/js/debug/basic-program-parser.js +436 -0
- package/src/js/debug/basic-program-window.js +2594 -0
- package/src/js/debug/basic-variable-inspector.js +447 -0
- package/src/js/debug/breakpoint-manager.js +472 -0
- package/src/js/debug/cpu-debugger-window.js +2396 -0
- package/src/js/debug/index.js +22 -0
- package/src/js/debug/label-manager.js +238 -0
- package/src/js/debug/memory-browser-window.js +416 -0
- package/src/js/debug/memory-heat-map-window.js +481 -0
- package/src/js/debug/memory-map-window.js +206 -0
- package/src/js/debug/mockingboard-window.js +882 -0
- package/src/js/debug/mouse-card-window.js +355 -0
- package/src/js/debug/rule-builder-window.js +648 -0
- package/src/js/debug/soft-switch-window.js +458 -0
- package/src/js/debug/stack-viewer-window.js +221 -0
- package/src/js/debug/symbols.js +416 -0
- package/src/js/debug/trace-panel.js +291 -0
- package/src/js/debug/zero-page-watch-window.js +297 -0
- package/src/js/disk-manager/disk-drives-window.js +212 -0
- package/src/js/disk-manager/disk-operations.js +284 -0
- package/src/js/disk-manager/disk-persistence.js +301 -0
- package/src/js/disk-manager/disk-surface-renderer.js +388 -0
- package/src/js/disk-manager/drive-sounds.js +139 -0
- package/src/js/disk-manager/hard-drive-manager.js +481 -0
- package/src/js/disk-manager/hard-drive-persistence.js +187 -0
- package/src/js/disk-manager/hard-drive-window.js +57 -0
- package/src/js/disk-manager/index.js +890 -0
- package/src/js/display/display-settings-window.js +383 -0
- package/src/js/display/index.js +10 -0
- package/src/js/display/screen-window.js +342 -0
- package/src/js/display/webgl-renderer.js +705 -0
- package/src/js/file-explorer/disassembler.js +574 -0
- package/src/js/file-explorer/dos33.js +266 -0
- package/src/js/file-explorer/file-viewer.js +359 -0
- package/src/js/file-explorer/index.js +1261 -0
- package/src/js/file-explorer/prodos.js +549 -0
- package/src/js/file-explorer/utils.js +67 -0
- package/src/js/help/documentation-window.js +1096 -0
- package/src/js/help/index.js +10 -0
- package/src/js/help/release-notes-window.js +85 -0
- package/src/js/help/release-notes.js +612 -0
- package/src/js/input/gamepad-handler.js +176 -0
- package/src/js/input/index.js +12 -0
- package/src/js/input/input-handler.js +396 -0
- package/src/js/input/joystick-window.js +404 -0
- package/src/js/input/mouse-handler.js +99 -0
- package/src/js/input/text-selection.js +462 -0
- package/src/js/main.js +653 -0
- package/src/js/state/index.js +15 -0
- package/src/js/state/save-states-window.js +393 -0
- package/src/js/state/state-manager.js +409 -0
- package/src/js/state/state-persistence.js +218 -0
- package/src/js/ui/confirm.js +43 -0
- package/src/js/ui/disk-drive-positioner.js +347 -0
- package/src/js/ui/reminder-controller.js +129 -0
- package/src/js/ui/slot-configuration-window.js +560 -0
- package/src/js/ui/theme-manager.js +61 -0
- package/src/js/ui/toast.js +44 -0
- package/src/js/ui/ui-controller.js +897 -0
- package/src/js/ui/window-switcher.js +275 -0
- package/src/js/utils/basic-autocomplete.js +832 -0
- package/src/js/utils/basic-highlighting.js +473 -0
- package/src/js/utils/basic-tokenizer.js +153 -0
- package/src/js/utils/basic-tokens.js +117 -0
- package/src/js/utils/constants.js +28 -0
- package/src/js/utils/indexeddb-helper.js +225 -0
- package/src/js/utils/merlin-editor-support.js +905 -0
- package/src/js/utils/merlin-highlighting.js +551 -0
- package/src/js/utils/storage.js +125 -0
- package/src/js/utils/string-utils.js +19 -0
- package/src/js/utils/wasm-memory.js +54 -0
- package/src/js/windows/base-window.js +690 -0
- package/src/js/windows/index.js +9 -0
- package/src/js/windows/window-manager.js +375 -0
- package/tests/catch2/catch.hpp +17976 -0
- package/tests/common/basic_program_builder.cpp +119 -0
- package/tests/common/basic_program_builder.hpp +209 -0
- package/tests/common/disk_image_builder.cpp +444 -0
- package/tests/common/disk_image_builder.hpp +141 -0
- package/tests/common/test_helpers.hpp +118 -0
- package/tests/gcr/gcr-test.cpp +142 -0
- package/tests/integration/check-rom.js +70 -0
- package/tests/integration/compare-boot.js +239 -0
- package/tests/integration/crash-trace.js +102 -0
- package/tests/integration/disk-boot-test.js +264 -0
- package/tests/integration/memory-crash.js +108 -0
- package/tests/integration/nibble-read-test.js +249 -0
- package/tests/integration/phase-test.js +159 -0
- package/tests/integration/test_emulator.cpp +291 -0
- package/tests/integration/test_emulator_basic.cpp +91 -0
- package/tests/integration/test_emulator_debug.cpp +344 -0
- package/tests/integration/test_emulator_disk.cpp +153 -0
- package/tests/integration/test_emulator_state.cpp +163 -0
- package/tests/klaus/6502_functional_test.bin +0 -0
- package/tests/klaus/65C02_extended_opcodes_test.bin +0 -0
- package/tests/klaus/klaus_6502_test.cpp +184 -0
- package/tests/klaus/klaus_65c02_test.cpp +197 -0
- package/tests/thunderclock/thunderclock_mmu_test.cpp +304 -0
- package/tests/thunderclock/thunderclock_test.cpp +550 -0
- package/tests/unit/test_assembler.cpp +521 -0
- package/tests/unit/test_audio.cpp +196 -0
- package/tests/unit/test_ay8910.cpp +311 -0
- package/tests/unit/test_basic_detokenizer.cpp +265 -0
- package/tests/unit/test_basic_tokenizer.cpp +382 -0
- package/tests/unit/test_block_device.cpp +259 -0
- package/tests/unit/test_condition_evaluator.cpp +219 -0
- package/tests/unit/test_cpu6502.cpp +1301 -0
- package/tests/unit/test_cpu_addressing.cpp +361 -0
- package/tests/unit/test_cpu_cycle_counts.cpp +409 -0
- package/tests/unit/test_cpu_decimal.cpp +166 -0
- package/tests/unit/test_cpu_interrupts.cpp +285 -0
- package/tests/unit/test_disassembler.cpp +323 -0
- package/tests/unit/test_disk2_card.cpp +330 -0
- package/tests/unit/test_dos33.cpp +273 -0
- package/tests/unit/test_dsk_disk_image.cpp +315 -0
- package/tests/unit/test_expansion_card.cpp +178 -0
- package/tests/unit/test_gcr_encoding.cpp +232 -0
- package/tests/unit/test_keyboard.cpp +262 -0
- package/tests/unit/test_mmu.cpp +555 -0
- package/tests/unit/test_mmu_slots.cpp +323 -0
- package/tests/unit/test_mockingboard.cpp +352 -0
- package/tests/unit/test_mouse_card.cpp +386 -0
- package/tests/unit/test_pascal.cpp +248 -0
- package/tests/unit/test_prodos.cpp +259 -0
- package/tests/unit/test_smartport_card.cpp +321 -0
- package/tests/unit/test_thunderclock.cpp +354 -0
- package/tests/unit/test_via6522.cpp +323 -0
- package/tests/unit/test_video.cpp +319 -0
- package/tests/unit/test_woz_disk_image.cpp +257 -0
- package/vite.config.js +96 -0
- package/wiki/AI-Agent.md +372 -0
- package/wiki/Architecture-Overview.md +303 -0
- package/wiki/Audio-System.md +449 -0
- package/wiki/CPU-Emulation.md +477 -0
- package/wiki/Debugger.md +516 -0
- package/wiki/Disk-Drives.md +161 -0
- package/wiki/Disk-System-Internals.md +547 -0
- package/wiki/Display-Settings.md +88 -0
- package/wiki/Expansion-Slots.md +187 -0
- package/wiki/File-Explorer.md +259 -0
- package/wiki/Getting-Started.md +156 -0
- package/wiki/Home.md +69 -0
- package/wiki/Input-Devices.md +183 -0
- package/wiki/Keyboard-Shortcuts.md +158 -0
- package/wiki/Memory-System.md +364 -0
- package/wiki/Save-States.md +172 -0
- 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"
|