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,369 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* prodos.cpp - ProDOS filesystem reader for disk image browsing
|
|
3
|
+
*
|
|
4
|
+
* Written by
|
|
5
|
+
* Mike Daley <michael_daley@icloud.com>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#include "prodos.hpp"
|
|
9
|
+
#include <cstring>
|
|
10
|
+
#include <cstdio>
|
|
11
|
+
#include <unordered_set>
|
|
12
|
+
|
|
13
|
+
namespace a2e {
|
|
14
|
+
|
|
15
|
+
void ProDOS::readBlock(const uint8_t* data, size_t size, int blockNum,
|
|
16
|
+
bool dosOrder, uint8_t* out) {
|
|
17
|
+
memset(out, 0, BLOCK_SIZE);
|
|
18
|
+
|
|
19
|
+
if (size <= static_cast<size_t>(DISK_140K_SIZE)) {
|
|
20
|
+
// 140K disk - block N is at track N/8, sectors (N%8)*2 and (N%8)*2+1
|
|
21
|
+
int track = blockNum / 8;
|
|
22
|
+
int blockInTrack = blockNum % 8;
|
|
23
|
+
int prodosSector1 = blockInTrack * 2;
|
|
24
|
+
int prodosSector2 = blockInTrack * 2 + 1;
|
|
25
|
+
|
|
26
|
+
int sector1 = dosOrder ? PRODOS_TO_DOS_SECTOR[prodosSector1] : prodosSector1;
|
|
27
|
+
int sector2 = dosOrder ? PRODOS_TO_DOS_SECTOR[prodosSector2] : prodosSector2;
|
|
28
|
+
|
|
29
|
+
int offset1 = (track * 16 + sector1) * 256;
|
|
30
|
+
int offset2 = (track * 16 + sector2) * 256;
|
|
31
|
+
|
|
32
|
+
if (offset1 + 256 <= static_cast<int>(size)) {
|
|
33
|
+
memcpy(out, data + offset1, 256);
|
|
34
|
+
}
|
|
35
|
+
if (offset2 + 256 <= static_cast<int>(size)) {
|
|
36
|
+
memcpy(out + 256, data + offset2, 256);
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
// Larger disk - blocks are sequential
|
|
40
|
+
int offset = blockNum * BLOCK_SIZE;
|
|
41
|
+
if (offset + BLOCK_SIZE <= static_cast<int>(size)) {
|
|
42
|
+
memcpy(out, data + offset, BLOCK_SIZE);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
bool ProDOS::isValidVolumeHeader(const uint8_t* block) {
|
|
48
|
+
uint8_t storageTypeAndNameLen = block[0x04];
|
|
49
|
+
uint8_t storageType = (storageTypeAndNameLen >> 4) & 0x0F;
|
|
50
|
+
uint8_t nameLen = storageTypeAndNameLen & 0x0F;
|
|
51
|
+
|
|
52
|
+
if (storageType != STORAGE_VOLUME_HEADER) return false;
|
|
53
|
+
if (nameLen == 0 || nameLen > 15) return false;
|
|
54
|
+
|
|
55
|
+
uint8_t entryLength = block[0x23];
|
|
56
|
+
uint8_t entriesPerBlock = block[0x24];
|
|
57
|
+
if (entryLength != 0x27 || entriesPerBlock != 0x0D) return false;
|
|
58
|
+
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
bool ProDOS::detectSectorOrder(const uint8_t* data, size_t size, bool* dosOrder) {
|
|
63
|
+
uint8_t block[BLOCK_SIZE];
|
|
64
|
+
|
|
65
|
+
// Try ProDOS order first
|
|
66
|
+
readBlock(data, size, 2, false, block);
|
|
67
|
+
if (isValidVolumeHeader(block)) {
|
|
68
|
+
*dosOrder = false;
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Try DOS order
|
|
73
|
+
readBlock(data, size, 2, true, block);
|
|
74
|
+
if (isValidVolumeHeader(block)) {
|
|
75
|
+
*dosOrder = true;
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
void ProDOS::parseFilename(const uint8_t* bytes, int nameLen, char* out, int maxLen) {
|
|
83
|
+
int len = nameLen;
|
|
84
|
+
if (len > maxLen - 1) len = maxLen - 1;
|
|
85
|
+
for (int i = 0; i < len; i++) {
|
|
86
|
+
out[i] = bytes[i] & 0x7F;
|
|
87
|
+
}
|
|
88
|
+
out[len] = '\0';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const char* ProDOS::getFileTypeName(uint8_t fileType) {
|
|
92
|
+
switch (fileType) {
|
|
93
|
+
case 0x00: return "UNK";
|
|
94
|
+
case 0x01: return "BAD";
|
|
95
|
+
case 0x04: return "TXT";
|
|
96
|
+
case 0x06: return "BIN";
|
|
97
|
+
case 0x0F: return "DIR";
|
|
98
|
+
case 0x19: return "ADB";
|
|
99
|
+
case 0x1A: return "AWP";
|
|
100
|
+
case 0x1B: return "ASP";
|
|
101
|
+
case 0xB0: return "SRC";
|
|
102
|
+
case 0xB3: return "S16";
|
|
103
|
+
case 0xBF: return "DOC";
|
|
104
|
+
case 0xC0: return "PNT";
|
|
105
|
+
case 0xC1: return "PIC";
|
|
106
|
+
case 0xE0: return "SHK";
|
|
107
|
+
case 0xEF: return "PAS";
|
|
108
|
+
case 0xF0: return "CMD";
|
|
109
|
+
case 0xFA: return "INT";
|
|
110
|
+
case 0xFB: return "IVR";
|
|
111
|
+
case 0xFC: return "BAS";
|
|
112
|
+
case 0xFD: return "VAR";
|
|
113
|
+
case 0xFE: return "REL";
|
|
114
|
+
case 0xFF: return "SYS";
|
|
115
|
+
default: return "???";
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
bool ProDOS::isProDOS(const uint8_t* data, size_t size) {
|
|
120
|
+
if (size < static_cast<size_t>(DISK_140K_SIZE)) return false;
|
|
121
|
+
bool dosOrder;
|
|
122
|
+
return detectSectorOrder(data, size, &dosOrder);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
bool ProDOS::parseVolumeInfo(const uint8_t* data, size_t size, ProDOSVolumeInfo* info) {
|
|
126
|
+
if (size < static_cast<size_t>(DISK_140K_SIZE)) return false;
|
|
127
|
+
|
|
128
|
+
bool dosOrder;
|
|
129
|
+
if (!detectSectorOrder(data, size, &dosOrder)) return false;
|
|
130
|
+
|
|
131
|
+
uint8_t block[BLOCK_SIZE];
|
|
132
|
+
readBlock(data, size, 2, dosOrder, block);
|
|
133
|
+
|
|
134
|
+
uint8_t nameLen = block[0x04] & 0x0F;
|
|
135
|
+
parseFilename(block + 0x05, nameLen, info->volumeName, sizeof(info->volumeName));
|
|
136
|
+
|
|
137
|
+
// Validate volume name
|
|
138
|
+
if (info->volumeName[0] == '\0') return false;
|
|
139
|
+
|
|
140
|
+
info->fileCount = block[0x25] | (block[0x26] << 8);
|
|
141
|
+
info->totalBlocks = block[0x29] | (block[0x2A] << 8);
|
|
142
|
+
info->useDOSSectorOrder = dosOrder;
|
|
143
|
+
|
|
144
|
+
// Validate total blocks
|
|
145
|
+
int expectedBlocks = (size <= static_cast<size_t>(DISK_140K_SIZE)) ? 280
|
|
146
|
+
: static_cast<int>(size / BLOCK_SIZE);
|
|
147
|
+
if (info->totalBlocks == 0 || info->totalBlocks > expectedBlocks + 10) return false;
|
|
148
|
+
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
int ProDOS::readDirectoryEntries(const uint8_t* data, size_t size,
|
|
153
|
+
int startBlock, const char* pathPrefix,
|
|
154
|
+
bool dosOrder,
|
|
155
|
+
ProDOSCatalogEntry* entries, int maxEntries,
|
|
156
|
+
int currentCount) {
|
|
157
|
+
int count = currentCount;
|
|
158
|
+
int blockNum = startBlock;
|
|
159
|
+
int maxBlocks = static_cast<int>(size / BLOCK_SIZE);
|
|
160
|
+
|
|
161
|
+
// Visited set for cycle detection (supports large HD volumes)
|
|
162
|
+
std::unordered_set<int> visited;
|
|
163
|
+
|
|
164
|
+
while (blockNum != 0 && count < maxEntries) {
|
|
165
|
+
if (blockNum >= maxBlocks || visited.count(blockNum)) break;
|
|
166
|
+
visited.insert(blockNum);
|
|
167
|
+
|
|
168
|
+
uint8_t block[BLOCK_SIZE];
|
|
169
|
+
readBlock(data, size, blockNum, dosOrder, block);
|
|
170
|
+
|
|
171
|
+
int nextBlock = block[0x02] | (block[0x03] << 8);
|
|
172
|
+
|
|
173
|
+
// Parse entries (first block has header at entry 0)
|
|
174
|
+
int firstEntry = (blockNum == startBlock) ? 1 : 0;
|
|
175
|
+
|
|
176
|
+
for (int i = firstEntry; i < 13 && count < maxEntries; i++) {
|
|
177
|
+
int offset = 0x04 + (i * 39);
|
|
178
|
+
if (offset + 39 > BLOCK_SIZE) break;
|
|
179
|
+
|
|
180
|
+
const uint8_t* entry = block + offset;
|
|
181
|
+
uint8_t storageTypeAndNameLen = entry[0x00];
|
|
182
|
+
uint8_t storageType = (storageTypeAndNameLen >> 4) & 0x0F;
|
|
183
|
+
uint8_t nameLen = storageTypeAndNameLen & 0x0F;
|
|
184
|
+
|
|
185
|
+
if (storageType == STORAGE_DELETED || nameLen == 0) continue;
|
|
186
|
+
if (storageType == STORAGE_VOLUME_HEADER || storageType == STORAGE_SUBDIR_HEADER) continue;
|
|
187
|
+
|
|
188
|
+
ProDOSCatalogEntry& e = entries[count];
|
|
189
|
+
parseFilename(entry + 0x01, nameLen, e.filename, sizeof(e.filename));
|
|
190
|
+
e.fileType = entry[0x10];
|
|
191
|
+
const char* typeName = getFileTypeName(e.fileType);
|
|
192
|
+
strncpy(e.fileTypeName, typeName, sizeof(e.fileTypeName) - 1);
|
|
193
|
+
e.fileTypeName[sizeof(e.fileTypeName) - 1] = '\0';
|
|
194
|
+
e.storageType = storageType;
|
|
195
|
+
e.keyPointer = entry[0x11] | (entry[0x12] << 8);
|
|
196
|
+
e.blocksUsed = entry[0x13] | (entry[0x14] << 8);
|
|
197
|
+
e.eof = entry[0x15] | (entry[0x16] << 8) | (entry[0x17] << 16);
|
|
198
|
+
e.auxType = entry[0x1F] | (entry[0x20] << 8);
|
|
199
|
+
e.access = entry[0x1E];
|
|
200
|
+
e.isLocked = (e.access & 0x02) == 0;
|
|
201
|
+
e.isDirectory = (storageType == STORAGE_SUBDIR);
|
|
202
|
+
|
|
203
|
+
// Build full path
|
|
204
|
+
if (pathPrefix[0] != '\0') {
|
|
205
|
+
snprintf(e.path, sizeof(e.path), "%s/%s", pathPrefix, e.filename);
|
|
206
|
+
} else {
|
|
207
|
+
strncpy(e.path, e.filename, sizeof(e.path) - 1);
|
|
208
|
+
e.path[sizeof(e.path) - 1] = '\0';
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
count++;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
blockNum = nextBlock;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return count;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
int ProDOS::readCatalog(const uint8_t* data, size_t size,
|
|
221
|
+
ProDOSCatalogEntry* entries, int maxEntries) {
|
|
222
|
+
ProDOSVolumeInfo info;
|
|
223
|
+
if (!parseVolumeInfo(data, size, &info)) return 0;
|
|
224
|
+
|
|
225
|
+
bool dosOrder = info.useDOSSectorOrder;
|
|
226
|
+
int count = readDirectoryEntries(data, size, 2, "", dosOrder, entries, maxEntries, 0);
|
|
227
|
+
|
|
228
|
+
// Process subdirectories (breadth-first)
|
|
229
|
+
int processedDirs = 0;
|
|
230
|
+
while (processedDirs < count) {
|
|
231
|
+
// Scan for unprocessed subdirectories
|
|
232
|
+
bool found = false;
|
|
233
|
+
for (int i = processedDirs; i < count; i++) {
|
|
234
|
+
if (entries[i].isDirectory) {
|
|
235
|
+
int newCount = readDirectoryEntries(data, size, entries[i].keyPointer,
|
|
236
|
+
entries[i].path, dosOrder,
|
|
237
|
+
entries, maxEntries, count);
|
|
238
|
+
count = newCount;
|
|
239
|
+
found = true;
|
|
240
|
+
}
|
|
241
|
+
processedDirs = i + 1;
|
|
242
|
+
if (found) break;
|
|
243
|
+
}
|
|
244
|
+
if (!found) break;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return count;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
int ProDOS::readSeedlingFile(const uint8_t* data, size_t size, int blockNum,
|
|
251
|
+
int eof, bool dosOrder, uint8_t* out, int outMax) {
|
|
252
|
+
uint8_t block[BLOCK_SIZE];
|
|
253
|
+
readBlock(data, size, blockNum, dosOrder, block);
|
|
254
|
+
int bytes = eof < BLOCK_SIZE ? eof : BLOCK_SIZE;
|
|
255
|
+
if (bytes > outMax) bytes = outMax;
|
|
256
|
+
memcpy(out, block, bytes);
|
|
257
|
+
return bytes;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
int ProDOS::readSaplingFile(const uint8_t* data, size_t size, int indexBlock,
|
|
261
|
+
int eof, bool dosOrder, uint8_t* out, int outMax) {
|
|
262
|
+
uint8_t index[BLOCK_SIZE];
|
|
263
|
+
readBlock(data, size, indexBlock, dosOrder, index);
|
|
264
|
+
|
|
265
|
+
int bytesRead = 0;
|
|
266
|
+
for (int i = 0; i < 256 && bytesRead < eof && bytesRead < outMax; i++) {
|
|
267
|
+
int dataBlockNum = index[i] | (index[i + 256] << 8);
|
|
268
|
+
int bytesToCopy = BLOCK_SIZE;
|
|
269
|
+
if (bytesRead + bytesToCopy > eof) bytesToCopy = eof - bytesRead;
|
|
270
|
+
if (bytesRead + bytesToCopy > outMax) bytesToCopy = outMax - bytesRead;
|
|
271
|
+
|
|
272
|
+
if (dataBlockNum == 0) {
|
|
273
|
+
// Sparse file - fill with zeros
|
|
274
|
+
memset(out + bytesRead, 0, bytesToCopy);
|
|
275
|
+
} else {
|
|
276
|
+
uint8_t dataBlock[BLOCK_SIZE];
|
|
277
|
+
readBlock(data, size, dataBlockNum, dosOrder, dataBlock);
|
|
278
|
+
memcpy(out + bytesRead, dataBlock, bytesToCopy);
|
|
279
|
+
}
|
|
280
|
+
bytesRead += bytesToCopy;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return bytesRead;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
int ProDOS::readTreeFile(const uint8_t* data, size_t size, int masterBlock,
|
|
287
|
+
int eof, bool dosOrder, uint8_t* out, int outMax) {
|
|
288
|
+
uint8_t master[BLOCK_SIZE];
|
|
289
|
+
readBlock(data, size, masterBlock, dosOrder, master);
|
|
290
|
+
|
|
291
|
+
int bytesRead = 0;
|
|
292
|
+
for (int i = 0; i < 128 && bytesRead < eof && bytesRead < outMax; i++) {
|
|
293
|
+
int indexBlockNum = master[i] | (master[i + 256] << 8);
|
|
294
|
+
if (indexBlockNum == 0) {
|
|
295
|
+
// Sparse - skip up to 256 blocks
|
|
296
|
+
int bytesToSkip = 256 * BLOCK_SIZE;
|
|
297
|
+
if (bytesRead + bytesToSkip > eof) bytesToSkip = eof - bytesRead;
|
|
298
|
+
if (bytesRead + bytesToSkip > outMax) bytesToSkip = outMax - bytesRead;
|
|
299
|
+
memset(out + bytesRead, 0, bytesToSkip);
|
|
300
|
+
bytesRead += bytesToSkip;
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
uint8_t index[BLOCK_SIZE];
|
|
305
|
+
readBlock(data, size, indexBlockNum, dosOrder, index);
|
|
306
|
+
|
|
307
|
+
for (int j = 0; j < 256 && bytesRead < eof && bytesRead < outMax; j++) {
|
|
308
|
+
int dataBlockNum = index[j] | (index[j + 256] << 8);
|
|
309
|
+
int bytesToCopy = BLOCK_SIZE;
|
|
310
|
+
if (bytesRead + bytesToCopy > eof) bytesToCopy = eof - bytesRead;
|
|
311
|
+
if (bytesRead + bytesToCopy > outMax) bytesToCopy = outMax - bytesRead;
|
|
312
|
+
|
|
313
|
+
if (dataBlockNum == 0) {
|
|
314
|
+
memset(out + bytesRead, 0, bytesToCopy);
|
|
315
|
+
} else {
|
|
316
|
+
uint8_t dataBlock[BLOCK_SIZE];
|
|
317
|
+
readBlock(data, size, dataBlockNum, dosOrder, dataBlock);
|
|
318
|
+
memcpy(out + bytesRead, dataBlock, bytesToCopy);
|
|
319
|
+
}
|
|
320
|
+
bytesRead += bytesToCopy;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return bytesRead;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
int ProDOS::readFile(const uint8_t* data, size_t size,
|
|
328
|
+
const ProDOSCatalogEntry* entry,
|
|
329
|
+
uint8_t* outBuf, int outMax) {
|
|
330
|
+
if (entry->isDirectory) return 0;
|
|
331
|
+
|
|
332
|
+
ProDOSVolumeInfo info;
|
|
333
|
+
if (!parseVolumeInfo(data, size, &info)) return 0;
|
|
334
|
+
bool dosOrder = info.useDOSSectorOrder;
|
|
335
|
+
|
|
336
|
+
switch (entry->storageType) {
|
|
337
|
+
case STORAGE_SEEDLING:
|
|
338
|
+
return readSeedlingFile(data, size, entry->keyPointer, entry->eof, dosOrder, outBuf, outMax);
|
|
339
|
+
case STORAGE_SAPLING:
|
|
340
|
+
return readSaplingFile(data, size, entry->keyPointer, entry->eof, dosOrder, outBuf, outMax);
|
|
341
|
+
case STORAGE_TREE:
|
|
342
|
+
return readTreeFile(data, size, entry->keyPointer, entry->eof, dosOrder, outBuf, outMax);
|
|
343
|
+
default:
|
|
344
|
+
return 0;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
int ProDOS::mapFileTypeForViewer(uint8_t prodosType) {
|
|
349
|
+
switch (prodosType) {
|
|
350
|
+
case 0x04: return 0x00; // TXT -> Text
|
|
351
|
+
case 0xFA: return 0x01; // INT -> Integer BASIC
|
|
352
|
+
case 0xFC: return 0x02; // BAS -> Applesoft BASIC
|
|
353
|
+
case 0x06: return 0x04; // BIN -> Binary
|
|
354
|
+
case 0xFF: return 0x04; // SYS -> Binary
|
|
355
|
+
default: return -1;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
int ProDOS::readDirectory(const uint8_t* data, size_t size,
|
|
360
|
+
int startBlock, const char* pathPrefix,
|
|
361
|
+
ProDOSCatalogEntry* entries, int maxEntries) {
|
|
362
|
+
ProDOSVolumeInfo info;
|
|
363
|
+
if (!parseVolumeInfo(data, size, &info)) return 0;
|
|
364
|
+
|
|
365
|
+
return readDirectoryEntries(data, size, startBlock, pathPrefix,
|
|
366
|
+
info.useDOSSectorOrder, entries, maxEntries, 0);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
} // namespace a2e
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* prodos.hpp - ProDOS filesystem reader for disk image browsing
|
|
3
|
+
*
|
|
4
|
+
* Written by
|
|
5
|
+
* Mike Daley <michael_daley@icloud.com>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include <cstdint>
|
|
11
|
+
#include <cstddef>
|
|
12
|
+
|
|
13
|
+
namespace a2e {
|
|
14
|
+
|
|
15
|
+
struct ProDOSCatalogEntry {
|
|
16
|
+
char filename[16]; // Null-terminated (max 15 chars)
|
|
17
|
+
char path[128]; // Full path including subdirectory
|
|
18
|
+
uint8_t fileType; // ProDOS file type
|
|
19
|
+
char fileTypeName[4]; // Short type name
|
|
20
|
+
uint8_t storageType; // 1=seedling, 2=sapling, 3=tree, 0xD=subdir
|
|
21
|
+
uint16_t keyPointer; // Key block
|
|
22
|
+
uint16_t blocksUsed;
|
|
23
|
+
uint32_t eof; // File size (24-bit)
|
|
24
|
+
uint16_t auxType; // Aux type (load address for BIN)
|
|
25
|
+
uint8_t access;
|
|
26
|
+
bool isLocked;
|
|
27
|
+
bool isDirectory;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
struct ProDOSVolumeInfo {
|
|
31
|
+
char volumeName[16]; // Null-terminated
|
|
32
|
+
uint16_t totalBlocks;
|
|
33
|
+
uint16_t fileCount;
|
|
34
|
+
bool useDOSSectorOrder;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
class ProDOS {
|
|
38
|
+
public:
|
|
39
|
+
static constexpr int BLOCK_SIZE = 512;
|
|
40
|
+
static constexpr int DISK_140K_SIZE = 143360;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check if disk data is ProDOS format
|
|
44
|
+
*/
|
|
45
|
+
static bool isProDOS(const uint8_t* data, size_t size);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Parse volume information
|
|
49
|
+
*/
|
|
50
|
+
static bool parseVolumeInfo(const uint8_t* data, size_t size, ProDOSVolumeInfo* info);
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Read the full catalog (including subdirectories).
|
|
54
|
+
* Returns number of entries written.
|
|
55
|
+
*/
|
|
56
|
+
static int readCatalog(const uint8_t* data, size_t size,
|
|
57
|
+
ProDOSCatalogEntry* entries, int maxEntries);
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Read a single directory's entries (no recursion into subdirectories).
|
|
61
|
+
* startBlock=2 for root directory, or a subdirectory's keyPointer.
|
|
62
|
+
* Returns number of entries written.
|
|
63
|
+
*/
|
|
64
|
+
static int readDirectory(const uint8_t* data, size_t size,
|
|
65
|
+
int startBlock, const char* pathPrefix,
|
|
66
|
+
ProDOSCatalogEntry* entries, int maxEntries);
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Read a file's contents based on its catalog entry.
|
|
70
|
+
* outBuf must be large enough (at least entry.eof bytes).
|
|
71
|
+
* Returns bytes written.
|
|
72
|
+
*/
|
|
73
|
+
static int readFile(const uint8_t* data, size_t size,
|
|
74
|
+
const ProDOSCatalogEntry* entry,
|
|
75
|
+
uint8_t* outBuf, int outMax);
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Map ProDOS file type to DOS 3.3 type for viewer.
|
|
79
|
+
* Returns -1 if no mapping (use hex dump).
|
|
80
|
+
*/
|
|
81
|
+
static int mapFileTypeForViewer(uint8_t prodosType);
|
|
82
|
+
|
|
83
|
+
private:
|
|
84
|
+
static void readBlock(const uint8_t* data, size_t size, int blockNum,
|
|
85
|
+
bool dosOrder, uint8_t* out);
|
|
86
|
+
static bool isValidVolumeHeader(const uint8_t* block);
|
|
87
|
+
static bool detectSectorOrder(const uint8_t* data, size_t size, bool* dosOrder);
|
|
88
|
+
static void parseFilename(const uint8_t* bytes, int nameLen, char* out, int maxLen);
|
|
89
|
+
static const char* getFileTypeName(uint8_t fileType);
|
|
90
|
+
|
|
91
|
+
// ProDOS-to-DOS sector conversion table
|
|
92
|
+
static constexpr uint8_t PRODOS_TO_DOS_SECTOR[16] = {
|
|
93
|
+
0, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 15
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Storage types
|
|
97
|
+
static constexpr uint8_t STORAGE_DELETED = 0x0;
|
|
98
|
+
static constexpr uint8_t STORAGE_SEEDLING = 0x1;
|
|
99
|
+
static constexpr uint8_t STORAGE_SAPLING = 0x2;
|
|
100
|
+
static constexpr uint8_t STORAGE_TREE = 0x3;
|
|
101
|
+
static constexpr uint8_t STORAGE_SUBDIR = 0xD;
|
|
102
|
+
static constexpr uint8_t STORAGE_SUBDIR_HEADER = 0xE;
|
|
103
|
+
static constexpr uint8_t STORAGE_VOLUME_HEADER = 0xF;
|
|
104
|
+
|
|
105
|
+
static int readSeedlingFile(const uint8_t* data, size_t size, int blockNum,
|
|
106
|
+
int eof, bool dosOrder, uint8_t* out, int outMax);
|
|
107
|
+
static int readSaplingFile(const uint8_t* data, size_t size, int indexBlock,
|
|
108
|
+
int eof, bool dosOrder, uint8_t* out, int outMax);
|
|
109
|
+
static int readTreeFile(const uint8_t* data, size_t size, int masterBlock,
|
|
110
|
+
int eof, bool dosOrder, uint8_t* out, int outMax);
|
|
111
|
+
|
|
112
|
+
static int readDirectoryEntries(const uint8_t* data, size_t size,
|
|
113
|
+
int startBlock, const char* pathPrefix,
|
|
114
|
+
bool dosOrder,
|
|
115
|
+
ProDOSCatalogEntry* entries, int maxEntries,
|
|
116
|
+
int currentCount);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
} // namespace a2e
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* keyboard.cpp - Browser keycode to Apple II ASCII translation
|
|
3
|
+
*
|
|
4
|
+
* Written by
|
|
5
|
+
* Mike Daley <michael_daley@icloud.com>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#include "keyboard.hpp"
|
|
9
|
+
|
|
10
|
+
namespace a2e {
|
|
11
|
+
|
|
12
|
+
Keyboard::Keyboard() {}
|
|
13
|
+
|
|
14
|
+
int Keyboard::handleKeyDown(int browserKeycode, bool shift, bool ctrl,
|
|
15
|
+
bool alt, bool meta, bool capsLock) {
|
|
16
|
+
// Track modifier keys (Apple buttons)
|
|
17
|
+
if (browserKeycode == 18) { // Alt
|
|
18
|
+
openApplePressed_ = true;
|
|
19
|
+
return -1; // Don't generate a key
|
|
20
|
+
}
|
|
21
|
+
if (browserKeycode == 91 || browserKeycode == 93) { // Meta (left/right)
|
|
22
|
+
closedApplePressed_ = true;
|
|
23
|
+
return -1;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Skip pure modifier keys
|
|
27
|
+
if (browserKeycode == 16 || browserKeycode == 17) { // Shift, Ctrl
|
|
28
|
+
return -1;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Translate browser keycode to base Apple II code
|
|
32
|
+
int appleKey = translateKeycode(browserKeycode);
|
|
33
|
+
if (appleKey < 0) {
|
|
34
|
+
return -1; // Not a mapped key
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Handle letters (a-z)
|
|
38
|
+
if (appleKey >= 0x61 && appleKey <= 0x7A) {
|
|
39
|
+
// Apply caps lock and shift
|
|
40
|
+
if (capsLock && !shift) {
|
|
41
|
+
// Caps lock on, no shift -> uppercase
|
|
42
|
+
appleKey -= 32;
|
|
43
|
+
} else if (!capsLock && shift) {
|
|
44
|
+
// Caps lock off, shift pressed -> uppercase
|
|
45
|
+
appleKey -= 32;
|
|
46
|
+
}
|
|
47
|
+
// Otherwise stays lowercase
|
|
48
|
+
} else if (shift) {
|
|
49
|
+
// Apply shift to non-letter keys
|
|
50
|
+
appleKey = applyShift(browserKeycode, appleKey);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Apply control modifier (produces control characters)
|
|
54
|
+
if (ctrl) {
|
|
55
|
+
appleKey = applyControl(appleKey);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Send to emulator via callback
|
|
59
|
+
if (keyCallback_) {
|
|
60
|
+
keyCallback_(appleKey);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return appleKey;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
void Keyboard::handleKeyUp(int browserKeycode, bool shift, bool ctrl,
|
|
67
|
+
bool alt, bool meta) {
|
|
68
|
+
(void)shift;
|
|
69
|
+
(void)ctrl;
|
|
70
|
+
|
|
71
|
+
// Track modifier keys
|
|
72
|
+
if (browserKeycode == 18) { // Alt
|
|
73
|
+
openApplePressed_ = false;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (browserKeycode == 91 || browserKeycode == 93) { // Meta
|
|
77
|
+
closedApplePressed_ = false;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
int Keyboard::translateKeycode(int browserKeycode) const {
|
|
83
|
+
// Letters A-Z (browser codes 65-90) -> lowercase a-z (0x61-0x7A)
|
|
84
|
+
if (browserKeycode >= 65 && browserKeycode <= 90) {
|
|
85
|
+
return browserKeycode + 32; // Convert to lowercase
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Numbers 0-9 (browser codes 48-57) -> ASCII 0x30-0x39
|
|
89
|
+
if (browserKeycode >= 48 && browserKeycode <= 57) {
|
|
90
|
+
return browserKeycode;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Special keys
|
|
94
|
+
switch (browserKeycode) {
|
|
95
|
+
case 13:
|
|
96
|
+
return 0x0D; // Enter -> CR
|
|
97
|
+
case 8:
|
|
98
|
+
return 0x08; // Backspace -> Left arrow (delete)
|
|
99
|
+
case 27:
|
|
100
|
+
return 0x1B; // Escape
|
|
101
|
+
case 32:
|
|
102
|
+
return 0x20; // Space
|
|
103
|
+
case 9:
|
|
104
|
+
return 0x09; // Tab
|
|
105
|
+
|
|
106
|
+
// Arrow keys
|
|
107
|
+
case 37:
|
|
108
|
+
return 0x08; // Left arrow
|
|
109
|
+
case 38:
|
|
110
|
+
return 0x0B; // Up arrow
|
|
111
|
+
case 39:
|
|
112
|
+
return 0x15; // Right arrow
|
|
113
|
+
case 40:
|
|
114
|
+
return 0x0A; // Down arrow
|
|
115
|
+
|
|
116
|
+
// Punctuation (US keyboard layout)
|
|
117
|
+
case 188:
|
|
118
|
+
return 0x2C; // Comma
|
|
119
|
+
case 190:
|
|
120
|
+
return 0x2E; // Period
|
|
121
|
+
case 191:
|
|
122
|
+
return 0x2F; // Slash
|
|
123
|
+
case 186:
|
|
124
|
+
return 0x3B; // Semicolon
|
|
125
|
+
case 222:
|
|
126
|
+
return 0x27; // Quote
|
|
127
|
+
case 219:
|
|
128
|
+
return 0x5B; // Left bracket
|
|
129
|
+
case 221:
|
|
130
|
+
return 0x5D; // Right bracket
|
|
131
|
+
case 220:
|
|
132
|
+
return 0x5C; // Backslash
|
|
133
|
+
case 189:
|
|
134
|
+
return 0x2D; // Minus
|
|
135
|
+
case 187:
|
|
136
|
+
return 0x3D; // Equals
|
|
137
|
+
case 192:
|
|
138
|
+
return 0x60; // Backtick
|
|
139
|
+
|
|
140
|
+
default:
|
|
141
|
+
return -1; // Not mapped
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
int Keyboard::applyShift(int browserKeycode, int baseKey) const {
|
|
146
|
+
// Number row shifted symbols
|
|
147
|
+
switch (browserKeycode) {
|
|
148
|
+
case 48:
|
|
149
|
+
return 0x29; // 0 -> )
|
|
150
|
+
case 49:
|
|
151
|
+
return 0x21; // 1 -> !
|
|
152
|
+
case 50:
|
|
153
|
+
return 0x40; // 2 -> @
|
|
154
|
+
case 51:
|
|
155
|
+
return 0x23; // 3 -> #
|
|
156
|
+
case 52:
|
|
157
|
+
return 0x24; // 4 -> $
|
|
158
|
+
case 53:
|
|
159
|
+
return 0x25; // 5 -> %
|
|
160
|
+
case 54:
|
|
161
|
+
return 0x5E; // 6 -> ^
|
|
162
|
+
case 55:
|
|
163
|
+
return 0x26; // 7 -> &
|
|
164
|
+
case 56:
|
|
165
|
+
return 0x2A; // 8 -> *
|
|
166
|
+
case 57:
|
|
167
|
+
return 0x28; // 9 -> (
|
|
168
|
+
|
|
169
|
+
// Punctuation shifted
|
|
170
|
+
case 188:
|
|
171
|
+
return 0x3C; // , -> <
|
|
172
|
+
case 190:
|
|
173
|
+
return 0x3E; // . -> >
|
|
174
|
+
case 191:
|
|
175
|
+
return 0x3F; // / -> ?
|
|
176
|
+
case 186:
|
|
177
|
+
return 0x3A; // ; -> :
|
|
178
|
+
case 222:
|
|
179
|
+
return 0x22; // ' -> "
|
|
180
|
+
case 219:
|
|
181
|
+
return 0x7B; // [ -> {
|
|
182
|
+
case 221:
|
|
183
|
+
return 0x7D; // ] -> }
|
|
184
|
+
case 220:
|
|
185
|
+
return 0x7C; // \ -> |
|
|
186
|
+
case 189:
|
|
187
|
+
return 0x5F; // - -> _
|
|
188
|
+
case 187:
|
|
189
|
+
return 0x2B; // = -> +
|
|
190
|
+
case 192:
|
|
191
|
+
return 0x7E; // ` -> ~
|
|
192
|
+
|
|
193
|
+
default:
|
|
194
|
+
return baseKey;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
int Keyboard::applyControl(int key) const {
|
|
199
|
+
// Convert a-z to Ctrl+A-Z (0x01-0x1A)
|
|
200
|
+
if (key >= 0x61 && key <= 0x7A) {
|
|
201
|
+
return key - 0x60;
|
|
202
|
+
}
|
|
203
|
+
// Convert A-Z to Ctrl+A-Z
|
|
204
|
+
if (key >= 0x41 && key <= 0x5A) {
|
|
205
|
+
return key - 0x40;
|
|
206
|
+
}
|
|
207
|
+
return key;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
int charToAppleKey(int charCode) {
|
|
211
|
+
// Newline/CR -> CR
|
|
212
|
+
if (charCode == 0x0A || charCode == 0x0D) {
|
|
213
|
+
return 0x0D;
|
|
214
|
+
}
|
|
215
|
+
// Tab
|
|
216
|
+
if (charCode == 0x09) {
|
|
217
|
+
return 0x09;
|
|
218
|
+
}
|
|
219
|
+
// Printable ASCII (space through tilde)
|
|
220
|
+
if (charCode >= 0x20 && charCode <= 0x7E) {
|
|
221
|
+
return charCode;
|
|
222
|
+
}
|
|
223
|
+
// Not mappable
|
|
224
|
+
return -1;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
} // namespace a2e
|