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.
Files changed (295) hide show
  1. package/.clangd +5 -0
  2. package/.mcp.json +12 -0
  3. package/CLAUDE.md +362 -0
  4. package/CMakeLists.txt +774 -0
  5. package/LICENSE +21 -0
  6. package/README.md +392 -0
  7. package/build-wasm/generated/roms.cpp +2447 -0
  8. package/docker-compose.staging.yml +9 -0
  9. package/docs/basic-rom-disassembly.md +6663 -0
  10. package/docs/softswitch-comparison.md +273 -0
  11. package/docs/thunderclock-debug.md +89 -0
  12. package/examples/cube.bas +72 -0
  13. package/examples/hello.s +55 -0
  14. package/examples/scroll.s +140 -0
  15. package/package.json +18 -0
  16. package/public/assets/apple-logo-old.png +0 -0
  17. package/public/assets/apple-logo.png +0 -0
  18. package/public/assets/drive-closed-light-on.png +0 -0
  19. package/public/assets/drive-closed.png +0 -0
  20. package/public/assets/drive-open-light-on.png +0 -0
  21. package/public/assets/drive-open.png +0 -0
  22. package/public/audio-worklet.js +82 -0
  23. package/public/disks/Apple DOS 3.3 January 1983.dsk +0 -0
  24. package/public/disks/ProDOS 2.4.3.po +0 -0
  25. package/public/disks/h32mb.2mg +0 -0
  26. package/public/disks/library.json +26 -0
  27. package/public/docs/llms/llm-assembler.txt +90 -0
  28. package/public/docs/llms/llm-basic-program.txt +256 -0
  29. package/public/docs/llms/llm-disk-drives.txt +72 -0
  30. package/public/docs/llms/llm-file-explorer.txt +50 -0
  31. package/public/docs/llms/llm-hard-drives.txt +80 -0
  32. package/public/docs/llms/llm-main.txt +51 -0
  33. package/public/docs/llms/llm-slot-configuration.txt +66 -0
  34. package/public/icons/icon-192.svg +4 -0
  35. package/public/icons/icon-512.svg +4 -0
  36. package/public/index.html +661 -0
  37. package/public/llms.txt +49 -0
  38. package/public/manifest.json +29 -0
  39. package/public/shaders/burnin.glsl +22 -0
  40. package/public/shaders/crt.glsl +706 -0
  41. package/public/shaders/edge.glsl +109 -0
  42. package/public/shaders/vertex.glsl +8 -0
  43. package/public/sw.js +186 -0
  44. package/roms/341-0027.bin +0 -0
  45. package/roms/341-0160-A-US-UK.bin +0 -0
  46. package/roms/341-0160-A.bin +0 -0
  47. package/roms/342-0273-A-US-UK.bin +0 -0
  48. package/roms/342-0349-B-C0-FF.bin +0 -0
  49. package/roms/Apple Mouse Interface Card ROM - 342-0270-C.bin +0 -0
  50. package/roms/Thunderclock Plus ROM.bin +0 -0
  51. package/scripts/generate_roms.sh +69 -0
  52. package/src/bindings/wasm_interface.cpp +1940 -0
  53. package/src/core/assembler/assembler.cpp +1239 -0
  54. package/src/core/assembler/assembler.hpp +115 -0
  55. package/src/core/audio/audio.cpp +160 -0
  56. package/src/core/audio/audio.hpp +81 -0
  57. package/src/core/basic/basic_detokenizer.cpp +436 -0
  58. package/src/core/basic/basic_detokenizer.hpp +41 -0
  59. package/src/core/basic/basic_tokenizer.cpp +286 -0
  60. package/src/core/basic/basic_tokenizer.hpp +26 -0
  61. package/src/core/basic/basic_tokens.hpp +295 -0
  62. package/src/core/cards/disk2_card.cpp +568 -0
  63. package/src/core/cards/disk2_card.hpp +316 -0
  64. package/src/core/cards/expansion_card.hpp +185 -0
  65. package/src/core/cards/mockingboard/ay8910.cpp +616 -0
  66. package/src/core/cards/mockingboard/ay8910.hpp +159 -0
  67. package/src/core/cards/mockingboard/via6522.cpp +530 -0
  68. package/src/core/cards/mockingboard/via6522.hpp +163 -0
  69. package/src/core/cards/mockingboard_card.cpp +312 -0
  70. package/src/core/cards/mockingboard_card.hpp +159 -0
  71. package/src/core/cards/mouse_card.cpp +654 -0
  72. package/src/core/cards/mouse_card.hpp +190 -0
  73. package/src/core/cards/smartport/block_device.cpp +202 -0
  74. package/src/core/cards/smartport/block_device.hpp +60 -0
  75. package/src/core/cards/smartport/smartport_card.cpp +603 -0
  76. package/src/core/cards/smartport/smartport_card.hpp +120 -0
  77. package/src/core/cards/thunderclock_card.cpp +237 -0
  78. package/src/core/cards/thunderclock_card.hpp +122 -0
  79. package/src/core/cpu/cpu6502.cpp +1609 -0
  80. package/src/core/cpu/cpu6502.hpp +203 -0
  81. package/src/core/debug/condition_evaluator.cpp +470 -0
  82. package/src/core/debug/condition_evaluator.hpp +87 -0
  83. package/src/core/disassembler/disassembler.cpp +552 -0
  84. package/src/core/disassembler/disassembler.hpp +171 -0
  85. package/src/core/disk-image/disk_image.hpp +267 -0
  86. package/src/core/disk-image/dsk_disk_image.cpp +827 -0
  87. package/src/core/disk-image/dsk_disk_image.hpp +204 -0
  88. package/src/core/disk-image/gcr_encoding.cpp +147 -0
  89. package/src/core/disk-image/gcr_encoding.hpp +78 -0
  90. package/src/core/disk-image/woz_disk_image.cpp +1049 -0
  91. package/src/core/disk-image/woz_disk_image.hpp +343 -0
  92. package/src/core/emulator.cpp +2126 -0
  93. package/src/core/emulator.hpp +434 -0
  94. package/src/core/filesystem/dos33.cpp +178 -0
  95. package/src/core/filesystem/dos33.hpp +66 -0
  96. package/src/core/filesystem/pascal.cpp +262 -0
  97. package/src/core/filesystem/pascal.hpp +87 -0
  98. package/src/core/filesystem/prodos.cpp +369 -0
  99. package/src/core/filesystem/prodos.hpp +119 -0
  100. package/src/core/input/keyboard.cpp +227 -0
  101. package/src/core/input/keyboard.hpp +111 -0
  102. package/src/core/mmu/mmu.cpp +1387 -0
  103. package/src/core/mmu/mmu.hpp +236 -0
  104. package/src/core/types.hpp +196 -0
  105. package/src/core/video/video.cpp +680 -0
  106. package/src/core/video/video.hpp +156 -0
  107. package/src/css/assembler-editor.css +1617 -0
  108. package/src/css/base.css +470 -0
  109. package/src/css/basic-debugger.css +791 -0
  110. package/src/css/basic-editor.css +792 -0
  111. package/src/css/controls.css +783 -0
  112. package/src/css/cpu-debugger.css +1413 -0
  113. package/src/css/debug-base.css +160 -0
  114. package/src/css/debug-windows.css +6455 -0
  115. package/src/css/disk-drives.css +406 -0
  116. package/src/css/documentation.css +392 -0
  117. package/src/css/file-explorer.css +867 -0
  118. package/src/css/hard-drive.css +180 -0
  119. package/src/css/layout.css +217 -0
  120. package/src/css/memory-windows.css +798 -0
  121. package/src/css/modals.css +510 -0
  122. package/src/css/monitor.css +425 -0
  123. package/src/css/release-notes.css +101 -0
  124. package/src/css/responsive.css +400 -0
  125. package/src/css/rule-builder.css +340 -0
  126. package/src/css/save-states.css +201 -0
  127. package/src/css/settings-windows.css +1231 -0
  128. package/src/css/window-switcher.css +150 -0
  129. package/src/js/agent/agent-manager.js +643 -0
  130. package/src/js/agent/agent-tools.js +293 -0
  131. package/src/js/agent/agent-version-tools.js +131 -0
  132. package/src/js/agent/assembler-tools.js +357 -0
  133. package/src/js/agent/basic-program-tools.js +894 -0
  134. package/src/js/agent/disk-tools.js +417 -0
  135. package/src/js/agent/file-explorer-tools.js +269 -0
  136. package/src/js/agent/index.js +13 -0
  137. package/src/js/agent/main-tools.js +222 -0
  138. package/src/js/agent/slot-tools.js +303 -0
  139. package/src/js/agent/smartport-tools.js +257 -0
  140. package/src/js/agent/window-tools.js +80 -0
  141. package/src/js/audio/audio-driver.js +417 -0
  142. package/src/js/audio/audio-worklet.js +85 -0
  143. package/src/js/audio/index.js +8 -0
  144. package/src/js/config/default-layout.js +34 -0
  145. package/src/js/config/version.js +8 -0
  146. package/src/js/data/apple2-rom-routines.js +577 -0
  147. package/src/js/debug/assembler-editor-window.js +2993 -0
  148. package/src/js/debug/basic-breakpoint-manager.js +529 -0
  149. package/src/js/debug/basic-program-parser.js +436 -0
  150. package/src/js/debug/basic-program-window.js +2594 -0
  151. package/src/js/debug/basic-variable-inspector.js +447 -0
  152. package/src/js/debug/breakpoint-manager.js +472 -0
  153. package/src/js/debug/cpu-debugger-window.js +2396 -0
  154. package/src/js/debug/index.js +22 -0
  155. package/src/js/debug/label-manager.js +238 -0
  156. package/src/js/debug/memory-browser-window.js +416 -0
  157. package/src/js/debug/memory-heat-map-window.js +481 -0
  158. package/src/js/debug/memory-map-window.js +206 -0
  159. package/src/js/debug/mockingboard-window.js +882 -0
  160. package/src/js/debug/mouse-card-window.js +355 -0
  161. package/src/js/debug/rule-builder-window.js +648 -0
  162. package/src/js/debug/soft-switch-window.js +458 -0
  163. package/src/js/debug/stack-viewer-window.js +221 -0
  164. package/src/js/debug/symbols.js +416 -0
  165. package/src/js/debug/trace-panel.js +291 -0
  166. package/src/js/debug/zero-page-watch-window.js +297 -0
  167. package/src/js/disk-manager/disk-drives-window.js +212 -0
  168. package/src/js/disk-manager/disk-operations.js +284 -0
  169. package/src/js/disk-manager/disk-persistence.js +301 -0
  170. package/src/js/disk-manager/disk-surface-renderer.js +388 -0
  171. package/src/js/disk-manager/drive-sounds.js +139 -0
  172. package/src/js/disk-manager/hard-drive-manager.js +481 -0
  173. package/src/js/disk-manager/hard-drive-persistence.js +187 -0
  174. package/src/js/disk-manager/hard-drive-window.js +57 -0
  175. package/src/js/disk-manager/index.js +890 -0
  176. package/src/js/display/display-settings-window.js +383 -0
  177. package/src/js/display/index.js +10 -0
  178. package/src/js/display/screen-window.js +342 -0
  179. package/src/js/display/webgl-renderer.js +705 -0
  180. package/src/js/file-explorer/disassembler.js +574 -0
  181. package/src/js/file-explorer/dos33.js +266 -0
  182. package/src/js/file-explorer/file-viewer.js +359 -0
  183. package/src/js/file-explorer/index.js +1261 -0
  184. package/src/js/file-explorer/prodos.js +549 -0
  185. package/src/js/file-explorer/utils.js +67 -0
  186. package/src/js/help/documentation-window.js +1096 -0
  187. package/src/js/help/index.js +10 -0
  188. package/src/js/help/release-notes-window.js +85 -0
  189. package/src/js/help/release-notes.js +612 -0
  190. package/src/js/input/gamepad-handler.js +176 -0
  191. package/src/js/input/index.js +12 -0
  192. package/src/js/input/input-handler.js +396 -0
  193. package/src/js/input/joystick-window.js +404 -0
  194. package/src/js/input/mouse-handler.js +99 -0
  195. package/src/js/input/text-selection.js +462 -0
  196. package/src/js/main.js +653 -0
  197. package/src/js/state/index.js +15 -0
  198. package/src/js/state/save-states-window.js +393 -0
  199. package/src/js/state/state-manager.js +409 -0
  200. package/src/js/state/state-persistence.js +218 -0
  201. package/src/js/ui/confirm.js +43 -0
  202. package/src/js/ui/disk-drive-positioner.js +347 -0
  203. package/src/js/ui/reminder-controller.js +129 -0
  204. package/src/js/ui/slot-configuration-window.js +560 -0
  205. package/src/js/ui/theme-manager.js +61 -0
  206. package/src/js/ui/toast.js +44 -0
  207. package/src/js/ui/ui-controller.js +897 -0
  208. package/src/js/ui/window-switcher.js +275 -0
  209. package/src/js/utils/basic-autocomplete.js +832 -0
  210. package/src/js/utils/basic-highlighting.js +473 -0
  211. package/src/js/utils/basic-tokenizer.js +153 -0
  212. package/src/js/utils/basic-tokens.js +117 -0
  213. package/src/js/utils/constants.js +28 -0
  214. package/src/js/utils/indexeddb-helper.js +225 -0
  215. package/src/js/utils/merlin-editor-support.js +905 -0
  216. package/src/js/utils/merlin-highlighting.js +551 -0
  217. package/src/js/utils/storage.js +125 -0
  218. package/src/js/utils/string-utils.js +19 -0
  219. package/src/js/utils/wasm-memory.js +54 -0
  220. package/src/js/windows/base-window.js +690 -0
  221. package/src/js/windows/index.js +9 -0
  222. package/src/js/windows/window-manager.js +375 -0
  223. package/tests/catch2/catch.hpp +17976 -0
  224. package/tests/common/basic_program_builder.cpp +119 -0
  225. package/tests/common/basic_program_builder.hpp +209 -0
  226. package/tests/common/disk_image_builder.cpp +444 -0
  227. package/tests/common/disk_image_builder.hpp +141 -0
  228. package/tests/common/test_helpers.hpp +118 -0
  229. package/tests/gcr/gcr-test.cpp +142 -0
  230. package/tests/integration/check-rom.js +70 -0
  231. package/tests/integration/compare-boot.js +239 -0
  232. package/tests/integration/crash-trace.js +102 -0
  233. package/tests/integration/disk-boot-test.js +264 -0
  234. package/tests/integration/memory-crash.js +108 -0
  235. package/tests/integration/nibble-read-test.js +249 -0
  236. package/tests/integration/phase-test.js +159 -0
  237. package/tests/integration/test_emulator.cpp +291 -0
  238. package/tests/integration/test_emulator_basic.cpp +91 -0
  239. package/tests/integration/test_emulator_debug.cpp +344 -0
  240. package/tests/integration/test_emulator_disk.cpp +153 -0
  241. package/tests/integration/test_emulator_state.cpp +163 -0
  242. package/tests/klaus/6502_functional_test.bin +0 -0
  243. package/tests/klaus/65C02_extended_opcodes_test.bin +0 -0
  244. package/tests/klaus/klaus_6502_test.cpp +184 -0
  245. package/tests/klaus/klaus_65c02_test.cpp +197 -0
  246. package/tests/thunderclock/thunderclock_mmu_test.cpp +304 -0
  247. package/tests/thunderclock/thunderclock_test.cpp +550 -0
  248. package/tests/unit/test_assembler.cpp +521 -0
  249. package/tests/unit/test_audio.cpp +196 -0
  250. package/tests/unit/test_ay8910.cpp +311 -0
  251. package/tests/unit/test_basic_detokenizer.cpp +265 -0
  252. package/tests/unit/test_basic_tokenizer.cpp +382 -0
  253. package/tests/unit/test_block_device.cpp +259 -0
  254. package/tests/unit/test_condition_evaluator.cpp +219 -0
  255. package/tests/unit/test_cpu6502.cpp +1301 -0
  256. package/tests/unit/test_cpu_addressing.cpp +361 -0
  257. package/tests/unit/test_cpu_cycle_counts.cpp +409 -0
  258. package/tests/unit/test_cpu_decimal.cpp +166 -0
  259. package/tests/unit/test_cpu_interrupts.cpp +285 -0
  260. package/tests/unit/test_disassembler.cpp +323 -0
  261. package/tests/unit/test_disk2_card.cpp +330 -0
  262. package/tests/unit/test_dos33.cpp +273 -0
  263. package/tests/unit/test_dsk_disk_image.cpp +315 -0
  264. package/tests/unit/test_expansion_card.cpp +178 -0
  265. package/tests/unit/test_gcr_encoding.cpp +232 -0
  266. package/tests/unit/test_keyboard.cpp +262 -0
  267. package/tests/unit/test_mmu.cpp +555 -0
  268. package/tests/unit/test_mmu_slots.cpp +323 -0
  269. package/tests/unit/test_mockingboard.cpp +352 -0
  270. package/tests/unit/test_mouse_card.cpp +386 -0
  271. package/tests/unit/test_pascal.cpp +248 -0
  272. package/tests/unit/test_prodos.cpp +259 -0
  273. package/tests/unit/test_smartport_card.cpp +321 -0
  274. package/tests/unit/test_thunderclock.cpp +354 -0
  275. package/tests/unit/test_via6522.cpp +323 -0
  276. package/tests/unit/test_video.cpp +319 -0
  277. package/tests/unit/test_woz_disk_image.cpp +257 -0
  278. package/vite.config.js +96 -0
  279. package/wiki/AI-Agent.md +372 -0
  280. package/wiki/Architecture-Overview.md +303 -0
  281. package/wiki/Audio-System.md +449 -0
  282. package/wiki/CPU-Emulation.md +477 -0
  283. package/wiki/Debugger.md +516 -0
  284. package/wiki/Disk-Drives.md +161 -0
  285. package/wiki/Disk-System-Internals.md +547 -0
  286. package/wiki/Display-Settings.md +88 -0
  287. package/wiki/Expansion-Slots.md +187 -0
  288. package/wiki/File-Explorer.md +259 -0
  289. package/wiki/Getting-Started.md +156 -0
  290. package/wiki/Home.md +69 -0
  291. package/wiki/Input-Devices.md +183 -0
  292. package/wiki/Keyboard-Shortcuts.md +158 -0
  293. package/wiki/Memory-System.md +364 -0
  294. package/wiki/Save-States.md +172 -0
  295. package/wiki/Video-Rendering.md +658 -0
@@ -0,0 +1,204 @@
1
+ /*
2
+ * dsk_disk_image.hpp - DSK/DO/PO raw sector disk image format support
3
+ *
4
+ * Written by
5
+ * Mike Daley <michael_daley@icloud.com>
6
+ */
7
+
8
+ #pragma once
9
+
10
+ #include "disk_image.hpp"
11
+ #include <array>
12
+ #include <cstdint>
13
+ #include <string>
14
+ #include <vector>
15
+
16
+ namespace a2e {
17
+
18
+ /**
19
+ * DskDiskImage - DSK/DO/PO disk image format support
20
+ *
21
+ * DSK is a raw sector image format commonly used for Apple II disk images.
22
+ * It stores 35 tracks x 16 sectors x 256 bytes = 143,360 bytes (140KB).
23
+ *
24
+ * There are two sector ordering schemes:
25
+ * - DOS order (.dsk, .do): Sectors stored in DOS 3.3 logical order
26
+ * - ProDOS order (.po): Sectors stored in ProDOS physical order
27
+ *
28
+ * This class converts the raw sector data to/from GCR-encoded nibbles
29
+ * on-the-fly, simulating how a real Disk II controller reads/writes.
30
+ */
31
+ class DskDiskImage : public DiskImage {
32
+ public:
33
+ // Disk geometry constants
34
+ static constexpr int TRACKS = 35;
35
+ static constexpr int SECTORS_PER_TRACK = 16;
36
+ static constexpr int BYTES_PER_SECTOR = 256;
37
+ static constexpr int TRACK_SIZE =
38
+ SECTORS_PER_TRACK * BYTES_PER_SECTOR; // 4096 bytes
39
+ static constexpr int DISK_SIZE = TRACKS * TRACK_SIZE; // 143360 bytes
40
+
41
+ // Nibble track size (approximate, varies slightly per track)
42
+ static constexpr int NIBBLES_PER_TRACK = 6656;
43
+
44
+ DskDiskImage();
45
+ ~DskDiskImage() override = default;
46
+
47
+ // ===== Loading =====
48
+ bool load(const uint8_t *data, size_t size,
49
+ const std::string &filename) override;
50
+ bool isLoaded() const override { return loaded_; }
51
+ Format getFormat() const override { return format_; }
52
+
53
+ // ===== Reset =====
54
+ void resetState() override;
55
+
56
+ // ===== Head Positioning =====
57
+ void setPhase(int phase, bool on) override;
58
+ int getQuarterTrack() const override { return quarter_track_; }
59
+ int getTrack() const override { return quarter_track_ / 4; }
60
+ void setQuarterTrack(int quarter_track) override {
61
+ quarter_track_ = std::max(0, std::min(quarter_track, (TRACKS - 1) * 4));
62
+ }
63
+
64
+ // ===== Geometry =====
65
+ int getTrackCount() const override { return TRACKS; }
66
+ bool hasData() const override;
67
+
68
+ // ===== Data Access =====
69
+ void advanceBitPosition(uint64_t current_cycles) override;
70
+ uint8_t readNibble() override;
71
+
72
+ // ===== Write Operations =====
73
+ void writeNibble(uint8_t nibble) override;
74
+
75
+ // ===== Bit-Level Access (for LSS) =====
76
+ uint8_t readBit() override;
77
+ void writeBit(uint8_t bit) override;
78
+
79
+ // ===== Status =====
80
+ bool isWriteProtected() const override { return write_protected_; }
81
+ bool isModified() const override { return modified_; }
82
+ std::string getFormatName() const override;
83
+ const uint8_t *getSectorData(size_t *size) const override;
84
+ const uint8_t *exportData(size_t *size) override;
85
+
86
+ // ===== Debug Methods =====
87
+ uint8_t getNibbleAt(int track, int position) const override;
88
+ int getTrackNibbleCount(int track) const override;
89
+ size_t getCurrentNibblePosition() const override { return bit_position_ / 8; }
90
+
91
+ // ===== DSK-specific =====
92
+
93
+ /**
94
+ * Check if this is a ProDOS-order image
95
+ */
96
+ bool isProDOSOrder() const { return format_ == Format::PO; }
97
+
98
+ /**
99
+ * Get the volume number (default 254)
100
+ */
101
+ uint8_t getVolumeNumber() const { return volume_number_; }
102
+
103
+ /**
104
+ * Set the volume number for address field encoding
105
+ */
106
+ void setVolumeNumber(uint8_t volume) { volume_number_ = volume; }
107
+
108
+ private:
109
+ // Raw sector data storage
110
+ std::array<uint8_t, DISK_SIZE> sector_data_{};
111
+
112
+ // Nibblized track cache
113
+ struct NibbleTrack {
114
+ std::vector<uint8_t> nibbles;
115
+ std::vector<bool> is_sync; // true = 10-bit self-sync byte
116
+ bool dirty = false; // Track has been modified
117
+ bool valid = false; // Track has been nibblized
118
+ };
119
+ std::array<NibbleTrack, TRACKS> nibble_tracks_{};
120
+
121
+ // State
122
+ Format format_ = Format::Unknown;
123
+ bool loaded_ = false;
124
+ bool write_protected_ = false;
125
+ bool modified_ = false;
126
+ uint8_t volume_number_ = 254;
127
+
128
+ // Head position (0-139 quarter-tracks)
129
+ int quarter_track_ = 0;
130
+ uint8_t phase_states_ = 0; // Bit field of active phases
131
+ int current_phase_ = 0; // Current phase where head is settled (for stepper)
132
+
133
+ // Nibble position within current track
134
+ size_t nibble_position_ = 0;
135
+
136
+ // Bit stream cache (parallel to nibble cache, for LSS bit-level access)
137
+ struct BitTrack {
138
+ std::vector<uint8_t> bits; // Packed bit data, MSB first (same as WOZ)
139
+ uint32_t bit_count = 0;
140
+ bool dirty = false;
141
+ bool valid = false;
142
+ };
143
+ std::array<BitTrack, TRACKS> bit_tracks_{};
144
+ uint32_t bit_position_ = 0;
145
+
146
+ // Cycle count for disk rotation timing
147
+ uint64_t last_cycle_count_ = 0;
148
+
149
+ // ===== Internal Methods =====
150
+
151
+ /**
152
+ * Detect disk format from content (ProDOS vs DOS order)
153
+ * Checks for filesystem signatures rather than relying on file extension
154
+ */
155
+ Format detectFormat(const std::string &filename) const;
156
+
157
+ /**
158
+ * Nibblize a track from sector data
159
+ */
160
+ void nibblizeTrack(int track);
161
+
162
+ /**
163
+ * Denibblize a track back to sector data
164
+ */
165
+ void denibblizeTrack(int track);
166
+
167
+ /**
168
+ * Update head position based on phase magnet states
169
+ * Called when a phase is turned OFF to check if stepping should occur
170
+ */
171
+ void updateHeadPosition();
172
+
173
+ /**
174
+ * Get the logical sector number for a physical sector
175
+ */
176
+ int getLogicalSector(int physical_sector) const;
177
+
178
+ /**
179
+ * Ensure the current track is nibblized
180
+ */
181
+ void ensureTrackNibblized();
182
+
183
+ /**
184
+ * Ensure the current track has a valid bit stream cache
185
+ */
186
+ void ensureTrackBitified();
187
+
188
+ /**
189
+ * Convert a dirty bit track back to nibbles for denibblization
190
+ */
191
+ void bitTrackToNibbleTrack(int track);
192
+
193
+ /**
194
+ * Decode a 4-and-4 encoded byte pair
195
+ */
196
+ static uint8_t decode4and4(uint8_t odd, uint8_t even);
197
+
198
+ /**
199
+ * Decode 6-and-2 encoded data back to 256 bytes
200
+ */
201
+ static bool decode6and2(const uint8_t *encoded, uint8_t *output);
202
+ };
203
+
204
+ } // namespace a2e
@@ -0,0 +1,147 @@
1
+ /*
2
+ * gcr_encoding.cpp - 6-and-2 and 4-and-4 GCR encoding for Apple II disk data
3
+ *
4
+ * Written by
5
+ * Mike Daley <michael_daley@icloud.com>
6
+ */
7
+
8
+ #include "gcr_encoding.hpp"
9
+ #include <algorithm>
10
+ #include <cstring>
11
+
12
+ namespace a2e {
13
+ namespace GCR {
14
+
15
+ std::vector<uint8_t> encode6and2(const uint8_t *data)
16
+ {
17
+ // The 6-and-2 encoding converts 256 bytes into 342 6-bit values
18
+ // which are then encoded into 342 disk nibbles plus a checksum
19
+
20
+ // Buffer for prenibblized data (342 bytes)
21
+ // First 86 bytes: auxiliary buffer (2 bits from each of 256 bytes)
22
+ // Next 256 bytes: primary buffer (6 bits from each data byte)
23
+ uint8_t buffer[342];
24
+ std::memset(buffer, 0, sizeof(buffer));
25
+
26
+ // Step 1: Build auxiliary buffer (first 86 bytes)
27
+ // Extract bits 0-1 from each data byte and pack them
28
+ // Each aux byte holds bits from 3 data bytes (or 2 for the last few)
29
+ for (int i = 0; i < 86; i++)
30
+ {
31
+ uint8_t val = 0;
32
+
33
+ // Bits from data[i] (bits 0,1 -> bits 0,1 of aux)
34
+ if (i < 86)
35
+ {
36
+ val = ((data[i] & 0x01) << 1) | ((data[i] & 0x02) >> 1);
37
+ }
38
+
39
+ // Bits from data[i+86] (bits 0,1 -> bits 2,3 of aux)
40
+ if (i + 86 < 256)
41
+ {
42
+ val |= ((data[i + 86] & 0x01) << 3) | ((data[i + 86] & 0x02) << 1);
43
+ }
44
+
45
+ // Bits from data[i+172] (bits 0,1 -> bits 4,5 of aux)
46
+ if (i + 172 < 256)
47
+ {
48
+ val |= ((data[i + 172] & 0x01) << 5) | ((data[i + 172] & 0x02) << 3);
49
+ }
50
+
51
+ buffer[i] = val;
52
+ }
53
+
54
+ // Step 2: Build primary buffer (next 256 bytes)
55
+ // Store bits 2-7 from each data byte (shifted right by 2)
56
+ for (int i = 0; i < 256; i++)
57
+ {
58
+ buffer[86 + i] = data[i] >> 2;
59
+ }
60
+
61
+ // Step 3: XOR encode and convert to disk nibbles
62
+ // Each byte is XORed with the previous byte's value (differential encoding)
63
+ // This allows the decoder to recover the original by XORing each value with previous
64
+ std::vector<uint8_t> result;
65
+ result.reserve(343);
66
+
67
+ // XOR encode in forward order
68
+ uint8_t prev = 0;
69
+ for (int i = 0; i < 342; i++)
70
+ {
71
+ uint8_t xor_val = buffer[i] ^ prev;
72
+ result.push_back(ENCODE_6_AND_2[xor_val & 0x3F]);
73
+ prev = buffer[i];
74
+ }
75
+
76
+ // Append checksum nibble (the last raw value before XOR)
77
+ result.push_back(ENCODE_6_AND_2[prev & 0x3F]);
78
+
79
+ return result;
80
+ }
81
+
82
+ std::vector<uint8_t> buildSector(uint8_t volume, uint8_t track,
83
+ uint8_t sector, const uint8_t *data)
84
+ {
85
+ std::vector<uint8_t> nibbles;
86
+ nibbles.reserve(400); // Approximate sector size
87
+
88
+ // Gap 1: Self-sync bytes before address field (~12-16 sync bytes)
89
+ for (int i = 0; i < 14; i++)
90
+ {
91
+ nibbles.push_back(SYNC_BYTE);
92
+ }
93
+
94
+ // Address field prologue: D5 AA 96
95
+ nibbles.push_back(ADDR_PROLOGUE[0]);
96
+ nibbles.push_back(ADDR_PROLOGUE[1]);
97
+ nibbles.push_back(ADDR_PROLOGUE[2]);
98
+
99
+ // Address field data (4-and-4 encoded): volume, track, sector, checksum
100
+ auto [vol_odd, vol_even] = encode4and4(volume);
101
+ nibbles.push_back(vol_odd);
102
+ nibbles.push_back(vol_even);
103
+
104
+ auto [trk_odd, trk_even] = encode4and4(track);
105
+ nibbles.push_back(trk_odd);
106
+ nibbles.push_back(trk_even);
107
+
108
+ auto [sec_odd, sec_even] = encode4and4(sector);
109
+ nibbles.push_back(sec_odd);
110
+ nibbles.push_back(sec_even);
111
+
112
+ // Checksum is XOR of volume, track, sector
113
+ uint8_t addr_checksum = volume ^ track ^ sector;
114
+ auto [chk_odd, chk_even] = encode4and4(addr_checksum);
115
+ nibbles.push_back(chk_odd);
116
+ nibbles.push_back(chk_even);
117
+
118
+ // Address field epilogue: DE AA EB
119
+ nibbles.push_back(ADDR_EPILOGUE[0]);
120
+ nibbles.push_back(ADDR_EPILOGUE[1]);
121
+ nibbles.push_back(ADDR_EPILOGUE[2]);
122
+
123
+ // Gap 2: Sync bytes between address and data (~5-7 sync bytes)
124
+ for (int i = 0; i < 6; i++)
125
+ {
126
+ nibbles.push_back(SYNC_BYTE);
127
+ }
128
+
129
+ // Data field prologue: D5 AA AD
130
+ nibbles.push_back(DATA_PROLOGUE[0]);
131
+ nibbles.push_back(DATA_PROLOGUE[1]);
132
+ nibbles.push_back(DATA_PROLOGUE[2]);
133
+
134
+ // Data field: 343 nibbles (6-and-2 encoded 256 bytes + checksum)
135
+ std::vector<uint8_t> encoded_data = encode6and2(data);
136
+ nibbles.insert(nibbles.end(), encoded_data.begin(), encoded_data.end());
137
+
138
+ // Data field epilogue: DE AA EB
139
+ nibbles.push_back(DATA_EPILOGUE[0]);
140
+ nibbles.push_back(DATA_EPILOGUE[1]);
141
+ nibbles.push_back(DATA_EPILOGUE[2]);
142
+
143
+ return nibbles;
144
+ }
145
+
146
+ } // namespace GCR
147
+ } // namespace a2e
@@ -0,0 +1,78 @@
1
+ /*
2
+ * gcr_encoding.hpp - GCR encoding tables and routines for Disk II emulation
3
+ *
4
+ * Written by
5
+ * Mike Daley <michael_daley@icloud.com>
6
+ */
7
+
8
+ #pragma once
9
+
10
+ #include <array>
11
+ #include <cstdint>
12
+ #include <utility>
13
+ #include <vector>
14
+
15
+ namespace a2e {
16
+ namespace GCR {
17
+
18
+ // 6-and-2 encoding lookup table
19
+ // Maps 6-bit values (0-63) to valid disk nibbles (bit 7 set, no adjacent zeros)
20
+ constexpr std::array<uint8_t, 64> ENCODE_6_AND_2 = {
21
+ 0x96, 0x97, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xA6, 0xA7, 0xAB, 0xAC,
22
+ 0xAD, 0xAE, 0xAF, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB9, 0xBA,
23
+ 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xCB, 0xCD, 0xCE, 0xCF, 0xD3, 0xD6,
24
+ 0xD7, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE5, 0xE6, 0xE7,
25
+ 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF2, 0xF3, 0xF4, 0xF5,
26
+ 0xF6, 0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF};
27
+
28
+ // Address field markers
29
+ constexpr uint8_t ADDR_PROLOGUE[3] = {0xD5, 0xAA, 0x96};
30
+ constexpr uint8_t ADDR_EPILOGUE[3] = {0xDE, 0xAA, 0xEB};
31
+
32
+ // Data field markers
33
+ constexpr uint8_t DATA_PROLOGUE[3] = {0xD5, 0xAA, 0xAD};
34
+ constexpr uint8_t DATA_EPILOGUE[3] = {0xDE, 0xAA, 0xEB};
35
+
36
+ // Self-sync byte
37
+ constexpr uint8_t SYNC_BYTE = 0xFF;
38
+
39
+ /**
40
+ * 4-and-4 encoding for address field values
41
+ * Splits byte into odd bits (first) and even bits (second)
42
+ * Each byte is OR'd with 0xAA to ensure high bit set
43
+ *
44
+ * @param value Byte to encode
45
+ * @return Pair of encoded bytes (odd bits, even bits)
46
+ */
47
+ inline std::pair<uint8_t, uint8_t> encode4and4(uint8_t value) {
48
+ // Odd bits: bits 7,5,3,1 go to positions 6,4,2,0
49
+ uint8_t odd = 0xAA | ((value >> 1) & 0x55);
50
+ // Even bits: bits 6,4,2,0 stay in positions 6,4,2,0
51
+ uint8_t even = 0xAA | (value & 0x55);
52
+ return {odd, even};
53
+ }
54
+
55
+ /**
56
+ * Encode 256 bytes of sector data using 6-and-2 encoding
57
+ * Returns 343 nibbles (342 encoded data + 1 checksum)
58
+ *
59
+ * @param data Pointer to 256 bytes of sector data
60
+ * @return Vector of 343 encoded nibbles
61
+ */
62
+ std::vector<uint8_t> encode6and2(const uint8_t *data);
63
+
64
+ /**
65
+ * Build a complete sector as a nibble stream
66
+ * Includes sync bytes, address field, gap, and data field
67
+ *
68
+ * @param volume Volume number (typically 254)
69
+ * @param track Track number (0-34)
70
+ * @param sector Sector number (0-15)
71
+ * @param data Pointer to 256 bytes of sector data
72
+ * @return Vector of nibbles for the complete sector
73
+ */
74
+ std::vector<uint8_t> buildSector(uint8_t volume, uint8_t track, uint8_t sector,
75
+ const uint8_t *data);
76
+
77
+ } // namespace GCR
78
+ } // namespace a2e