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,262 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* pascal.cpp - Apple Pascal filesystem reader for disk image browsing
|
|
3
|
+
*
|
|
4
|
+
* Written by
|
|
5
|
+
* Mike Daley <michael_daley@icloud.com>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#include "pascal.hpp"
|
|
9
|
+
#include <cstring>
|
|
10
|
+
|
|
11
|
+
namespace a2e {
|
|
12
|
+
|
|
13
|
+
void Pascal::readBlock(const uint8_t* data, size_t size, int blockNum,
|
|
14
|
+
bool dosOrder, uint8_t* out) {
|
|
15
|
+
memset(out, 0, BLOCK_SIZE);
|
|
16
|
+
|
|
17
|
+
if (size <= static_cast<size_t>(DISK_140K_SIZE)) {
|
|
18
|
+
// 140K disk - block N is at track N/8, sectors (N%8)*2 and (N%8)*2+1
|
|
19
|
+
int track = blockNum / 8;
|
|
20
|
+
int blockInTrack = blockNum % 8;
|
|
21
|
+
int prodosSector1 = blockInTrack * 2;
|
|
22
|
+
int prodosSector2 = blockInTrack * 2 + 1;
|
|
23
|
+
|
|
24
|
+
int sector1 = dosOrder ? PRODOS_TO_DOS_SECTOR[prodosSector1] : prodosSector1;
|
|
25
|
+
int sector2 = dosOrder ? PRODOS_TO_DOS_SECTOR[prodosSector2] : prodosSector2;
|
|
26
|
+
|
|
27
|
+
int offset1 = (track * 16 + sector1) * 256;
|
|
28
|
+
int offset2 = (track * 16 + sector2) * 256;
|
|
29
|
+
|
|
30
|
+
if (offset1 + 256 <= static_cast<int>(size)) {
|
|
31
|
+
memcpy(out, data + offset1, 256);
|
|
32
|
+
}
|
|
33
|
+
if (offset2 + 256 <= static_cast<int>(size)) {
|
|
34
|
+
memcpy(out + 256, data + offset2, 256);
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
// Larger disk - blocks are sequential
|
|
38
|
+
int offset = blockNum * BLOCK_SIZE;
|
|
39
|
+
if (offset + BLOCK_SIZE <= static_cast<int>(size)) {
|
|
40
|
+
memcpy(out, data + offset, BLOCK_SIZE);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
bool Pascal::isValidVolumeHeader(const uint8_t* dirData) {
|
|
46
|
+
// Volume header is the first 26-byte entry in the directory (blocks 2-5)
|
|
47
|
+
// Bytes 0-1: first block of volume (must be 0)
|
|
48
|
+
// Bytes 2-3: next block after directory (must be 6)
|
|
49
|
+
// Byte 4: file type (must be 0 for volume header)
|
|
50
|
+
// Byte 6: volume name length (1-7)
|
|
51
|
+
// Bytes 7-13: volume name characters
|
|
52
|
+
// Bytes 14-15: total blocks on volume
|
|
53
|
+
// Bytes 16-17: number of files
|
|
54
|
+
|
|
55
|
+
uint16_t firstBlock = dirData[0] | (dirData[1] << 8);
|
|
56
|
+
uint16_t nextBlock = dirData[2] | (dirData[3] << 8);
|
|
57
|
+
uint8_t fileType = dirData[4];
|
|
58
|
+
uint8_t nameLen = dirData[6];
|
|
59
|
+
uint16_t totalBlocks = dirData[14] | (dirData[15] << 8);
|
|
60
|
+
uint16_t fileCount = dirData[16] | (dirData[17] << 8);
|
|
61
|
+
|
|
62
|
+
// Validate volume header fields
|
|
63
|
+
if (firstBlock != 0) return false;
|
|
64
|
+
if (nextBlock != 6) return false;
|
|
65
|
+
if (fileType != 0) return false;
|
|
66
|
+
if (nameLen < 1 || nameLen > 7) return false;
|
|
67
|
+
if (fileCount > MAX_DIR_ENTRIES) return false;
|
|
68
|
+
|
|
69
|
+
// Validate volume name characters (should be printable ASCII)
|
|
70
|
+
for (int i = 0; i < nameLen; i++) {
|
|
71
|
+
uint8_t ch = dirData[7 + i];
|
|
72
|
+
if (ch < 0x20 || ch > 0x7E) return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Total blocks should be reasonable (280 for 140K, could be larger for other media)
|
|
76
|
+
if (totalBlocks == 0 || totalBlocks > 65535) return false;
|
|
77
|
+
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
bool Pascal::detectSectorOrder(const uint8_t* data, size_t size, bool* dosOrder) {
|
|
82
|
+
uint8_t dirData[DIR_BLOCKS * BLOCK_SIZE];
|
|
83
|
+
|
|
84
|
+
// Try ProDOS order first
|
|
85
|
+
for (int i = 0; i < DIR_BLOCKS; i++) {
|
|
86
|
+
readBlock(data, size, DIR_START_BLOCK + i, false, dirData + i * BLOCK_SIZE);
|
|
87
|
+
}
|
|
88
|
+
if (isValidVolumeHeader(dirData)) {
|
|
89
|
+
*dosOrder = false;
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Try DOS order
|
|
94
|
+
for (int i = 0; i < DIR_BLOCKS; i++) {
|
|
95
|
+
readBlock(data, size, DIR_START_BLOCK + i, true, dirData + i * BLOCK_SIZE);
|
|
96
|
+
}
|
|
97
|
+
if (isValidVolumeHeader(dirData)) {
|
|
98
|
+
*dosOrder = true;
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const char* Pascal::getFileTypeName(uint8_t fileType) {
|
|
106
|
+
switch (fileType) {
|
|
107
|
+
case 0: return "VOL";
|
|
108
|
+
case 1: return "BAD";
|
|
109
|
+
case 2: return "CODE";
|
|
110
|
+
case 3: return "TEXT";
|
|
111
|
+
case 4: return "INFO";
|
|
112
|
+
case 5: return "DATA";
|
|
113
|
+
case 6: return "GRAF";
|
|
114
|
+
case 7: return "FOTO";
|
|
115
|
+
case 8: return "SDIR";
|
|
116
|
+
default: return "???";
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
bool Pascal::isPascal(const uint8_t* data, size_t size) {
|
|
121
|
+
if (size < static_cast<size_t>(DISK_140K_SIZE)) return false;
|
|
122
|
+
bool dosOrder;
|
|
123
|
+
return detectSectorOrder(data, size, &dosOrder);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
bool Pascal::parseVolumeInfo(const uint8_t* data, size_t size, PascalVolumeInfo* info) {
|
|
127
|
+
if (size < static_cast<size_t>(DISK_140K_SIZE)) return false;
|
|
128
|
+
|
|
129
|
+
bool dosOrder;
|
|
130
|
+
if (!detectSectorOrder(data, size, &dosOrder)) return false;
|
|
131
|
+
|
|
132
|
+
// Read the full directory area (blocks 2-5)
|
|
133
|
+
uint8_t dirData[DIR_BLOCKS * BLOCK_SIZE];
|
|
134
|
+
for (int i = 0; i < DIR_BLOCKS; i++) {
|
|
135
|
+
readBlock(data, size, DIR_START_BLOCK + i, dosOrder, dirData + i * BLOCK_SIZE);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Parse volume header (first 26-byte entry)
|
|
139
|
+
uint8_t nameLen = dirData[6];
|
|
140
|
+
if (nameLen > 7) nameLen = 7;
|
|
141
|
+
for (int i = 0; i < nameLen; i++) {
|
|
142
|
+
info->volumeName[i] = dirData[7 + i] & 0x7F;
|
|
143
|
+
}
|
|
144
|
+
info->volumeName[nameLen] = '\0';
|
|
145
|
+
|
|
146
|
+
info->totalBlocks = dirData[14] | (dirData[15] << 8);
|
|
147
|
+
info->fileCount = dirData[16] | (dirData[17] << 8);
|
|
148
|
+
info->useDOSSectorOrder = dosOrder;
|
|
149
|
+
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
int Pascal::readCatalog(const uint8_t* data, size_t size,
|
|
154
|
+
PascalCatalogEntry* entries, int maxEntries) {
|
|
155
|
+
PascalVolumeInfo info;
|
|
156
|
+
if (!parseVolumeInfo(data, size, &info)) return 0;
|
|
157
|
+
|
|
158
|
+
bool dosOrder = info.useDOSSectorOrder;
|
|
159
|
+
|
|
160
|
+
// Read the full directory area (blocks 2-5)
|
|
161
|
+
uint8_t dirData[DIR_BLOCKS * BLOCK_SIZE];
|
|
162
|
+
for (int i = 0; i < DIR_BLOCKS; i++) {
|
|
163
|
+
readBlock(data, size, DIR_START_BLOCK + i, dosOrder, dirData + i * BLOCK_SIZE);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
int count = 0;
|
|
167
|
+
int numFiles = info.fileCount;
|
|
168
|
+
if (numFiles > MAX_DIR_ENTRIES) numFiles = MAX_DIR_ENTRIES;
|
|
169
|
+
|
|
170
|
+
// File entries start at entry 1 (entry 0 is the volume header)
|
|
171
|
+
for (int i = 1; i <= numFiles && count < maxEntries; i++) {
|
|
172
|
+
int offset = i * DIR_ENTRY_SIZE;
|
|
173
|
+
if (offset + DIR_ENTRY_SIZE > DIR_BLOCKS * BLOCK_SIZE) break;
|
|
174
|
+
|
|
175
|
+
const uint8_t* entry = dirData + offset;
|
|
176
|
+
|
|
177
|
+
PascalCatalogEntry& e = entries[count];
|
|
178
|
+
|
|
179
|
+
e.startBlock = entry[0] | (entry[1] << 8);
|
|
180
|
+
e.nextBlock = entry[2] | (entry[3] << 8);
|
|
181
|
+
e.fileType = entry[4];
|
|
182
|
+
|
|
183
|
+
// Skip deleted entries (startBlock == 0 or nextBlock <= startBlock)
|
|
184
|
+
if (e.startBlock == 0 || e.nextBlock <= e.startBlock) continue;
|
|
185
|
+
|
|
186
|
+
// File name: length-prefixed at byte 6
|
|
187
|
+
uint8_t nameLen = entry[6];
|
|
188
|
+
if (nameLen > 15) nameLen = 15;
|
|
189
|
+
for (int j = 0; j < nameLen; j++) {
|
|
190
|
+
e.filename[j] = entry[7 + j] & 0x7F;
|
|
191
|
+
}
|
|
192
|
+
e.filename[nameLen] = '\0';
|
|
193
|
+
|
|
194
|
+
// Skip entries with empty names
|
|
195
|
+
if (nameLen == 0) continue;
|
|
196
|
+
|
|
197
|
+
const char* typeName = getFileTypeName(e.fileType);
|
|
198
|
+
strncpy(e.fileTypeName, typeName, sizeof(e.fileTypeName) - 1);
|
|
199
|
+
e.fileTypeName[sizeof(e.fileTypeName) - 1] = '\0';
|
|
200
|
+
|
|
201
|
+
e.bytesInLastBlock = entry[22] | (entry[23] << 8);
|
|
202
|
+
e.modDate = entry[24] | (entry[25] << 8);
|
|
203
|
+
|
|
204
|
+
// Compute file size
|
|
205
|
+
int numBlocks = e.nextBlock - e.startBlock;
|
|
206
|
+
if (numBlocks > 1) {
|
|
207
|
+
e.fileSize = (numBlocks - 1) * BLOCK_SIZE + e.bytesInLastBlock;
|
|
208
|
+
} else if (numBlocks == 1) {
|
|
209
|
+
e.fileSize = e.bytesInLastBlock;
|
|
210
|
+
} else {
|
|
211
|
+
e.fileSize = 0;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
count++;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return count;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
int Pascal::readFile(const uint8_t* data, size_t size,
|
|
221
|
+
const PascalCatalogEntry* entry,
|
|
222
|
+
uint8_t* outBuf, int outMax) {
|
|
223
|
+
PascalVolumeInfo info;
|
|
224
|
+
if (!parseVolumeInfo(data, size, &info)) return 0;
|
|
225
|
+
bool dosOrder = info.useDOSSectorOrder;
|
|
226
|
+
|
|
227
|
+
int numBlocks = entry->nextBlock - entry->startBlock;
|
|
228
|
+
if (numBlocks <= 0) return 0;
|
|
229
|
+
|
|
230
|
+
int bytesRead = 0;
|
|
231
|
+
for (int i = 0; i < numBlocks && bytesRead < outMax; i++) {
|
|
232
|
+
uint8_t block[BLOCK_SIZE];
|
|
233
|
+
readBlock(data, size, entry->startBlock + i, dosOrder, block);
|
|
234
|
+
|
|
235
|
+
int bytesToCopy;
|
|
236
|
+
if (i == numBlocks - 1) {
|
|
237
|
+
// Last block - use bytesInLastBlock
|
|
238
|
+
bytesToCopy = entry->bytesInLastBlock;
|
|
239
|
+
if (bytesToCopy == 0) bytesToCopy = BLOCK_SIZE;
|
|
240
|
+
} else {
|
|
241
|
+
bytesToCopy = BLOCK_SIZE;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (bytesRead + bytesToCopy > outMax) {
|
|
245
|
+
bytesToCopy = outMax - bytesRead;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
memcpy(outBuf + bytesRead, block, bytesToCopy);
|
|
249
|
+
bytesRead += bytesToCopy;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return bytesRead;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
int Pascal::mapFileTypeForViewer(uint8_t pascalType) {
|
|
256
|
+
switch (pascalType) {
|
|
257
|
+
case 3: return 0x00; // TEXT -> Text viewer
|
|
258
|
+
default: return -1; // Everything else -> hex dump
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
} // namespace a2e
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* pascal.hpp - Apple Pascal 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 PascalCatalogEntry {
|
|
16
|
+
char filename[16]; // Max 15 chars + null
|
|
17
|
+
uint8_t fileType; // 0-8
|
|
18
|
+
char fileTypeName[5]; // "CODE", "TEXT", "DATA", etc.
|
|
19
|
+
uint16_t startBlock; // First block of file
|
|
20
|
+
uint16_t nextBlock; // First block past end
|
|
21
|
+
uint16_t bytesInLastBlock;// Bytes used in final block
|
|
22
|
+
uint32_t fileSize; // Computed: (nextBlock - startBlock - 1) * 512 + bytesInLastBlock
|
|
23
|
+
uint16_t modDate; // Raw Pascal date
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
struct PascalVolumeInfo {
|
|
27
|
+
char volumeName[8]; // Max 7 chars + null
|
|
28
|
+
uint16_t totalBlocks;
|
|
29
|
+
uint16_t fileCount;
|
|
30
|
+
bool useDOSSectorOrder; // DOS order (.DO/.DSK) vs ProDOS order (.PO)
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
class Pascal {
|
|
34
|
+
public:
|
|
35
|
+
static constexpr int BLOCK_SIZE = 512;
|
|
36
|
+
static constexpr int DISK_140K_SIZE = 143360;
|
|
37
|
+
static constexpr int DIR_START_BLOCK = 2;
|
|
38
|
+
static constexpr int DIR_BLOCKS = 4; // Blocks 2-5
|
|
39
|
+
static constexpr int DIR_ENTRY_SIZE = 26;
|
|
40
|
+
static constexpr int MAX_DIR_ENTRIES = 77; // (2048 / 26) - 1
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check if disk data is Apple Pascal format.
|
|
44
|
+
* Should be called after isProDOS() fails to avoid false positives.
|
|
45
|
+
*/
|
|
46
|
+
static bool isPascal(const uint8_t* data, size_t size);
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Parse volume information
|
|
50
|
+
*/
|
|
51
|
+
static bool parseVolumeInfo(const uint8_t* data, size_t size, PascalVolumeInfo* info);
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Read the catalog (flat directory).
|
|
55
|
+
* Returns number of entries written.
|
|
56
|
+
*/
|
|
57
|
+
static int readCatalog(const uint8_t* data, size_t size,
|
|
58
|
+
PascalCatalogEntry* entries, int maxEntries);
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Read a file's contents based on its catalog entry.
|
|
62
|
+
* Returns bytes written.
|
|
63
|
+
*/
|
|
64
|
+
static int readFile(const uint8_t* data, size_t size,
|
|
65
|
+
const PascalCatalogEntry* entry,
|
|
66
|
+
uint8_t* outBuf, int outMax);
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Map Pascal file type to DOS 3.3 type for viewer.
|
|
70
|
+
* Returns -1 if no mapping (use hex dump).
|
|
71
|
+
*/
|
|
72
|
+
static int mapFileTypeForViewer(uint8_t pascalType);
|
|
73
|
+
|
|
74
|
+
private:
|
|
75
|
+
static void readBlock(const uint8_t* data, size_t size, int blockNum,
|
|
76
|
+
bool dosOrder, uint8_t* out);
|
|
77
|
+
static bool detectSectorOrder(const uint8_t* data, size_t size, bool* dosOrder);
|
|
78
|
+
static bool isValidVolumeHeader(const uint8_t* dirData);
|
|
79
|
+
static const char* getFileTypeName(uint8_t fileType);
|
|
80
|
+
|
|
81
|
+
// ProDOS-to-DOS sector conversion table (same interleave as ProDOS)
|
|
82
|
+
static constexpr uint8_t PRODOS_TO_DOS_SECTOR[16] = {
|
|
83
|
+
0, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 15
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
} // namespace a2e
|