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,222 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* main-tools.js - Main emulator control tools
|
|
3
|
+
*
|
|
4
|
+
* Written by
|
|
5
|
+
* Shawn Bullock <shawn@agenticexpert.ai>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Parse address or length value from hex ($xxxx) or decimal format
|
|
10
|
+
* @param {string|number} value - Value to parse
|
|
11
|
+
* @param {string} paramName - Parameter name for error messages
|
|
12
|
+
* @returns {number} Parsed integer value
|
|
13
|
+
*/
|
|
14
|
+
function parseHexOrDecimal(value, paramName) {
|
|
15
|
+
if (value === undefined || value === null) {
|
|
16
|
+
throw new Error(`${paramName} parameter is required`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// If already a number, use it directly
|
|
20
|
+
if (typeof value === "number") {
|
|
21
|
+
return Math.floor(value);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// If string, check for hex prefix
|
|
25
|
+
if (typeof value === "string") {
|
|
26
|
+
const trimmed = value.trim();
|
|
27
|
+
|
|
28
|
+
// Hex format: $xxxx or 0xXXXX
|
|
29
|
+
if (trimmed.startsWith("$")) {
|
|
30
|
+
const parsed = parseInt(trimmed.substring(1), 16);
|
|
31
|
+
if (isNaN(parsed)) {
|
|
32
|
+
throw new Error(`Invalid hex ${paramName}: ${value}`);
|
|
33
|
+
}
|
|
34
|
+
return parsed;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (trimmed.toLowerCase().startsWith("0x")) {
|
|
38
|
+
const parsed = parseInt(trimmed, 16);
|
|
39
|
+
if (isNaN(parsed)) {
|
|
40
|
+
throw new Error(`Invalid hex ${paramName}: ${value}`);
|
|
41
|
+
}
|
|
42
|
+
return parsed;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Decimal format
|
|
46
|
+
const parsed = parseInt(trimmed, 10);
|
|
47
|
+
if (isNaN(parsed)) {
|
|
48
|
+
throw new Error(`Invalid decimal ${paramName}: ${value}`);
|
|
49
|
+
}
|
|
50
|
+
return parsed;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
throw new Error(`${paramName} must be a number or string`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const mainTools = {
|
|
57
|
+
/**
|
|
58
|
+
* Power control
|
|
59
|
+
*/
|
|
60
|
+
emulatorPower: async (args) => {
|
|
61
|
+
const { action = "toggle" } = args;
|
|
62
|
+
|
|
63
|
+
const emulator = window.emulator;
|
|
64
|
+
if (!emulator) {
|
|
65
|
+
throw new Error("Emulator not available");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (action === "on" && !emulator.running) {
|
|
69
|
+
emulator.start();
|
|
70
|
+
} else if (action === "off" && emulator.running) {
|
|
71
|
+
emulator.stop();
|
|
72
|
+
} else if (action === "toggle") {
|
|
73
|
+
if (emulator.running) {
|
|
74
|
+
emulator.stop();
|
|
75
|
+
} else {
|
|
76
|
+
emulator.start();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
success: true,
|
|
82
|
+
running: emulator.running,
|
|
83
|
+
message: `Emulator is now ${emulator.running ? "running" : "stopped"}`,
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Ctrl-Reset (warm reset)
|
|
89
|
+
*/
|
|
90
|
+
emulatorCtrlReset: async (args) => {
|
|
91
|
+
const wasmModule = window.emulator?.wasmModule;
|
|
92
|
+
if (!wasmModule) {
|
|
93
|
+
throw new Error("Emulator not available");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
wasmModule._warmReset();
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
success: true,
|
|
100
|
+
message: "Ctrl-Reset executed (warm reset)",
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Reboot (cold reset)
|
|
106
|
+
*/
|
|
107
|
+
emulatorReboot: async (args) => {
|
|
108
|
+
const wasmModule = window.emulator?.wasmModule;
|
|
109
|
+
if (!wasmModule) {
|
|
110
|
+
throw new Error("Emulator not available");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
wasmModule._reset();
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
success: true,
|
|
117
|
+
message: "Reboot executed (cold reset)",
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Load binary data into memory at a specific address
|
|
123
|
+
*/
|
|
124
|
+
directLoadBinaryAt: async (args) => {
|
|
125
|
+
const { address, contentBase64 } = args;
|
|
126
|
+
|
|
127
|
+
if (!contentBase64) {
|
|
128
|
+
throw new Error("contentBase64 parameter is required");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const wasmModule = window.emulator?.wasmModule;
|
|
132
|
+
if (!wasmModule) {
|
|
133
|
+
throw new Error("WASM module not available");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Parse address (supports $xxxx hex or decimal)
|
|
137
|
+
const addr = parseHexOrDecimal(address, "address");
|
|
138
|
+
|
|
139
|
+
// Decode base64 to binary
|
|
140
|
+
const binaryString = atob(contentBase64);
|
|
141
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
142
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
143
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Pause emulator while writing to ensure clean state
|
|
147
|
+
const wasPaused = wasmModule._isPaused();
|
|
148
|
+
wasmModule._setPaused(true);
|
|
149
|
+
|
|
150
|
+
// Write bytes using writeMemory (like assembler does)
|
|
151
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
152
|
+
wasmModule._writeMemory((addr + i) & 0xffff, bytes[i]);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Restore paused state
|
|
156
|
+
wasmModule._setPaused(wasPaused);
|
|
157
|
+
|
|
158
|
+
const addrHex = "$" + addr.toString(16).toUpperCase().padStart(4, "0");
|
|
159
|
+
const endAddr = (addr + bytes.length - 1) & 0xffff;
|
|
160
|
+
const endHex = "$" + endAddr.toString(16).toUpperCase().padStart(4, "0");
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
success: true,
|
|
164
|
+
address: addr,
|
|
165
|
+
addressHex: addrHex,
|
|
166
|
+
size: bytes.length,
|
|
167
|
+
endAddress: endAddr,
|
|
168
|
+
endAddressHex: endHex,
|
|
169
|
+
message: `Loaded ${bytes.length} bytes to ${addrHex}-${endHex}`,
|
|
170
|
+
};
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Save binary data from memory range to base64
|
|
175
|
+
*/
|
|
176
|
+
directSaveBinaryRangeTo: async (args) => {
|
|
177
|
+
const { address, length } = args;
|
|
178
|
+
|
|
179
|
+
const wasmModule = window.emulator?.wasmModule;
|
|
180
|
+
if (!wasmModule) {
|
|
181
|
+
throw new Error("WASM module not available");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Parse address and length (supports $xxxx hex or decimal)
|
|
185
|
+
const addr = parseHexOrDecimal(address, "address");
|
|
186
|
+
const len = parseHexOrDecimal(length, "length");
|
|
187
|
+
|
|
188
|
+
if (len <= 0) {
|
|
189
|
+
throw new Error("length must be > 0");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Read bytes using peekMemory (no side effects)
|
|
193
|
+
const bytes = new Uint8Array(len);
|
|
194
|
+
for (let i = 0; i < len; i++) {
|
|
195
|
+
bytes[i] = wasmModule._peekMemory((addr + i) & 0xffff);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Encode to base64
|
|
199
|
+
let binaryString = "";
|
|
200
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
201
|
+
binaryString += String.fromCharCode(bytes[i]);
|
|
202
|
+
}
|
|
203
|
+
const contentBase64 = btoa(binaryString);
|
|
204
|
+
|
|
205
|
+
const addrHex = "$" + addr.toString(16).toUpperCase().padStart(4, "0");
|
|
206
|
+
const lengthHex = "$" + len.toString(16).toUpperCase().padStart(4, "0");
|
|
207
|
+
const endAddr = (addr + len - 1) & 0xffff;
|
|
208
|
+
const endHex = "$" + endAddr.toString(16).toUpperCase().padStart(4, "0");
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
success: true,
|
|
212
|
+
address: addr,
|
|
213
|
+
addressHex: addrHex,
|
|
214
|
+
length: len,
|
|
215
|
+
lengthHex: lengthHex,
|
|
216
|
+
endAddress: endAddr,
|
|
217
|
+
endAddressHex: endHex,
|
|
218
|
+
contentBase64: contentBase64,
|
|
219
|
+
message: `Read ${len} bytes (${lengthHex}) from ${addrHex}-${endHex}`,
|
|
220
|
+
};
|
|
221
|
+
},
|
|
222
|
+
};
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* slot-tools.js - Expansion slot management tools
|
|
3
|
+
*
|
|
4
|
+
* Written by
|
|
5
|
+
* Shawn Bullock <shawn@agenticexpert.ai>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Slot metadata (matches slot-configuration-window.js)
|
|
9
|
+
const SLOT_CONFIG = [
|
|
10
|
+
{ slot: 1, compatible: [], fixed: false },
|
|
11
|
+
{ slot: 2, compatible: ["smartport"], fixed: false },
|
|
12
|
+
{ slot: 3, compatible: [], fixed: true },
|
|
13
|
+
{ slot: 4, compatible: ["mockingboard", "mouse", "smartport"], fixed: false },
|
|
14
|
+
{ slot: 5, compatible: ["thunderclock", "smartport"], fixed: false },
|
|
15
|
+
{ slot: 6, compatible: ["disk2"], fixed: false },
|
|
16
|
+
{ slot: 7, compatible: ["thunderclock", "smartport"], fixed: false },
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const ALL_CARDS = ["disk2", "mockingboard", "thunderclock", "mouse", "smartport"];
|
|
20
|
+
|
|
21
|
+
function getWasm() {
|
|
22
|
+
const wasmModule = window.emulator?.wasmModule;
|
|
23
|
+
if (!wasmModule) {
|
|
24
|
+
throw new Error("WASM module not available");
|
|
25
|
+
}
|
|
26
|
+
return wasmModule;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getSlotCard(wasmModule, slot) {
|
|
30
|
+
const ptr = wasmModule._getSlotCard(slot);
|
|
31
|
+
return ptr ? wasmModule.UTF8ToString(ptr) : "empty";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getSlotConfig(slot) {
|
|
35
|
+
return SLOT_CONFIG.find(cfg => cfg.slot === slot);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function setSlotCardWasm(wasmModule, slot, cardId) {
|
|
39
|
+
const cardIdPtr = wasmModule._malloc(cardId.length + 1);
|
|
40
|
+
wasmModule.stringToUTF8(cardId, cardIdPtr, cardId.length + 1);
|
|
41
|
+
wasmModule._setSlotCard(slot, cardIdPtr);
|
|
42
|
+
wasmModule._free(cardIdPtr);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function persistSlotConfig(wasmModule) {
|
|
46
|
+
try {
|
|
47
|
+
const config = {};
|
|
48
|
+
for (const cfg of SLOT_CONFIG) {
|
|
49
|
+
if (!cfg.fixed) {
|
|
50
|
+
config[cfg.slot] = getSlotCard(wasmModule, cfg.slot);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
localStorage.setItem("a2e-slot-config", JSON.stringify(config));
|
|
54
|
+
} catch (e) {
|
|
55
|
+
// Non-fatal
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Refresh the slot configuration window UI if it exists
|
|
59
|
+
const slotWindow = window.emulator?.windowManager?.getWindow("slot-configuration");
|
|
60
|
+
if (slotWindow) {
|
|
61
|
+
slotWindow.loadSettings();
|
|
62
|
+
slotWindow.updateDisabledOptions();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const slotTools = {
|
|
67
|
+
/**
|
|
68
|
+
* List all expansion slots with current card and available options
|
|
69
|
+
*/
|
|
70
|
+
slotsListAll: async () => {
|
|
71
|
+
const wasmModule = getWasm();
|
|
72
|
+
|
|
73
|
+
// Get current card in every slot
|
|
74
|
+
const slotState = SLOT_CONFIG.map(cfg => ({
|
|
75
|
+
slot: cfg.slot,
|
|
76
|
+
currentCard: cfg.fixed ? "80col" : getSlotCard(wasmModule, cfg.slot),
|
|
77
|
+
fixed: cfg.fixed,
|
|
78
|
+
compatible: cfg.compatible,
|
|
79
|
+
}));
|
|
80
|
+
|
|
81
|
+
// Collect all cards currently installed
|
|
82
|
+
const installedCards = {};
|
|
83
|
+
for (const s of slotState) {
|
|
84
|
+
if (!s.fixed && s.currentCard !== "empty") {
|
|
85
|
+
installedCards[s.currentCard] = s.slot;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// For each slot, determine which compatible cards are available to install
|
|
90
|
+
const slots = slotState.map(s => {
|
|
91
|
+
const available = s.fixed
|
|
92
|
+
? []
|
|
93
|
+
: s.compatible.filter(card => {
|
|
94
|
+
return !installedCards[card] || installedCards[card] === s.slot;
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
slot: s.slot,
|
|
99
|
+
currentCard: s.currentCard,
|
|
100
|
+
fixed: s.fixed,
|
|
101
|
+
compatible: s.compatible,
|
|
102
|
+
available,
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const uninstalled = ALL_CARDS.filter(card => !installedCards[card]);
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
success: true,
|
|
110
|
+
slots,
|
|
111
|
+
installedCards,
|
|
112
|
+
uninstalledCards: uninstalled,
|
|
113
|
+
message: `${Object.keys(installedCards).length} card(s) installed, ${uninstalled.length} available`,
|
|
114
|
+
};
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Install a card into a slot (auto-removes from previous slot if needed)
|
|
119
|
+
*/
|
|
120
|
+
slotsInstallCard: async (args) => {
|
|
121
|
+
const { slot, card } = args;
|
|
122
|
+
|
|
123
|
+
if (slot === undefined || slot === null) {
|
|
124
|
+
throw new Error("slot parameter is required");
|
|
125
|
+
}
|
|
126
|
+
if (!card) {
|
|
127
|
+
throw new Error("card parameter is required");
|
|
128
|
+
}
|
|
129
|
+
if (!ALL_CARDS.includes(card)) {
|
|
130
|
+
throw new Error(`Unknown card: ${card}. Valid cards: ${ALL_CARDS.join(", ")}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const cfg = getSlotConfig(slot);
|
|
134
|
+
if (!cfg) {
|
|
135
|
+
throw new Error(`Invalid slot: ${slot}. Valid slots: 1-7`);
|
|
136
|
+
}
|
|
137
|
+
if (cfg.fixed) {
|
|
138
|
+
throw new Error(`Slot ${slot} is fixed and cannot be changed`);
|
|
139
|
+
}
|
|
140
|
+
if (!cfg.compatible.includes(card)) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
`${card} is not compatible with slot ${slot}. Compatible: ${cfg.compatible.join(", ") || "none"}`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const wasmModule = getWasm();
|
|
147
|
+
const currentCard = getSlotCard(wasmModule, slot);
|
|
148
|
+
|
|
149
|
+
// Already installed in this slot
|
|
150
|
+
if (currentCard === card) {
|
|
151
|
+
return {
|
|
152
|
+
success: true,
|
|
153
|
+
slot,
|
|
154
|
+
card,
|
|
155
|
+
message: `${card} is already installed in slot ${slot}`,
|
|
156
|
+
reset: false,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// If the card is installed in another slot, remove it from there
|
|
161
|
+
let movedFrom = null;
|
|
162
|
+
for (const c of SLOT_CONFIG) {
|
|
163
|
+
if (!c.fixed && c.slot !== slot && getSlotCard(wasmModule, c.slot) === card) {
|
|
164
|
+
setSlotCardWasm(wasmModule, c.slot, "empty");
|
|
165
|
+
movedFrom = c.slot;
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Install the card
|
|
171
|
+
setSlotCardWasm(wasmModule, slot, card);
|
|
172
|
+
persistSlotConfig(wasmModule);
|
|
173
|
+
wasmModule._reset();
|
|
174
|
+
|
|
175
|
+
const displaced = currentCard !== "empty" ? currentCard : null;
|
|
176
|
+
let message = `${card} installed in slot ${slot}. Emulator reset.`;
|
|
177
|
+
if (movedFrom) {
|
|
178
|
+
message = `${card} moved from slot ${movedFrom} to slot ${slot}. Emulator reset.`;
|
|
179
|
+
}
|
|
180
|
+
if (displaced && displaced !== card) {
|
|
181
|
+
message += ` Displaced ${displaced}.`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
success: true,
|
|
186
|
+
slot,
|
|
187
|
+
card,
|
|
188
|
+
displaced,
|
|
189
|
+
movedFrom,
|
|
190
|
+
message,
|
|
191
|
+
reset: true,
|
|
192
|
+
};
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Remove a card from a slot (set to empty)
|
|
197
|
+
*/
|
|
198
|
+
slotsRemoveCard: async (args) => {
|
|
199
|
+
const { slot } = args;
|
|
200
|
+
|
|
201
|
+
if (slot === undefined || slot === null) {
|
|
202
|
+
throw new Error("slot parameter is required");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const cfg = getSlotConfig(slot);
|
|
206
|
+
if (!cfg) {
|
|
207
|
+
throw new Error(`Invalid slot: ${slot}. Valid slots: 1-7`);
|
|
208
|
+
}
|
|
209
|
+
if (cfg.fixed) {
|
|
210
|
+
throw new Error(`Slot ${slot} is fixed and cannot be changed`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const wasmModule = getWasm();
|
|
214
|
+
const currentCard = getSlotCard(wasmModule, slot);
|
|
215
|
+
|
|
216
|
+
if (currentCard === "empty") {
|
|
217
|
+
return {
|
|
218
|
+
success: true,
|
|
219
|
+
slot,
|
|
220
|
+
message: `Slot ${slot} is already empty`,
|
|
221
|
+
reset: false,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
setSlotCardWasm(wasmModule, slot, "empty");
|
|
226
|
+
persistSlotConfig(wasmModule);
|
|
227
|
+
wasmModule._reset();
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
success: true,
|
|
231
|
+
slot,
|
|
232
|
+
removed: currentCard,
|
|
233
|
+
message: `Removed ${currentCard} from slot ${slot}. Emulator reset.`,
|
|
234
|
+
reset: true,
|
|
235
|
+
};
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Move a card from one slot to another
|
|
240
|
+
*/
|
|
241
|
+
slotsMoveCard: async (args) => {
|
|
242
|
+
const { fromSlot, toSlot } = args;
|
|
243
|
+
|
|
244
|
+
if (fromSlot === undefined || fromSlot === null) {
|
|
245
|
+
throw new Error("fromSlot parameter is required");
|
|
246
|
+
}
|
|
247
|
+
if (toSlot === undefined || toSlot === null) {
|
|
248
|
+
throw new Error("toSlot parameter is required");
|
|
249
|
+
}
|
|
250
|
+
if (fromSlot === toSlot) {
|
|
251
|
+
throw new Error("fromSlot and toSlot must be different");
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const fromCfg = getSlotConfig(fromSlot);
|
|
255
|
+
const toCfg = getSlotConfig(toSlot);
|
|
256
|
+
if (!fromCfg) {
|
|
257
|
+
throw new Error(`Invalid fromSlot: ${fromSlot}. Valid slots: 1-7`);
|
|
258
|
+
}
|
|
259
|
+
if (!toCfg) {
|
|
260
|
+
throw new Error(`Invalid toSlot: ${toSlot}. Valid slots: 1-7`);
|
|
261
|
+
}
|
|
262
|
+
if (fromCfg.fixed) {
|
|
263
|
+
throw new Error(`Slot ${fromSlot} is fixed and cannot be changed`);
|
|
264
|
+
}
|
|
265
|
+
if (toCfg.fixed) {
|
|
266
|
+
throw new Error(`Slot ${toSlot} is fixed and cannot be changed`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const wasmModule = getWasm();
|
|
270
|
+
const card = getSlotCard(wasmModule, fromSlot);
|
|
271
|
+
|
|
272
|
+
if (card === "empty") {
|
|
273
|
+
throw new Error(`Slot ${fromSlot} is empty — nothing to move`);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (!toCfg.compatible.includes(card)) {
|
|
277
|
+
throw new Error(
|
|
278
|
+
`${card} is not compatible with slot ${toSlot}. Compatible: ${toCfg.compatible.join(", ") || "none"}`
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const occupant = getSlotCard(wasmModule, toSlot);
|
|
283
|
+
if (occupant !== "empty") {
|
|
284
|
+
throw new Error(
|
|
285
|
+
`Slot ${toSlot} is occupied by ${occupant}. Remove it first with slotsRemoveCard.`
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
setSlotCardWasm(wasmModule, fromSlot, "empty");
|
|
290
|
+
setSlotCardWasm(wasmModule, toSlot, card);
|
|
291
|
+
persistSlotConfig(wasmModule);
|
|
292
|
+
wasmModule._reset();
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
success: true,
|
|
296
|
+
card,
|
|
297
|
+
fromSlot,
|
|
298
|
+
toSlot,
|
|
299
|
+
message: `Moved ${card} from slot ${fromSlot} to slot ${toSlot}. Emulator reset.`,
|
|
300
|
+
reset: true,
|
|
301
|
+
};
|
|
302
|
+
},
|
|
303
|
+
};
|