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,142 @@
|
|
|
1
|
+
// Simple GCR encoding/decoding test
|
|
2
|
+
// Compile: g++ -std=c++17 -I../src/core/disk -o gcr-test gcr-test.cpp ../src/core/disk/gcr_encoding.cpp
|
|
3
|
+
|
|
4
|
+
#include <iostream>
|
|
5
|
+
#include <iomanip>
|
|
6
|
+
#include <cstring>
|
|
7
|
+
#include "../src/core/disk/gcr_encoding.hpp"
|
|
8
|
+
|
|
9
|
+
using namespace a2e;
|
|
10
|
+
|
|
11
|
+
// 6-and-2 decoding table (reverse of ENCODE_6_AND_2)
|
|
12
|
+
static constexpr std::array<int8_t, 256> DECODE_6_AND_2 = []() {
|
|
13
|
+
std::array<int8_t, 256> table{};
|
|
14
|
+
for (int i = 0; i < 256; i++) {
|
|
15
|
+
table[i] = -1; // Invalid by default
|
|
16
|
+
}
|
|
17
|
+
// Fill in valid mappings from the encode table
|
|
18
|
+
for (int i = 0; i < 64; i++) {
|
|
19
|
+
table[GCR::ENCODE_6_AND_2[i]] = static_cast<int8_t>(i);
|
|
20
|
+
}
|
|
21
|
+
return table;
|
|
22
|
+
}();
|
|
23
|
+
|
|
24
|
+
bool decode6and2(const uint8_t *encoded, uint8_t *output) {
|
|
25
|
+
// Decode 343 nibbles back to 256 bytes
|
|
26
|
+
uint8_t buffer[342];
|
|
27
|
+
|
|
28
|
+
// XOR decode (reverse of encode)
|
|
29
|
+
uint8_t prev = 0;
|
|
30
|
+
for (int i = 0; i < 342; i++) {
|
|
31
|
+
int8_t decoded = DECODE_6_AND_2[encoded[i]];
|
|
32
|
+
if (decoded < 0) {
|
|
33
|
+
std::cerr << "Invalid nibble at position " << i << ": 0x"
|
|
34
|
+
<< std::hex << (int)encoded[i] << std::dec << std::endl;
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
buffer[i] = decoded ^ prev;
|
|
38
|
+
prev = buffer[i];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Verify checksum
|
|
42
|
+
int8_t checksum_decoded = DECODE_6_AND_2[encoded[342]];
|
|
43
|
+
if (checksum_decoded < 0) {
|
|
44
|
+
std::cerr << "Invalid checksum nibble: 0x" << std::hex << (int)encoded[342] << std::dec << std::endl;
|
|
45
|
+
} else if ((prev & 0x3F) != (checksum_decoded & 0x3F)) {
|
|
46
|
+
std::cerr << "Checksum mismatch: expected 0x" << std::hex << (int)(prev & 0x3F)
|
|
47
|
+
<< " got 0x" << (int)(checksum_decoded & 0x3F) << std::dec << std::endl;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Reconstruct 256 bytes from auxiliary (86) and primary (256) buffers
|
|
51
|
+
for (int i = 0; i < 256; i++) {
|
|
52
|
+
// High 6 bits from primary buffer
|
|
53
|
+
uint8_t high = buffer[86 + i] << 2;
|
|
54
|
+
|
|
55
|
+
// Low 2 bits from auxiliary buffer
|
|
56
|
+
// NOTE: encode swaps bits 0,1, so decode must unswap
|
|
57
|
+
uint8_t aux_byte = buffer[i % 86];
|
|
58
|
+
int shift = (i / 86) * 2;
|
|
59
|
+
uint8_t raw = (aux_byte >> shift) & 0x03;
|
|
60
|
+
// Unswap: bit 0 <-> bit 1
|
|
61
|
+
uint8_t low = ((raw & 0x01) << 1) | ((raw & 0x02) >> 1);
|
|
62
|
+
|
|
63
|
+
output[i] = high | low;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
int main() {
|
|
70
|
+
// Create a test sector with known data
|
|
71
|
+
uint8_t sector_data[256];
|
|
72
|
+
for (int i = 0; i < 256; i++) {
|
|
73
|
+
sector_data[i] = i; // Simple pattern: 0, 1, 2, ..., 255
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
std::cout << "Original sector data (first 32 bytes):" << std::endl;
|
|
77
|
+
for (int i = 0; i < 32; i++) {
|
|
78
|
+
std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)sector_data[i] << " ";
|
|
79
|
+
if ((i + 1) % 16 == 0) std::cout << std::endl;
|
|
80
|
+
}
|
|
81
|
+
std::cout << std::dec << std::endl;
|
|
82
|
+
|
|
83
|
+
// Encode using 6-and-2
|
|
84
|
+
std::vector<uint8_t> encoded = GCR::encode6and2(sector_data);
|
|
85
|
+
|
|
86
|
+
std::cout << "Encoded nibbles: " << encoded.size() << " bytes" << std::endl;
|
|
87
|
+
std::cout << "First 48 nibbles:" << std::endl;
|
|
88
|
+
for (int i = 0; i < 48; i++) {
|
|
89
|
+
std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)encoded[i] << " ";
|
|
90
|
+
if ((i + 1) % 16 == 0) std::cout << std::endl;
|
|
91
|
+
}
|
|
92
|
+
std::cout << std::dec << std::endl;
|
|
93
|
+
|
|
94
|
+
// Verify all nibbles have bit 7 set
|
|
95
|
+
int invalid_count = 0;
|
|
96
|
+
for (size_t i = 0; i < encoded.size(); i++) {
|
|
97
|
+
if (!(encoded[i] & 0x80)) {
|
|
98
|
+
std::cout << "Warning: nibble at position " << i << " doesn't have bit 7 set: 0x"
|
|
99
|
+
<< std::hex << (int)encoded[i] << std::dec << std::endl;
|
|
100
|
+
invalid_count++;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (invalid_count == 0) {
|
|
104
|
+
std::cout << "All nibbles have bit 7 set (good)" << std::endl;
|
|
105
|
+
}
|
|
106
|
+
std::cout << std::endl;
|
|
107
|
+
|
|
108
|
+
// Decode
|
|
109
|
+
uint8_t decoded[256];
|
|
110
|
+
if (!decode6and2(encoded.data(), decoded)) {
|
|
111
|
+
std::cerr << "Decode failed!" << std::endl;
|
|
112
|
+
return 1;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
std::cout << "Decoded sector data (first 32 bytes):" << std::endl;
|
|
116
|
+
for (int i = 0; i < 32; i++) {
|
|
117
|
+
std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)decoded[i] << " ";
|
|
118
|
+
if ((i + 1) % 16 == 0) std::cout << std::endl;
|
|
119
|
+
}
|
|
120
|
+
std::cout << std::dec << std::endl;
|
|
121
|
+
|
|
122
|
+
// Compare
|
|
123
|
+
int mismatches = 0;
|
|
124
|
+
for (int i = 0; i < 256; i++) {
|
|
125
|
+
if (sector_data[i] != decoded[i]) {
|
|
126
|
+
if (mismatches < 10) {
|
|
127
|
+
std::cout << "Mismatch at position " << i << ": expected 0x"
|
|
128
|
+
<< std::hex << (int)sector_data[i] << " got 0x" << (int)decoded[i]
|
|
129
|
+
<< std::dec << std::endl;
|
|
130
|
+
}
|
|
131
|
+
mismatches++;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (mismatches == 0) {
|
|
136
|
+
std::cout << "SUCCESS: All 256 bytes match!" << std::endl;
|
|
137
|
+
return 0;
|
|
138
|
+
} else {
|
|
139
|
+
std::cout << "FAILED: " << mismatches << " mismatches" << std::endl;
|
|
140
|
+
return 1;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
import { createContext, runInContext } from 'vm';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = dirname(__filename);
|
|
8
|
+
|
|
9
|
+
async function loadWasmModule() {
|
|
10
|
+
const wasmJsPath = join(__dirname, '..', 'public', 'a2e.js');
|
|
11
|
+
const wasmDir = dirname(wasmJsPath);
|
|
12
|
+
const code = readFileSync(wasmJsPath, 'utf8');
|
|
13
|
+
const context = {
|
|
14
|
+
module: { exports: {} },
|
|
15
|
+
exports: {},
|
|
16
|
+
globalThis: global,
|
|
17
|
+
console: console,
|
|
18
|
+
require: (await import('module')).createRequire(import.meta.url),
|
|
19
|
+
__dirname: wasmDir,
|
|
20
|
+
__filename: wasmJsPath,
|
|
21
|
+
process: process,
|
|
22
|
+
WebAssembly: WebAssembly,
|
|
23
|
+
URL: URL,
|
|
24
|
+
TextDecoder: TextDecoder,
|
|
25
|
+
setTimeout: setTimeout,
|
|
26
|
+
clearTimeout: clearTimeout,
|
|
27
|
+
performance: { now: () => Date.now() }
|
|
28
|
+
};
|
|
29
|
+
createContext(context);
|
|
30
|
+
runInContext(code, context);
|
|
31
|
+
return context.module.exports;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function runTest() {
|
|
35
|
+
const createA2EModule = await loadWasmModule();
|
|
36
|
+
const Module = await createA2EModule();
|
|
37
|
+
Module._init();
|
|
38
|
+
|
|
39
|
+
// Check ROM at FE89 and surrounding area
|
|
40
|
+
console.log('ROM at $FE80-$FE9F:');
|
|
41
|
+
for (let row = 0; row < 2; row++) {
|
|
42
|
+
const addr = 0xFE80 + row * 16;
|
|
43
|
+
let line = '$' + addr.toString(16).toUpperCase() + ': ';
|
|
44
|
+
for (let col = 0; col < 16; col++) {
|
|
45
|
+
line += Module._readMemory(addr + col).toString(16).padStart(2, '0').toUpperCase() + ' ';
|
|
46
|
+
}
|
|
47
|
+
console.log(line);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Check IRQ/BRK vectors
|
|
51
|
+
console.log('\nVectors:');
|
|
52
|
+
const nmilo = Module._readMemory(0xFFFA);
|
|
53
|
+
const nmihi = Module._readMemory(0xFFFB);
|
|
54
|
+
console.log('NMI ($FFFA): $' + ((nmihi << 8) | nmilo).toString(16).toUpperCase());
|
|
55
|
+
|
|
56
|
+
const resetlo = Module._readMemory(0xFFFC);
|
|
57
|
+
const resethi = Module._readMemory(0xFFFD);
|
|
58
|
+
console.log('RESET ($FFFC): $' + ((resethi << 8) | resetlo).toString(16).toUpperCase());
|
|
59
|
+
|
|
60
|
+
const irqlo = Module._readMemory(0xFFFE);
|
|
61
|
+
const irqhi = Module._readMemory(0xFFFF);
|
|
62
|
+
console.log('IRQ/BRK ($FFFE): $' + ((irqhi << 8) | irqlo).toString(16).toUpperCase());
|
|
63
|
+
|
|
64
|
+
// What should be at FE89 in real Apple IIe ROM?
|
|
65
|
+
console.log('\n$FE89 contains: $' + Module._readMemory(0xFE89).toString(16).toUpperCase());
|
|
66
|
+
console.log('$FE8A contains: $' + Module._readMemory(0xFE8A).toString(16).toUpperCase());
|
|
67
|
+
console.log('$FE8B contains: $' + Module._readMemory(0xFE8B).toString(16).toUpperCase());
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
runTest().catch(err => { console.error(err); process.exit(1); });
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compare boot sector loading between disk image and memory
|
|
3
|
+
*/
|
|
4
|
+
import { readFileSync } from 'fs';
|
|
5
|
+
import { createContext, runInContext } from 'vm';
|
|
6
|
+
|
|
7
|
+
async function loadWasmModule() {
|
|
8
|
+
const wasmJsPath = '/Users/michaeldaley/Source/web-a2e/public/a2e.js';
|
|
9
|
+
|
|
10
|
+
const code = readFileSync(wasmJsPath, 'utf8');
|
|
11
|
+
|
|
12
|
+
const context = {
|
|
13
|
+
module: { exports: {} },
|
|
14
|
+
exports: {},
|
|
15
|
+
globalThis: global,
|
|
16
|
+
console: console,
|
|
17
|
+
require: (await import('module')).createRequire(import.meta.url),
|
|
18
|
+
__dirname: '/Users/michaeldaley/Source/web-a2e/public',
|
|
19
|
+
__filename: wasmJsPath,
|
|
20
|
+
process: process,
|
|
21
|
+
WebAssembly: WebAssembly,
|
|
22
|
+
URL: URL,
|
|
23
|
+
TextDecoder: TextDecoder,
|
|
24
|
+
setTimeout: setTimeout,
|
|
25
|
+
clearTimeout: clearTimeout,
|
|
26
|
+
performance: { now: () => Date.now() }
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
createContext(context);
|
|
30
|
+
runInContext(code, context);
|
|
31
|
+
|
|
32
|
+
return context.module.exports;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function main() {
|
|
36
|
+
console.log('Loading WASM module...');
|
|
37
|
+
const createA2EModule = await loadWasmModule();
|
|
38
|
+
const Module = await createA2EModule();
|
|
39
|
+
|
|
40
|
+
console.log('Initializing emulator...');
|
|
41
|
+
Module._init();
|
|
42
|
+
|
|
43
|
+
// Load the disk image
|
|
44
|
+
const diskPath = '/Users/michaeldaley/Source/web-a2e/public/Apple DOS 3.3 August 1980.dsk';
|
|
45
|
+
const diskData = readFileSync(diskPath);
|
|
46
|
+
|
|
47
|
+
// Allocate memory for disk data
|
|
48
|
+
const diskPtr = Module._malloc(diskData.length);
|
|
49
|
+
Module.HEAPU8.set(diskData, diskPtr);
|
|
50
|
+
|
|
51
|
+
// Allocate memory for filename
|
|
52
|
+
const filename = 'Apple DOS 3.3 August 1980.dsk';
|
|
53
|
+
const filenamePtr = Module._malloc(filename.length + 1);
|
|
54
|
+
Module.stringToUTF8(filename, filenamePtr, filename.length + 1);
|
|
55
|
+
|
|
56
|
+
// Insert the disk
|
|
57
|
+
const inserted = Module._insertDisk(0, diskPtr, diskData.length, filenamePtr);
|
|
58
|
+
Module._free(filenamePtr);
|
|
59
|
+
|
|
60
|
+
if (!inserted) {
|
|
61
|
+
console.error('Failed to insert disk!');
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Run boot cycles in batches to see progress
|
|
66
|
+
console.log('Running boot cycles...');
|
|
67
|
+
|
|
68
|
+
// Run first to get to the interesting part
|
|
69
|
+
Module._runCycles(4000000);
|
|
70
|
+
console.log(`At 4M cycles: PC=$${Module._getPC().toString(16).toUpperCase().padStart(4, '0')}`);
|
|
71
|
+
|
|
72
|
+
// Now run in smaller batches to catch the crash
|
|
73
|
+
for (let batch = 0; batch < 20; batch++) {
|
|
74
|
+
Module._runCycles(50000);
|
|
75
|
+
const pc = Module._getPC();
|
|
76
|
+
const track = Module._getDiskTrack(0);
|
|
77
|
+
const cycles = Module._getTotalCycles();
|
|
78
|
+
console.log(`${cycles} cycles: PC=$${pc.toString(16).toUpperCase().padStart(4, '0')}, Track=${track}`);
|
|
79
|
+
|
|
80
|
+
// Check if crashed
|
|
81
|
+
if (pc === 0) {
|
|
82
|
+
console.log('CRASHED at $0000!');
|
|
83
|
+
|
|
84
|
+
// Dump memory around $3900-$397F (boot stage 2 code)
|
|
85
|
+
console.log('\nBoot stage 2 code ($3900-$397F):');
|
|
86
|
+
let line = '';
|
|
87
|
+
for (let i = 0; i < 128; i++) {
|
|
88
|
+
const byte = Module._readMemory(0x3900 + i);
|
|
89
|
+
line += byte.toString(16).padStart(2, '0').toUpperCase() + ' ';
|
|
90
|
+
if ((i + 1) % 16 === 0) {
|
|
91
|
+
console.log('$' + (0x3900 + i - 15).toString(16).toUpperCase() + ': ' + line);
|
|
92
|
+
line = '';
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Dump memory around where crash happened ($3E50-$3EAF)
|
|
97
|
+
console.log('\nCode near crash location ($3E50-$3EAF):');
|
|
98
|
+
line = '';
|
|
99
|
+
for (let i = 0; i < 96; i++) {
|
|
100
|
+
const byte = Module._readMemory(0x3E50 + i);
|
|
101
|
+
line += byte.toString(16).padStart(2, '0').toUpperCase() + ' ';
|
|
102
|
+
if ((i + 1) % 16 === 0) {
|
|
103
|
+
console.log('$' + (0x3E50 + i - 15).toString(16).toUpperCase() + ': ' + line);
|
|
104
|
+
line = '';
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Dump stack area
|
|
109
|
+
console.log('\nStack area ($0100-$01FF):');
|
|
110
|
+
const sp = Module._getSP();
|
|
111
|
+
console.log(`SP = $${sp.toString(16).toUpperCase().padStart(2, '0')}`);
|
|
112
|
+
line = '';
|
|
113
|
+
for (let i = sp; i <= 0xFF; i++) {
|
|
114
|
+
const byte = Module._readMemory(0x0100 + i);
|
|
115
|
+
line += byte.toString(16).padStart(2, '0').toUpperCase() + ' ';
|
|
116
|
+
}
|
|
117
|
+
console.log('Stack: ' + line);
|
|
118
|
+
|
|
119
|
+
// Check what's at $0236 (the return address on the stack)
|
|
120
|
+
console.log('\nMemory at $0230-$024F:');
|
|
121
|
+
line = '';
|
|
122
|
+
for (let i = 0; i < 32; i++) {
|
|
123
|
+
const byte = Module._readMemory(0x0230 + i);
|
|
124
|
+
line += byte.toString(16).padStart(2, '0').toUpperCase() + ' ';
|
|
125
|
+
if ((i + 1) % 16 === 0) {
|
|
126
|
+
console.log('$' + (0x0230 + i - 15).toString(16).toUpperCase() + ': ' + line);
|
|
127
|
+
line = '';
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Check what's at $0000 (where we crashed)
|
|
132
|
+
console.log('\nMemory at $0000-$001F:');
|
|
133
|
+
line = '';
|
|
134
|
+
for (let i = 0; i < 32; i++) {
|
|
135
|
+
const byte = Module._readMemory(i);
|
|
136
|
+
line += byte.toString(16).padStart(2, '0').toUpperCase() + ' ';
|
|
137
|
+
if ((i + 1) % 16 === 0) {
|
|
138
|
+
console.log('$' + (i - 15).toString(16).toUpperCase() + ': ' + line);
|
|
139
|
+
line = '';
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Read memory at $0800 to see if boot sector loaded
|
|
148
|
+
console.log('\nMemory at $0800 (boot sector destination):');
|
|
149
|
+
let line = '';
|
|
150
|
+
for (let i = 0; i < 32; i++) {
|
|
151
|
+
const byte = Module._readMemory(0x0800 + i);
|
|
152
|
+
line += byte.toString(16).padStart(2, '0').toUpperCase() + ' ';
|
|
153
|
+
if ((i + 1) % 16 === 0) {
|
|
154
|
+
console.log(line);
|
|
155
|
+
line = '';
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Check the raw DSK data for sector 0, track 0
|
|
160
|
+
console.log('\nRaw DSK file sector 0, track 0:');
|
|
161
|
+
line = '';
|
|
162
|
+
for (let i = 0; i < 32; i++) {
|
|
163
|
+
line += diskData[i].toString(16).padStart(2, '0').toUpperCase() + ' ';
|
|
164
|
+
if ((i + 1) % 16 === 0) {
|
|
165
|
+
console.log(line);
|
|
166
|
+
line = '';
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// They should match if boot sector loaded correctly
|
|
171
|
+
console.log('\nComparing boot sector (256 bytes)...');
|
|
172
|
+
let match = true;
|
|
173
|
+
let mismatches = [];
|
|
174
|
+
for (let i = 0; i < 256; i++) {
|
|
175
|
+
const mem = Module._readMemory(0x0800 + i);
|
|
176
|
+
const disk = diskData[i];
|
|
177
|
+
if (mem !== disk) {
|
|
178
|
+
mismatches.push({
|
|
179
|
+
addr: 0x0800 + i,
|
|
180
|
+
mem: mem,
|
|
181
|
+
disk: disk
|
|
182
|
+
});
|
|
183
|
+
match = false;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (match) {
|
|
188
|
+
console.log('Boot sector matches DSK data! Loading is correct.');
|
|
189
|
+
} else {
|
|
190
|
+
console.log('Boot sector MOSTLY matches DSK data!');
|
|
191
|
+
console.log('Mismatches (likely runtime modifications):');
|
|
192
|
+
mismatches.forEach(m => {
|
|
193
|
+
const addr = m.addr.toString(16).toUpperCase().padStart(4, '0');
|
|
194
|
+
const mem = m.mem.toString(16).toUpperCase().padStart(2, '0');
|
|
195
|
+
const disk = m.disk.toString(16).toUpperCase().padStart(2, '0');
|
|
196
|
+
console.log(` $${addr}: memory=${mem} disk=${disk}`);
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Check CPU state
|
|
201
|
+
console.log('\nCPU State:');
|
|
202
|
+
console.log(`PC: $${Module._getPC().toString(16).toUpperCase().padStart(4, '0')}`);
|
|
203
|
+
console.log(`A: $${Module._getA().toString(16).toUpperCase().padStart(2, '0')}`);
|
|
204
|
+
console.log(`X: $${Module._getX().toString(16).toUpperCase().padStart(2, '0')}`);
|
|
205
|
+
console.log(`Y: $${Module._getY().toString(16).toUpperCase().padStart(2, '0')}`);
|
|
206
|
+
console.log(`SP: $${Module._getSP().toString(16).toUpperCase().padStart(2, '0')}`);
|
|
207
|
+
console.log(`Total cycles: ${Module._getTotalCycles()}`);
|
|
208
|
+
|
|
209
|
+
// Check disk state
|
|
210
|
+
console.log('\nDisk State:');
|
|
211
|
+
console.log(`Track: ${Module._getDiskTrack(0)}`);
|
|
212
|
+
console.log(`Motor: ${Module._getDiskMotorOn(0)}`);
|
|
213
|
+
|
|
214
|
+
// Check what DOS loads next - it should load sectors 1-9 of track 0 to RWTS area
|
|
215
|
+
// Let's check a few key memory locations
|
|
216
|
+
console.log('\nKey memory locations:');
|
|
217
|
+
console.log('$B600-$B60F (RWTS entry):');
|
|
218
|
+
line = '';
|
|
219
|
+
for (let i = 0; i < 16; i++) {
|
|
220
|
+
const byte = Module._readMemory(0xB600 + i);
|
|
221
|
+
line += byte.toString(16).padStart(2, '0').toUpperCase() + ' ';
|
|
222
|
+
}
|
|
223
|
+
console.log(line);
|
|
224
|
+
|
|
225
|
+
console.log('$9D00-$9D0F (DOS start):');
|
|
226
|
+
line = '';
|
|
227
|
+
for (let i = 0; i < 16; i++) {
|
|
228
|
+
const byte = Module._readMemory(0x9D00 + i);
|
|
229
|
+
line += byte.toString(16).padStart(2, '0').toUpperCase() + ' ';
|
|
230
|
+
}
|
|
231
|
+
console.log(line);
|
|
232
|
+
|
|
233
|
+
Module._free(diskPtr);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
main().catch(err => {
|
|
237
|
+
console.error('Error:', err);
|
|
238
|
+
process.exit(1);
|
|
239
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
import { createContext, runInContext } from 'vm';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = dirname(__filename);
|
|
8
|
+
|
|
9
|
+
async function loadWasmModule() {
|
|
10
|
+
const wasmJsPath = join(__dirname, '..', 'public', 'a2e.js');
|
|
11
|
+
const wasmDir = dirname(wasmJsPath);
|
|
12
|
+
const code = readFileSync(wasmJsPath, 'utf8');
|
|
13
|
+
const context = {
|
|
14
|
+
module: { exports: {} },
|
|
15
|
+
exports: {},
|
|
16
|
+
globalThis: global,
|
|
17
|
+
console: console,
|
|
18
|
+
require: (await import('module')).createRequire(import.meta.url),
|
|
19
|
+
__dirname: wasmDir,
|
|
20
|
+
__filename: wasmJsPath,
|
|
21
|
+
process: process,
|
|
22
|
+
WebAssembly: WebAssembly,
|
|
23
|
+
URL: URL,
|
|
24
|
+
TextDecoder: TextDecoder,
|
|
25
|
+
setTimeout: setTimeout,
|
|
26
|
+
clearTimeout: clearTimeout,
|
|
27
|
+
performance: { now: () => Date.now() }
|
|
28
|
+
};
|
|
29
|
+
createContext(context);
|
|
30
|
+
runInContext(code, context);
|
|
31
|
+
return context.module.exports;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function runTest() {
|
|
35
|
+
console.log('Loading...');
|
|
36
|
+
const createA2EModule = await loadWasmModule();
|
|
37
|
+
const Module = await createA2EModule();
|
|
38
|
+
Module._init();
|
|
39
|
+
|
|
40
|
+
const diskPath = join(__dirname, '..', 'public', 'Apple DOS 3.3 August 1980.dsk');
|
|
41
|
+
const diskData = readFileSync(diskPath);
|
|
42
|
+
const diskPtr = Module._malloc(diskData.length);
|
|
43
|
+
Module.HEAPU8.set(diskData, diskPtr);
|
|
44
|
+
const filename = 'test.dsk';
|
|
45
|
+
const filenamePtr = Module._malloc(filename.length + 1);
|
|
46
|
+
Module.stringToUTF8(filename, filenamePtr, filename.length + 1);
|
|
47
|
+
Module._insertDisk(0, diskPtr, diskData.length, filenamePtr);
|
|
48
|
+
Module._free(filenamePtr);
|
|
49
|
+
|
|
50
|
+
// Run to earlier point
|
|
51
|
+
console.log('Running to 4,050,000 cycles...');
|
|
52
|
+
while (Module._getTotalCycles() < 4050000) {
|
|
53
|
+
Module._runCycles(1000);
|
|
54
|
+
}
|
|
55
|
+
console.log('At cycle ' + Module._getTotalCycles());
|
|
56
|
+
|
|
57
|
+
// Capture last 200 instructions before crash
|
|
58
|
+
const history = [];
|
|
59
|
+
|
|
60
|
+
for (let i = 0; i < 500000; i++) {
|
|
61
|
+
const pc = Module._getPC();
|
|
62
|
+
const sp = Module._getSP();
|
|
63
|
+
const a = Module._getA();
|
|
64
|
+
const x = Module._getX();
|
|
65
|
+
const y = Module._getY();
|
|
66
|
+
const op = Module._readMemory(pc);
|
|
67
|
+
|
|
68
|
+
history.push({pc, sp, a, x, y, op});
|
|
69
|
+
if (history.length > 200) history.shift();
|
|
70
|
+
|
|
71
|
+
Module._stepInstruction();
|
|
72
|
+
|
|
73
|
+
const newPC = Module._getPC();
|
|
74
|
+
if (newPC === 0) {
|
|
75
|
+
console.log('\n*** CRASH at cycle ' + Module._getTotalCycles() + ' ***');
|
|
76
|
+
console.log('Last 100 instructions:');
|
|
77
|
+
for (let j = Math.max(0, history.length - 100); j < history.length; j++) {
|
|
78
|
+
const h = history[j];
|
|
79
|
+
console.log('$' + h.pc.toString(16).toUpperCase().padStart(4,'0') +
|
|
80
|
+
' [' + h.op.toString(16).padStart(2,'0') + ']' +
|
|
81
|
+
' A=' + h.a.toString(16).padStart(2,'0') +
|
|
82
|
+
' X=' + h.x.toString(16).padStart(2,'0') +
|
|
83
|
+
' Y=' + h.y.toString(16).padStart(2,'0') +
|
|
84
|
+
' SP=' + h.sp.toString(16).padStart(2,'0'));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
console.log('\nStack ($01C0-$01FF):');
|
|
88
|
+
for (let row = 0; row < 4; row++) {
|
|
89
|
+
let line = '$' + (0x01C0 + row*16).toString(16).toUpperCase() + ': ';
|
|
90
|
+
for (let col = 0; col < 16; col++) {
|
|
91
|
+
line += Module._readMemory(0x01C0 + row*16 + col).toString(16).padStart(2,'0') + ' ';
|
|
92
|
+
}
|
|
93
|
+
console.log(line);
|
|
94
|
+
}
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
Module._free(diskPtr);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
runTest().catch(err => { console.error(err); process.exit(1); });
|