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,1609 @@
1
+ /*
2
+ * cpu6502.cpp - Cycle-accurate 65C02 CPU emulation implementation
3
+ *
4
+ * Written by
5
+ * Mike Daley <michael_daley@icloud.com>
6
+ */
7
+
8
+ #include "cpu6502.hpp"
9
+ #include <iomanip>
10
+ #include <sstream>
11
+
12
+ namespace a2e {
13
+
14
+ CPU6502::CPU6502(ReadCallback read, WriteCallback write, CPUVariant variant)
15
+ : read_(std::move(read)), write_(std::move(write)), variant_(variant) {}
16
+
17
+ void CPU6502::reset() {
18
+ a_ = 0;
19
+ x_ = 0;
20
+ y_ = 0;
21
+ sp_ = 0xFD;
22
+ p_ = 0x24; // I flag set, U always set
23
+
24
+ // Read reset vector
25
+ uint16_t lo = read_(0xFFFC);
26
+ uint16_t hi = read_(0xFFFD);
27
+ pc_ = lo | (hi << 8);
28
+
29
+ cycleCount_ = 0;
30
+ totalCycles_ += 7; // Reset takes 7 cycles
31
+
32
+ irqPending_ = false;
33
+ nmiPending_ = false;
34
+ nmiEdge_ = false;
35
+ }
36
+
37
+ void CPU6502::executeInstruction() {
38
+ // Note: VIA IRQs are handled via edge-triggered callback in checkIRQ()
39
+ // The callback sets irqPending_ when interrupt first becomes active
40
+ // Level-triggered re-assertion after RTI is handled by the VIA callback
41
+ // mechanism - no polling needed here
42
+
43
+ // Handle pending interrupts
44
+ if (nmiPending_) {
45
+ nmiPending_ = false;
46
+ pushWord(pc_);
47
+ push((p_ & ~FLAG_B) | FLAG_U);
48
+ setFlag(FLAG_I, true);
49
+ if (variant_ == CPUVariant::CMOS_65C02) {
50
+ setFlag(FLAG_D, false); // 65C02 clears D on NMI
51
+ }
52
+ pc_ = read_(0xFFFA) | (read_(0xFFFB) << 8);
53
+ totalCycles_ += 7;
54
+ return;
55
+ }
56
+
57
+ if (irqPending_ && !getFlag(FLAG_I)) {
58
+ irqPending_ = false;
59
+ pushWord(pc_);
60
+ push((p_ & ~FLAG_B) | FLAG_U);
61
+ setFlag(FLAG_I, true);
62
+ if (variant_ == CPUVariant::CMOS_65C02) {
63
+ setFlag(FLAG_D, false); // 65C02 clears D on IRQ
64
+ }
65
+ pc_ = read_(0xFFFE) | (read_(0xFFFF) << 8);
66
+ totalCycles_ += 7;
67
+ return;
68
+ }
69
+
70
+ // Fetch and execute opcode
71
+ pageCrossed_ = false;
72
+ uint8_t opcode = fetch();
73
+ cycleCount_ = CYCLE_TABLE[opcode];
74
+
75
+ executeOpcode(opcode);
76
+
77
+ if (pageCrossed_) {
78
+ // Some instructions take an extra cycle on page crossing
79
+ // This is handled per-instruction in executeOpcode
80
+ }
81
+
82
+ totalCycles_ += cycleCount_;
83
+ cycleCount_ = 0;
84
+ }
85
+
86
+ void CPU6502::step() {
87
+ // For cycle-accurate emulation, we execute one instruction at a time
88
+ // and track the cycles. A more sophisticated implementation would
89
+ // break down each instruction into individual cycle steps.
90
+ if (cycleCount_ == 0) {
91
+ executeInstruction();
92
+ } else {
93
+ cycleCount_--;
94
+ }
95
+ }
96
+
97
+ void CPU6502::irq() { irqPending_ = true; }
98
+
99
+ void CPU6502::nmi() {
100
+ // NMI is edge-triggered
101
+ if (!nmiEdge_) {
102
+ nmiPending_ = true;
103
+ nmiEdge_ = true;
104
+ }
105
+ }
106
+
107
+ uint8_t CPU6502::read(uint16_t address) { return read_(address); }
108
+
109
+ void CPU6502::write(uint16_t address, uint8_t value) { write_(address, value); }
110
+
111
+ uint8_t CPU6502::fetch() { return read(pc_++); }
112
+
113
+ uint16_t CPU6502::fetchWord() {
114
+ uint8_t lo = fetch();
115
+ uint8_t hi = fetch();
116
+ return lo | (hi << 8);
117
+ }
118
+
119
+ void CPU6502::push(uint8_t value) {
120
+ write(0x0100 | sp_, value);
121
+ sp_--;
122
+ }
123
+
124
+ uint8_t CPU6502::pop() {
125
+ sp_++;
126
+ return read(0x0100 | sp_);
127
+ }
128
+
129
+ void CPU6502::pushWord(uint16_t value) {
130
+ push(value >> 8);
131
+ push(value & 0xFF);
132
+ }
133
+
134
+ uint16_t CPU6502::popWord() {
135
+ uint8_t lo = pop();
136
+ uint8_t hi = pop();
137
+ return lo | (hi << 8);
138
+ }
139
+
140
+ void CPU6502::updateNZ(uint8_t value) {
141
+ setFlag(FLAG_Z, value == 0);
142
+ setFlag(FLAG_N, (value & 0x80) != 0);
143
+ }
144
+
145
+ void CPU6502::compare(uint8_t reg, uint8_t value) {
146
+ uint16_t result = reg - value;
147
+ setFlag(FLAG_C, reg >= value);
148
+ setFlag(FLAG_Z, reg == value);
149
+ setFlag(FLAG_N, (result & 0x80) != 0);
150
+ }
151
+
152
+ // Addressing modes
153
+ uint16_t CPU6502::addrImmediate() { return pc_++; }
154
+
155
+ uint16_t CPU6502::addrZeroPage() { return fetch(); }
156
+
157
+ uint16_t CPU6502::addrZeroPageX() { return (fetch() + x_) & 0xFF; }
158
+
159
+ uint16_t CPU6502::addrZeroPageY() { return (fetch() + y_) & 0xFF; }
160
+
161
+ uint16_t CPU6502::addrAbsolute() { return fetchWord(); }
162
+
163
+ uint16_t CPU6502::addrAbsoluteX(bool checkPage) {
164
+ uint16_t base = fetchWord();
165
+ uint16_t addr = base + x_;
166
+ if (checkPage && ((base & 0xFF00) != (addr & 0xFF00))) {
167
+ pageCrossed_ = true;
168
+ cycleCount_++;
169
+ }
170
+ return addr;
171
+ }
172
+
173
+ uint16_t CPU6502::addrAbsoluteY(bool checkPage) {
174
+ uint16_t base = fetchWord();
175
+ uint16_t addr = base + y_;
176
+ if (checkPage && ((base & 0xFF00) != (addr & 0xFF00))) {
177
+ pageCrossed_ = true;
178
+ cycleCount_++;
179
+ }
180
+ return addr;
181
+ }
182
+
183
+ uint16_t CPU6502::addrIndirect() {
184
+ uint16_t ptr = fetchWord();
185
+ // 6502 bug: if low byte is 0xFF, high byte comes from same page
186
+ if (variant_ == CPUVariant::NMOS_6502 && (ptr & 0xFF) == 0xFF) {
187
+ return read(ptr) | (read(ptr & 0xFF00) << 8);
188
+ }
189
+ return read(ptr) | (read(ptr + 1) << 8);
190
+ }
191
+
192
+ uint16_t CPU6502::addrIndexedIndirect() {
193
+ uint8_t zp = (fetch() + x_) & 0xFF;
194
+ return read(zp) | (read((zp + 1) & 0xFF) << 8);
195
+ }
196
+
197
+ uint16_t CPU6502::addrIndirectIndexed(bool checkPage) {
198
+ uint8_t zp = fetch();
199
+ uint16_t base = read(zp) | (read((zp + 1) & 0xFF) << 8);
200
+ uint16_t addr = base + y_;
201
+ if (checkPage && ((base & 0xFF00) != (addr & 0xFF00))) {
202
+ pageCrossed_ = true;
203
+ cycleCount_++;
204
+ }
205
+ return addr;
206
+ }
207
+
208
+ uint16_t CPU6502::addrIndirectZP() {
209
+ uint8_t zp = fetch();
210
+ return read(zp) | (read((zp + 1) & 0xFF) << 8);
211
+ }
212
+
213
+ // ALU operations
214
+ void CPU6502::opADC(uint8_t value) {
215
+ if (getFlag(FLAG_D)) {
216
+ // Decimal mode - 65C02 takes an extra cycle
217
+ if (variant_ == CPUVariant::CMOS_65C02) {
218
+ cycleCount_++;
219
+ }
220
+
221
+ uint8_t carry = getFlag(FLAG_C) ? 1 : 0;
222
+
223
+ // V flag is computed from binary result (same for both variants)
224
+ uint16_t binResult = a_ + value + carry;
225
+ setFlag(FLAG_V, ~(a_ ^ value) & (a_ ^ binResult) & 0x80);
226
+
227
+ // Add low nibbles
228
+ int lo = (a_ & 0x0F) + (value & 0x0F) + carry;
229
+
230
+ // Add high nibbles
231
+ int hi = (a_ >> 4) + (value >> 4);
232
+
233
+ // BCD adjust low nibble
234
+ if (lo > 9) {
235
+ lo += 6;
236
+ }
237
+
238
+ // Carry from low to high (handles overflow from correction)
239
+ hi += (lo >> 4);
240
+ lo &= 0x0F;
241
+
242
+ // BCD adjust high nibble and set carry
243
+ if (hi > 9) {
244
+ hi += 6;
245
+ setFlag(FLAG_C, true);
246
+ } else {
247
+ setFlag(FLAG_C, false);
248
+ }
249
+
250
+ a_ = static_cast<uint8_t>(((hi & 0x0F) << 4) | lo);
251
+
252
+ // 65C02: N and Z are set from final BCD result (valid flags)
253
+ // NMOS 6502: N and Z are set but may not be valid
254
+ updateNZ(a_);
255
+ } else {
256
+ uint16_t result = a_ + value + (getFlag(FLAG_C) ? 1 : 0);
257
+ setFlag(FLAG_C, result > 0xFF);
258
+ setFlag(FLAG_V, ~(a_ ^ value) & (a_ ^ result) & 0x80);
259
+ a_ = result & 0xFF;
260
+ updateNZ(a_);
261
+ }
262
+ }
263
+
264
+ void CPU6502::opSBC(uint8_t value) {
265
+ if (getFlag(FLAG_D)) {
266
+ // Decimal mode - 65C02 takes an extra cycle
267
+ if (variant_ == CPUVariant::CMOS_65C02) {
268
+ cycleCount_++;
269
+ }
270
+
271
+ uint8_t borrow = getFlag(FLAG_C) ? 0 : 1;
272
+
273
+ // V and C flags computed from binary result
274
+ uint16_t binResult = a_ - value - borrow;
275
+ setFlag(FLAG_C, binResult < 0x100);
276
+ setFlag(FLAG_V, (a_ ^ value) & (a_ ^ binResult) & 0x80);
277
+
278
+ // Subtract low nibbles
279
+ int lo = (a_ & 0x0F) - (value & 0x0F) - borrow;
280
+
281
+ // Subtract high nibbles
282
+ int hi = (a_ >> 4) - (value >> 4);
283
+
284
+ // BCD adjust low nibble (borrow if negative)
285
+ if (lo < 0) {
286
+ lo -= 6;
287
+ hi--;
288
+ }
289
+
290
+ // BCD adjust high nibble
291
+ if (hi < 0) {
292
+ hi -= 6;
293
+ }
294
+
295
+ a_ = static_cast<uint8_t>(((hi & 0x0F) << 4) | (lo & 0x0F));
296
+
297
+ // N and Z from final BCD result
298
+ updateNZ(a_);
299
+ } else {
300
+ uint16_t result = a_ - value - (getFlag(FLAG_C) ? 0 : 1);
301
+ setFlag(FLAG_C, result < 0x100);
302
+ setFlag(FLAG_V, (a_ ^ value) & (a_ ^ result) & 0x80);
303
+ a_ = result & 0xFF;
304
+ updateNZ(a_);
305
+ }
306
+ }
307
+
308
+ void CPU6502::opAND(uint8_t value) {
309
+ a_ &= value;
310
+ updateNZ(a_);
311
+ }
312
+
313
+ void CPU6502::opORA(uint8_t value) {
314
+ a_ |= value;
315
+ updateNZ(a_);
316
+ }
317
+
318
+ void CPU6502::opEOR(uint8_t value) {
319
+ a_ ^= value;
320
+ updateNZ(a_);
321
+ }
322
+
323
+ void CPU6502::opCMP(uint8_t value) { compare(a_, value); }
324
+
325
+ void CPU6502::opCPX(uint8_t value) { compare(x_, value); }
326
+
327
+ void CPU6502::opCPY(uint8_t value) { compare(y_, value); }
328
+
329
+ void CPU6502::opBIT(uint8_t value) {
330
+ setFlag(FLAG_Z, (a_ & value) == 0);
331
+ setFlag(FLAG_N, (value & 0x80) != 0);
332
+ setFlag(FLAG_V, (value & 0x40) != 0);
333
+ }
334
+
335
+ void CPU6502::opASL_A() {
336
+ setFlag(FLAG_C, (a_ & 0x80) != 0);
337
+ a_ <<= 1;
338
+ updateNZ(a_);
339
+ }
340
+
341
+ uint8_t CPU6502::opASL(uint8_t value) {
342
+ setFlag(FLAG_C, (value & 0x80) != 0);
343
+ value <<= 1;
344
+ updateNZ(value);
345
+ return value;
346
+ }
347
+
348
+ void CPU6502::opLSR_A() {
349
+ setFlag(FLAG_C, (a_ & 0x01) != 0);
350
+ a_ >>= 1;
351
+ updateNZ(a_);
352
+ }
353
+
354
+ uint8_t CPU6502::opLSR(uint8_t value) {
355
+ setFlag(FLAG_C, (value & 0x01) != 0);
356
+ value >>= 1;
357
+ updateNZ(value);
358
+ return value;
359
+ }
360
+
361
+ void CPU6502::opROL_A() {
362
+ uint8_t carry = getFlag(FLAG_C) ? 1 : 0;
363
+ setFlag(FLAG_C, (a_ & 0x80) != 0);
364
+ a_ = (a_ << 1) | carry;
365
+ updateNZ(a_);
366
+ }
367
+
368
+ uint8_t CPU6502::opROL(uint8_t value) {
369
+ uint8_t carry = getFlag(FLAG_C) ? 1 : 0;
370
+ setFlag(FLAG_C, (value & 0x80) != 0);
371
+ value = (value << 1) | carry;
372
+ updateNZ(value);
373
+ return value;
374
+ }
375
+
376
+ void CPU6502::opROR_A() {
377
+ uint8_t carry = getFlag(FLAG_C) ? 0x80 : 0;
378
+ setFlag(FLAG_C, (a_ & 0x01) != 0);
379
+ a_ = (a_ >> 1) | carry;
380
+ updateNZ(a_);
381
+ }
382
+
383
+ uint8_t CPU6502::opROR(uint8_t value) {
384
+ uint8_t carry = getFlag(FLAG_C) ? 0x80 : 0;
385
+ setFlag(FLAG_C, (value & 0x01) != 0);
386
+ value = (value >> 1) | carry;
387
+ updateNZ(value);
388
+ return value;
389
+ }
390
+
391
+ uint8_t CPU6502::opINC(uint8_t value) {
392
+ value++;
393
+ updateNZ(value);
394
+ return value;
395
+ }
396
+
397
+ uint8_t CPU6502::opDEC(uint8_t value) {
398
+ value--;
399
+ updateNZ(value);
400
+ return value;
401
+ }
402
+
403
+ void CPU6502::branch(bool condition) {
404
+ int8_t offset = static_cast<int8_t>(fetch());
405
+ if (condition) {
406
+ uint16_t oldPC = pc_;
407
+ pc_ += offset;
408
+ cycleCount_++;
409
+ if ((oldPC & 0xFF00) != (pc_ & 0xFF00)) {
410
+ cycleCount_++; // Page crossing penalty
411
+ }
412
+ }
413
+ }
414
+
415
+ void CPU6502::executeOpcode(uint8_t opcode) {
416
+ uint16_t addr;
417
+ uint8_t value;
418
+
419
+ switch (opcode) {
420
+ // LDA
421
+ case 0xA9:
422
+ a_ = read(addrImmediate());
423
+ updateNZ(a_);
424
+ break;
425
+ case 0xA5:
426
+ a_ = read(addrZeroPage());
427
+ updateNZ(a_);
428
+ break;
429
+ case 0xB5:
430
+ a_ = read(addrZeroPageX());
431
+ updateNZ(a_);
432
+ break;
433
+ case 0xAD:
434
+ a_ = read(addrAbsolute());
435
+ updateNZ(a_);
436
+ break;
437
+ case 0xBD:
438
+ a_ = read(addrAbsoluteX());
439
+ updateNZ(a_);
440
+ break;
441
+ case 0xB9:
442
+ a_ = read(addrAbsoluteY());
443
+ updateNZ(a_);
444
+ break;
445
+ case 0xA1:
446
+ a_ = read(addrIndexedIndirect());
447
+ updateNZ(a_);
448
+ break;
449
+ case 0xB1:
450
+ a_ = read(addrIndirectIndexed());
451
+ updateNZ(a_);
452
+ break;
453
+ case 0xB2:
454
+ if (variant_ == CPUVariant::CMOS_65C02) {
455
+ a_ = read(addrIndirectZP());
456
+ updateNZ(a_);
457
+ }
458
+ break; // LDA (zp) - 65C02
459
+
460
+ // LDX
461
+ case 0xA2:
462
+ x_ = read(addrImmediate());
463
+ updateNZ(x_);
464
+ break;
465
+ case 0xA6:
466
+ x_ = read(addrZeroPage());
467
+ updateNZ(x_);
468
+ break;
469
+ case 0xB6:
470
+ x_ = read(addrZeroPageY());
471
+ updateNZ(x_);
472
+ break;
473
+ case 0xAE:
474
+ x_ = read(addrAbsolute());
475
+ updateNZ(x_);
476
+ break;
477
+ case 0xBE:
478
+ x_ = read(addrAbsoluteY());
479
+ updateNZ(x_);
480
+ break;
481
+
482
+ // LDY
483
+ case 0xA0:
484
+ y_ = read(addrImmediate());
485
+ updateNZ(y_);
486
+ break;
487
+ case 0xA4:
488
+ y_ = read(addrZeroPage());
489
+ updateNZ(y_);
490
+ break;
491
+ case 0xB4:
492
+ y_ = read(addrZeroPageX());
493
+ updateNZ(y_);
494
+ break;
495
+ case 0xAC:
496
+ y_ = read(addrAbsolute());
497
+ updateNZ(y_);
498
+ break;
499
+ case 0xBC:
500
+ y_ = read(addrAbsoluteX());
501
+ updateNZ(y_);
502
+ break;
503
+
504
+ // STA
505
+ case 0x85:
506
+ write(addrZeroPage(), a_);
507
+ break;
508
+ case 0x95:
509
+ write(addrZeroPageX(), a_);
510
+ break;
511
+ case 0x8D:
512
+ write(addrAbsolute(), a_);
513
+ break;
514
+ case 0x9D:
515
+ write(addrAbsoluteX(false), a_);
516
+ break;
517
+ case 0x99:
518
+ write(addrAbsoluteY(false), a_);
519
+ break;
520
+ case 0x81:
521
+ write(addrIndexedIndirect(), a_);
522
+ break;
523
+ case 0x91:
524
+ write(addrIndirectIndexed(false), a_);
525
+ break;
526
+ case 0x92:
527
+ if (variant_ == CPUVariant::CMOS_65C02)
528
+ write(addrIndirectZP(), a_);
529
+ break; // STA (zp) - 65C02
530
+
531
+ // STX
532
+ case 0x86:
533
+ write(addrZeroPage(), x_);
534
+ break;
535
+ case 0x96:
536
+ write(addrZeroPageY(), x_);
537
+ break;
538
+ case 0x8E:
539
+ write(addrAbsolute(), x_);
540
+ break;
541
+
542
+ // STY
543
+ case 0x84:
544
+ write(addrZeroPage(), y_);
545
+ break;
546
+ case 0x94:
547
+ write(addrZeroPageX(), y_);
548
+ break;
549
+ case 0x8C:
550
+ write(addrAbsolute(), y_);
551
+ break;
552
+
553
+ // STZ (65C02)
554
+ case 0x64:
555
+ if (variant_ == CPUVariant::CMOS_65C02)
556
+ write(addrZeroPage(), 0);
557
+ break;
558
+ case 0x74:
559
+ if (variant_ == CPUVariant::CMOS_65C02)
560
+ write(addrZeroPageX(), 0);
561
+ break;
562
+ case 0x9C:
563
+ if (variant_ == CPUVariant::CMOS_65C02)
564
+ write(addrAbsolute(), 0);
565
+ break;
566
+ case 0x9E:
567
+ if (variant_ == CPUVariant::CMOS_65C02)
568
+ write(addrAbsoluteX(false), 0);
569
+ break;
570
+
571
+ // Transfer
572
+ case 0xAA:
573
+ x_ = a_;
574
+ updateNZ(x_);
575
+ break; // TAX
576
+ case 0xA8:
577
+ y_ = a_;
578
+ updateNZ(y_);
579
+ break; // TAY
580
+ case 0xBA:
581
+ x_ = sp_;
582
+ updateNZ(x_);
583
+ break; // TSX
584
+ case 0x8A:
585
+ a_ = x_;
586
+ updateNZ(a_);
587
+ break; // TXA
588
+ case 0x9A:
589
+ sp_ = x_;
590
+ break; // TXS
591
+ case 0x98:
592
+ a_ = y_;
593
+ updateNZ(a_);
594
+ break; // TYA
595
+
596
+ // Stack
597
+ case 0x48:
598
+ push(a_);
599
+ break; // PHA
600
+ case 0x68:
601
+ a_ = pop();
602
+ updateNZ(a_);
603
+ break; // PLA
604
+ case 0x08:
605
+ push(p_ | FLAG_B | FLAG_U);
606
+ break; // PHP
607
+ case 0x28:
608
+ p_ = (pop() & ~FLAG_B) | FLAG_U;
609
+ break; // PLP
610
+ case 0xDA:
611
+ if (variant_ == CPUVariant::CMOS_65C02)
612
+ push(x_);
613
+ break; // PHX (65C02)
614
+ case 0xFA:
615
+ if (variant_ == CPUVariant::CMOS_65C02) {
616
+ x_ = pop();
617
+ updateNZ(x_);
618
+ }
619
+ break; // PLX (65C02)
620
+ case 0x5A:
621
+ if (variant_ == CPUVariant::CMOS_65C02)
622
+ push(y_);
623
+ break; // PHY (65C02)
624
+ case 0x7A:
625
+ if (variant_ == CPUVariant::CMOS_65C02) {
626
+ y_ = pop();
627
+ updateNZ(y_);
628
+ }
629
+ break; // PLY (65C02)
630
+
631
+ // ADC
632
+ case 0x69:
633
+ opADC(read(addrImmediate()));
634
+ break;
635
+ case 0x65:
636
+ opADC(read(addrZeroPage()));
637
+ break;
638
+ case 0x75:
639
+ opADC(read(addrZeroPageX()));
640
+ break;
641
+ case 0x6D:
642
+ opADC(read(addrAbsolute()));
643
+ break;
644
+ case 0x7D:
645
+ opADC(read(addrAbsoluteX()));
646
+ break;
647
+ case 0x79:
648
+ opADC(read(addrAbsoluteY()));
649
+ break;
650
+ case 0x61:
651
+ opADC(read(addrIndexedIndirect()));
652
+ break;
653
+ case 0x71:
654
+ opADC(read(addrIndirectIndexed()));
655
+ break;
656
+ case 0x72:
657
+ if (variant_ == CPUVariant::CMOS_65C02)
658
+ opADC(read(addrIndirectZP()));
659
+ break; // (65C02)
660
+
661
+ // SBC
662
+ case 0xE9:
663
+ opSBC(read(addrImmediate()));
664
+ break;
665
+ case 0xE5:
666
+ opSBC(read(addrZeroPage()));
667
+ break;
668
+ case 0xF5:
669
+ opSBC(read(addrZeroPageX()));
670
+ break;
671
+ case 0xED:
672
+ opSBC(read(addrAbsolute()));
673
+ break;
674
+ case 0xFD:
675
+ opSBC(read(addrAbsoluteX()));
676
+ break;
677
+ case 0xF9:
678
+ opSBC(read(addrAbsoluteY()));
679
+ break;
680
+ case 0xE1:
681
+ opSBC(read(addrIndexedIndirect()));
682
+ break;
683
+ case 0xF1:
684
+ opSBC(read(addrIndirectIndexed()));
685
+ break;
686
+ case 0xF2:
687
+ if (variant_ == CPUVariant::CMOS_65C02)
688
+ opSBC(read(addrIndirectZP()));
689
+ break; // (65C02)
690
+
691
+ // AND
692
+ case 0x29:
693
+ opAND(read(addrImmediate()));
694
+ break;
695
+ case 0x25:
696
+ opAND(read(addrZeroPage()));
697
+ break;
698
+ case 0x35:
699
+ opAND(read(addrZeroPageX()));
700
+ break;
701
+ case 0x2D:
702
+ opAND(read(addrAbsolute()));
703
+ break;
704
+ case 0x3D:
705
+ opAND(read(addrAbsoluteX()));
706
+ break;
707
+ case 0x39:
708
+ opAND(read(addrAbsoluteY()));
709
+ break;
710
+ case 0x21:
711
+ opAND(read(addrIndexedIndirect()));
712
+ break;
713
+ case 0x31:
714
+ opAND(read(addrIndirectIndexed()));
715
+ break;
716
+ case 0x32:
717
+ if (variant_ == CPUVariant::CMOS_65C02)
718
+ opAND(read(addrIndirectZP()));
719
+ break; // (65C02)
720
+
721
+ // ORA
722
+ case 0x09:
723
+ opORA(read(addrImmediate()));
724
+ break;
725
+ case 0x05:
726
+ opORA(read(addrZeroPage()));
727
+ break;
728
+ case 0x15:
729
+ opORA(read(addrZeroPageX()));
730
+ break;
731
+ case 0x0D:
732
+ opORA(read(addrAbsolute()));
733
+ break;
734
+ case 0x1D:
735
+ opORA(read(addrAbsoluteX()));
736
+ break;
737
+ case 0x19:
738
+ opORA(read(addrAbsoluteY()));
739
+ break;
740
+ case 0x01:
741
+ opORA(read(addrIndexedIndirect()));
742
+ break;
743
+ case 0x11:
744
+ opORA(read(addrIndirectIndexed()));
745
+ break;
746
+ case 0x12:
747
+ if (variant_ == CPUVariant::CMOS_65C02)
748
+ opORA(read(addrIndirectZP()));
749
+ break; // (65C02)
750
+
751
+ // EOR
752
+ case 0x49:
753
+ opEOR(read(addrImmediate()));
754
+ break;
755
+ case 0x45:
756
+ opEOR(read(addrZeroPage()));
757
+ break;
758
+ case 0x55:
759
+ opEOR(read(addrZeroPageX()));
760
+ break;
761
+ case 0x4D:
762
+ opEOR(read(addrAbsolute()));
763
+ break;
764
+ case 0x5D:
765
+ opEOR(read(addrAbsoluteX()));
766
+ break;
767
+ case 0x59:
768
+ opEOR(read(addrAbsoluteY()));
769
+ break;
770
+ case 0x41:
771
+ opEOR(read(addrIndexedIndirect()));
772
+ break;
773
+ case 0x51:
774
+ opEOR(read(addrIndirectIndexed()));
775
+ break;
776
+ case 0x52:
777
+ if (variant_ == CPUVariant::CMOS_65C02)
778
+ opEOR(read(addrIndirectZP()));
779
+ break; // (65C02)
780
+
781
+ // CMP
782
+ case 0xC9:
783
+ opCMP(read(addrImmediate()));
784
+ break;
785
+ case 0xC5:
786
+ opCMP(read(addrZeroPage()));
787
+ break;
788
+ case 0xD5:
789
+ opCMP(read(addrZeroPageX()));
790
+ break;
791
+ case 0xCD:
792
+ opCMP(read(addrAbsolute()));
793
+ break;
794
+ case 0xDD:
795
+ opCMP(read(addrAbsoluteX()));
796
+ break;
797
+ case 0xD9:
798
+ opCMP(read(addrAbsoluteY()));
799
+ break;
800
+ case 0xC1:
801
+ opCMP(read(addrIndexedIndirect()));
802
+ break;
803
+ case 0xD1:
804
+ opCMP(read(addrIndirectIndexed()));
805
+ break;
806
+ case 0xD2:
807
+ if (variant_ == CPUVariant::CMOS_65C02)
808
+ opCMP(read(addrIndirectZP()));
809
+ break; // (65C02)
810
+
811
+ // CPX
812
+ case 0xE0:
813
+ opCPX(read(addrImmediate()));
814
+ break;
815
+ case 0xE4:
816
+ opCPX(read(addrZeroPage()));
817
+ break;
818
+ case 0xEC:
819
+ opCPX(read(addrAbsolute()));
820
+ break;
821
+
822
+ // CPY
823
+ case 0xC0:
824
+ opCPY(read(addrImmediate()));
825
+ break;
826
+ case 0xC4:
827
+ opCPY(read(addrZeroPage()));
828
+ break;
829
+ case 0xCC:
830
+ opCPY(read(addrAbsolute()));
831
+ break;
832
+
833
+ // BIT
834
+ case 0x24:
835
+ opBIT(read(addrZeroPage()));
836
+ break;
837
+ case 0x2C:
838
+ opBIT(read(addrAbsolute()));
839
+ break;
840
+ case 0x89:
841
+ if (variant_ == CPUVariant::CMOS_65C02) { // BIT immediate (65C02)
842
+ value = read(addrImmediate());
843
+ setFlag(FLAG_Z, (a_ & value) == 0);
844
+ }
845
+ break;
846
+ case 0x34:
847
+ if (variant_ == CPUVariant::CMOS_65C02)
848
+ opBIT(read(addrZeroPageX()));
849
+ break;
850
+ case 0x3C:
851
+ if (variant_ == CPUVariant::CMOS_65C02)
852
+ opBIT(read(addrAbsoluteX()));
853
+ break;
854
+
855
+ // ASL - Read-modify-write
856
+ // NMOS 6502: read, write-old, write-new (double write)
857
+ // CMOS 65C02: read, read-dummy, write-new (double read)
858
+ case 0x0A:
859
+ opASL_A();
860
+ break;
861
+ case 0x06:
862
+ addr = addrZeroPage();
863
+ value = read(addr);
864
+ if (variant_ == CPUVariant::CMOS_65C02)
865
+ read(addr); // 65C02: dummy read
866
+ else
867
+ write(addr, value); // NMOS: dummy write of old value
868
+ write(addr, opASL(value));
869
+ break;
870
+ case 0x16:
871
+ addr = addrZeroPageX();
872
+ value = read(addr);
873
+ if (variant_ == CPUVariant::CMOS_65C02)
874
+ read(addr);
875
+ else
876
+ write(addr, value);
877
+ write(addr, opASL(value));
878
+ break;
879
+ case 0x0E:
880
+ addr = addrAbsolute();
881
+ value = read(addr);
882
+ if (variant_ == CPUVariant::CMOS_65C02)
883
+ read(addr);
884
+ else
885
+ write(addr, value);
886
+ write(addr, opASL(value));
887
+ break;
888
+ case 0x1E:
889
+ addr = addrAbsoluteX(false);
890
+ value = read(addr);
891
+ if (variant_ == CPUVariant::CMOS_65C02)
892
+ read(addr);
893
+ else
894
+ write(addr, value);
895
+ write(addr, opASL(value));
896
+ break;
897
+
898
+ // LSR - Read-modify-write
899
+ case 0x4A:
900
+ opLSR_A();
901
+ break;
902
+ case 0x46:
903
+ addr = addrZeroPage();
904
+ value = read(addr);
905
+ if (variant_ == CPUVariant::CMOS_65C02)
906
+ read(addr);
907
+ else
908
+ write(addr, value);
909
+ write(addr, opLSR(value));
910
+ break;
911
+ case 0x56:
912
+ addr = addrZeroPageX();
913
+ value = read(addr);
914
+ if (variant_ == CPUVariant::CMOS_65C02)
915
+ read(addr);
916
+ else
917
+ write(addr, value);
918
+ write(addr, opLSR(value));
919
+ break;
920
+ case 0x4E:
921
+ addr = addrAbsolute();
922
+ value = read(addr);
923
+ if (variant_ == CPUVariant::CMOS_65C02)
924
+ read(addr);
925
+ else
926
+ write(addr, value);
927
+ write(addr, opLSR(value));
928
+ break;
929
+ case 0x5E:
930
+ addr = addrAbsoluteX(false);
931
+ value = read(addr);
932
+ if (variant_ == CPUVariant::CMOS_65C02)
933
+ read(addr);
934
+ else
935
+ write(addr, value);
936
+ write(addr, opLSR(value));
937
+ break;
938
+
939
+ // ROL - Read-modify-write
940
+ case 0x2A:
941
+ opROL_A();
942
+ break;
943
+ case 0x26:
944
+ addr = addrZeroPage();
945
+ value = read(addr);
946
+ if (variant_ == CPUVariant::CMOS_65C02)
947
+ read(addr);
948
+ else
949
+ write(addr, value);
950
+ write(addr, opROL(value));
951
+ break;
952
+ case 0x36:
953
+ addr = addrZeroPageX();
954
+ value = read(addr);
955
+ if (variant_ == CPUVariant::CMOS_65C02)
956
+ read(addr);
957
+ else
958
+ write(addr, value);
959
+ write(addr, opROL(value));
960
+ break;
961
+ case 0x2E:
962
+ addr = addrAbsolute();
963
+ value = read(addr);
964
+ if (variant_ == CPUVariant::CMOS_65C02)
965
+ read(addr);
966
+ else
967
+ write(addr, value);
968
+ write(addr, opROL(value));
969
+ break;
970
+ case 0x3E:
971
+ addr = addrAbsoluteX(false);
972
+ value = read(addr);
973
+ if (variant_ == CPUVariant::CMOS_65C02)
974
+ read(addr);
975
+ else
976
+ write(addr, value);
977
+ write(addr, opROL(value));
978
+ break;
979
+
980
+ // ROR - Read-modify-write
981
+ case 0x6A:
982
+ opROR_A();
983
+ break;
984
+ case 0x66:
985
+ addr = addrZeroPage();
986
+ value = read(addr);
987
+ if (variant_ == CPUVariant::CMOS_65C02)
988
+ read(addr);
989
+ else
990
+ write(addr, value);
991
+ write(addr, opROR(value));
992
+ break;
993
+ case 0x76:
994
+ addr = addrZeroPageX();
995
+ value = read(addr);
996
+ if (variant_ == CPUVariant::CMOS_65C02)
997
+ read(addr);
998
+ else
999
+ write(addr, value);
1000
+ write(addr, opROR(value));
1001
+ break;
1002
+ case 0x6E:
1003
+ addr = addrAbsolute();
1004
+ value = read(addr);
1005
+ if (variant_ == CPUVariant::CMOS_65C02)
1006
+ read(addr);
1007
+ else
1008
+ write(addr, value);
1009
+ write(addr, opROR(value));
1010
+ break;
1011
+ case 0x7E:
1012
+ addr = addrAbsoluteX(false);
1013
+ value = read(addr);
1014
+ if (variant_ == CPUVariant::CMOS_65C02)
1015
+ read(addr);
1016
+ else
1017
+ write(addr, value);
1018
+ write(addr, opROR(value));
1019
+ break;
1020
+
1021
+ // INC - Read-modify-write
1022
+ case 0xE6:
1023
+ addr = addrZeroPage();
1024
+ value = read(addr);
1025
+ if (variant_ == CPUVariant::CMOS_65C02)
1026
+ read(addr);
1027
+ else
1028
+ write(addr, value);
1029
+ write(addr, opINC(value));
1030
+ break;
1031
+ case 0xF6:
1032
+ addr = addrZeroPageX();
1033
+ value = read(addr);
1034
+ if (variant_ == CPUVariant::CMOS_65C02)
1035
+ read(addr);
1036
+ else
1037
+ write(addr, value);
1038
+ write(addr, opINC(value));
1039
+ break;
1040
+ case 0xEE:
1041
+ addr = addrAbsolute();
1042
+ value = read(addr);
1043
+ if (variant_ == CPUVariant::CMOS_65C02)
1044
+ read(addr);
1045
+ else
1046
+ write(addr, value);
1047
+ write(addr, opINC(value));
1048
+ break;
1049
+ case 0xFE:
1050
+ addr = addrAbsoluteX(false);
1051
+ value = read(addr);
1052
+ if (variant_ == CPUVariant::CMOS_65C02)
1053
+ read(addr);
1054
+ else
1055
+ write(addr, value);
1056
+ write(addr, opINC(value));
1057
+ break;
1058
+ case 0x1A:
1059
+ if (variant_ == CPUVariant::CMOS_65C02) {
1060
+ a_ = opINC(a_);
1061
+ }
1062
+ break; // INC A (65C02)
1063
+
1064
+ // DEC - Read-modify-write
1065
+ case 0xC6:
1066
+ addr = addrZeroPage();
1067
+ value = read(addr);
1068
+ if (variant_ == CPUVariant::CMOS_65C02)
1069
+ read(addr);
1070
+ else
1071
+ write(addr, value);
1072
+ write(addr, opDEC(value));
1073
+ break;
1074
+ case 0xD6:
1075
+ addr = addrZeroPageX();
1076
+ value = read(addr);
1077
+ if (variant_ == CPUVariant::CMOS_65C02)
1078
+ read(addr);
1079
+ else
1080
+ write(addr, value);
1081
+ write(addr, opDEC(value));
1082
+ break;
1083
+ case 0xCE:
1084
+ addr = addrAbsolute();
1085
+ value = read(addr);
1086
+ if (variant_ == CPUVariant::CMOS_65C02)
1087
+ read(addr);
1088
+ else
1089
+ write(addr, value);
1090
+ write(addr, opDEC(value));
1091
+ break;
1092
+ case 0xDE:
1093
+ addr = addrAbsoluteX(false);
1094
+ value = read(addr);
1095
+ if (variant_ == CPUVariant::CMOS_65C02)
1096
+ read(addr);
1097
+ else
1098
+ write(addr, value);
1099
+ write(addr, opDEC(value));
1100
+ break;
1101
+ case 0x3A:
1102
+ if (variant_ == CPUVariant::CMOS_65C02) {
1103
+ a_ = opDEC(a_);
1104
+ }
1105
+ break; // DEC A (65C02)
1106
+
1107
+ // INX/INY/DEX/DEY
1108
+ case 0xE8:
1109
+ x_++;
1110
+ updateNZ(x_);
1111
+ break; // INX
1112
+ case 0xC8:
1113
+ y_++;
1114
+ updateNZ(y_);
1115
+ break; // INY
1116
+ case 0xCA:
1117
+ x_--;
1118
+ updateNZ(x_);
1119
+ break; // DEX
1120
+ case 0x88:
1121
+ y_--;
1122
+ updateNZ(y_);
1123
+ break; // DEY
1124
+
1125
+ // Branches
1126
+ case 0x10:
1127
+ branch(!getFlag(FLAG_N));
1128
+ break; // BPL
1129
+ case 0x30:
1130
+ branch(getFlag(FLAG_N));
1131
+ break; // BMI
1132
+ case 0x50:
1133
+ branch(!getFlag(FLAG_V));
1134
+ break; // BVC
1135
+ case 0x70:
1136
+ branch(getFlag(FLAG_V));
1137
+ break; // BVS
1138
+ case 0x90:
1139
+ branch(!getFlag(FLAG_C));
1140
+ break; // BCC
1141
+ case 0xB0:
1142
+ branch(getFlag(FLAG_C));
1143
+ break; // BCS
1144
+ case 0xD0:
1145
+ branch(!getFlag(FLAG_Z));
1146
+ break; // BNE
1147
+ case 0xF0:
1148
+ branch(getFlag(FLAG_Z));
1149
+ break; // BEQ
1150
+ case 0x80:
1151
+ if (variant_ == CPUVariant::CMOS_65C02)
1152
+ branch(true);
1153
+ break; // BRA (65C02)
1154
+
1155
+ // JMP
1156
+ case 0x4C:
1157
+ pc_ = addrAbsolute();
1158
+ break;
1159
+ case 0x6C:
1160
+ pc_ = addrIndirect();
1161
+ break;
1162
+ case 0x7C:
1163
+ if (variant_ == CPUVariant::CMOS_65C02) { // JMP (abs,X) - 65C02
1164
+ uint16_t base = fetchWord();
1165
+ pc_ = read(base + x_) | (read(base + x_ + 1) << 8);
1166
+ }
1167
+ break;
1168
+
1169
+ // JSR/RTS/RTI
1170
+ case 0x20:
1171
+ addr = fetchWord();
1172
+ pushWord(pc_ - 1);
1173
+ pc_ = addr;
1174
+ break;
1175
+ case 0x60:
1176
+ pc_ = popWord() + 1;
1177
+ break;
1178
+ case 0x40:
1179
+ p_ = (pop() & ~FLAG_B) | FLAG_U;
1180
+ pc_ = popWord();
1181
+ break;
1182
+
1183
+ // Flag operations
1184
+ case 0x18:
1185
+ setFlag(FLAG_C, false);
1186
+ break; // CLC
1187
+ case 0x38:
1188
+ setFlag(FLAG_C, true);
1189
+ break; // SEC
1190
+ case 0x58:
1191
+ setFlag(FLAG_I, false);
1192
+ break; // CLI
1193
+ case 0x78:
1194
+ setFlag(FLAG_I, true);
1195
+ break; // SEI
1196
+ case 0xB8:
1197
+ setFlag(FLAG_V, false);
1198
+ break; // CLV
1199
+ case 0xD8:
1200
+ setFlag(FLAG_D, false);
1201
+ break; // CLD
1202
+ case 0xF8:
1203
+ setFlag(FLAG_D, true);
1204
+ break; // SED
1205
+
1206
+ // BRK
1207
+ case 0x00:
1208
+ pc_++;
1209
+ pushWord(pc_);
1210
+ push(p_ | FLAG_B | FLAG_U);
1211
+ setFlag(FLAG_I, true);
1212
+ if (variant_ == CPUVariant::CMOS_65C02) {
1213
+ setFlag(FLAG_D, false); // 65C02 clears D on BRK
1214
+ }
1215
+ pc_ = read(0xFFFE) | (read(0xFFFF) << 8);
1216
+ break;
1217
+
1218
+ // NOP
1219
+ case 0xEA:
1220
+ break;
1221
+
1222
+ // 65C02 NOPs with operands
1223
+ case 0x02:
1224
+ case 0x22:
1225
+ case 0x42:
1226
+ case 0x62:
1227
+ case 0x82:
1228
+ case 0xC2:
1229
+ case 0xE2:
1230
+ if (variant_ == CPUVariant::CMOS_65C02)
1231
+ pc_++;
1232
+ break;
1233
+ case 0x44:
1234
+ if (variant_ == CPUVariant::CMOS_65C02)
1235
+ pc_++;
1236
+ break;
1237
+ case 0x54:
1238
+ case 0xD4:
1239
+ case 0xF4:
1240
+ if (variant_ == CPUVariant::CMOS_65C02)
1241
+ pc_++;
1242
+ break;
1243
+ case 0x5C:
1244
+ if (variant_ == CPUVariant::CMOS_65C02)
1245
+ pc_ += 2;
1246
+ break;
1247
+ case 0xDC:
1248
+ case 0xFC:
1249
+ if (variant_ == CPUVariant::CMOS_65C02)
1250
+ pc_ += 2;
1251
+ break;
1252
+
1253
+ // TRB/TSB (65C02)
1254
+ case 0x14:
1255
+ if (variant_ == CPUVariant::CMOS_65C02) {
1256
+ addr = addrZeroPage();
1257
+ value = read(addr);
1258
+ setFlag(FLAG_Z, (a_ & value) == 0);
1259
+ write(addr, value & ~a_);
1260
+ }
1261
+ break;
1262
+ case 0x1C:
1263
+ if (variant_ == CPUVariant::CMOS_65C02) {
1264
+ addr = addrAbsolute();
1265
+ value = read(addr);
1266
+ setFlag(FLAG_Z, (a_ & value) == 0);
1267
+ write(addr, value & ~a_);
1268
+ }
1269
+ break;
1270
+ case 0x04:
1271
+ if (variant_ == CPUVariant::CMOS_65C02) {
1272
+ addr = addrZeroPage();
1273
+ value = read(addr);
1274
+ setFlag(FLAG_Z, (a_ & value) == 0);
1275
+ write(addr, value | a_);
1276
+ }
1277
+ break;
1278
+ case 0x0C:
1279
+ if (variant_ == CPUVariant::CMOS_65C02) {
1280
+ addr = addrAbsolute();
1281
+ value = read(addr);
1282
+ setFlag(FLAG_Z, (a_ & value) == 0);
1283
+ write(addr, value | a_);
1284
+ }
1285
+ break;
1286
+
1287
+ // BBR/BBS (65C02 - Rockwell/WDC)
1288
+ case 0x0F:
1289
+ case 0x1F:
1290
+ case 0x2F:
1291
+ case 0x3F:
1292
+ case 0x4F:
1293
+ case 0x5F:
1294
+ case 0x6F:
1295
+ case 0x7F:
1296
+ if (variant_ == CPUVariant::CMOS_65C02) {
1297
+ addr = addrZeroPage();
1298
+ value = read(addr);
1299
+ int8_t offset = static_cast<int8_t>(fetch());
1300
+ int bit = (opcode >> 4) & 7;
1301
+ if (!(value & (1 << bit))) {
1302
+ pc_ += offset;
1303
+ }
1304
+ }
1305
+ break;
1306
+ case 0x8F:
1307
+ case 0x9F:
1308
+ case 0xAF:
1309
+ case 0xBF:
1310
+ case 0xCF:
1311
+ case 0xDF:
1312
+ case 0xEF:
1313
+ case 0xFF:
1314
+ if (variant_ == CPUVariant::CMOS_65C02) {
1315
+ addr = addrZeroPage();
1316
+ value = read(addr);
1317
+ int8_t offset = static_cast<int8_t>(fetch());
1318
+ int bit = (opcode >> 4) & 7;
1319
+ if (value & (1 << bit)) {
1320
+ pc_ += offset;
1321
+ }
1322
+ }
1323
+ break;
1324
+
1325
+ // RMB/SMB (65C02 - Rockwell/WDC)
1326
+ case 0x07:
1327
+ case 0x17:
1328
+ case 0x27:
1329
+ case 0x37:
1330
+ case 0x47:
1331
+ case 0x57:
1332
+ case 0x67:
1333
+ case 0x77:
1334
+ if (variant_ == CPUVariant::CMOS_65C02) {
1335
+ addr = addrZeroPage();
1336
+ value = read(addr);
1337
+ int bit = (opcode >> 4) & 7;
1338
+ write(addr, value & ~(1 << bit));
1339
+ }
1340
+ break;
1341
+ case 0x87:
1342
+ case 0x97:
1343
+ case 0xA7:
1344
+ case 0xB7:
1345
+ case 0xC7:
1346
+ case 0xD7:
1347
+ case 0xE7:
1348
+ case 0xF7:
1349
+ if (variant_ == CPUVariant::CMOS_65C02) {
1350
+ addr = addrZeroPage();
1351
+ value = read(addr);
1352
+ int bit = (opcode >> 4) & 7;
1353
+ write(addr, value | (1 << bit));
1354
+ }
1355
+ break;
1356
+
1357
+ // WAI/STP (65C02 - WDC)
1358
+ case 0xCB: // WAI - Wait for interrupt
1359
+ if (variant_ == CPUVariant::CMOS_65C02) {
1360
+ // Wait until interrupt - simplified: just continue
1361
+ }
1362
+ break;
1363
+ case 0xDB: // STP - Stop processor
1364
+ if (variant_ == CPUVariant::CMOS_65C02) {
1365
+ // Stop until reset - simplified: do nothing
1366
+ }
1367
+ break;
1368
+
1369
+ default:
1370
+ // Unknown opcode - treat as NOP
1371
+ break;
1372
+ }
1373
+ }
1374
+
1375
+ std::string CPU6502::disassembleAt(uint16_t address) const {
1376
+ // Addressing mode enumeration
1377
+ enum AddrMode {
1378
+ IMP, // Implied
1379
+ ACC, // Accumulator
1380
+ IMM, // Immediate #$nn
1381
+ ZP, // Zero Page $nn
1382
+ ZPX, // Zero Page,X $nn,X
1383
+ ZPY, // Zero Page,Y $nn,Y
1384
+ ABS, // Absolute $nnnn
1385
+ ABX, // Absolute,X $nnnn,X
1386
+ ABY, // Absolute,Y $nnnn,Y
1387
+ IND, // Indirect ($nnnn)
1388
+ IZX, // Indexed Indirect ($nn,X)
1389
+ IZY, // Indirect Indexed ($nn),Y
1390
+ REL, // Relative (branches)
1391
+ ZPI, // Zero Page Indirect ($nn) - 65C02
1392
+ AIX, // Absolute Indexed Indirect ($nnnn,X) - 65C02
1393
+ ZPR // Zero Page Relative (BBR/BBS) - 65C02
1394
+ };
1395
+
1396
+ // Opcode info: mnemonic and addressing mode
1397
+ struct OpcodeInfo {
1398
+ const char *mnemonic;
1399
+ AddrMode mode;
1400
+ };
1401
+
1402
+ static const OpcodeInfo opcodes[256] = {
1403
+ // 0x00-0x0F
1404
+ {"BRK", IMP}, {"ORA", IZX}, {"???", IMP}, {"???", IMP},
1405
+ {"TSB", ZP}, {"ORA", ZP}, {"ASL", ZP}, {"RMB0", ZP},
1406
+ {"PHP", IMP}, {"ORA", IMM}, {"ASL", ACC}, {"???", IMP},
1407
+ {"TSB", ABS}, {"ORA", ABS}, {"ASL", ABS}, {"BBR0", ZPR},
1408
+ // 0x10-0x1F
1409
+ {"BPL", REL}, {"ORA", IZY}, {"ORA", ZPI}, {"???", IMP},
1410
+ {"TRB", ZP}, {"ORA", ZPX}, {"ASL", ZPX}, {"RMB1", ZP},
1411
+ {"CLC", IMP}, {"ORA", ABY}, {"INC", ACC}, {"???", IMP},
1412
+ {"TRB", ABS}, {"ORA", ABX}, {"ASL", ABX}, {"BBR1", ZPR},
1413
+ // 0x20-0x2F
1414
+ {"JSR", ABS}, {"AND", IZX}, {"???", IMP}, {"???", IMP},
1415
+ {"BIT", ZP}, {"AND", ZP}, {"ROL", ZP}, {"RMB2", ZP},
1416
+ {"PLP", IMP}, {"AND", IMM}, {"ROL", ACC}, {"???", IMP},
1417
+ {"BIT", ABS}, {"AND", ABS}, {"ROL", ABS}, {"BBR2", ZPR},
1418
+ // 0x30-0x3F
1419
+ {"BMI", REL}, {"AND", IZY}, {"AND", ZPI}, {"???", IMP},
1420
+ {"BIT", ZPX}, {"AND", ZPX}, {"ROL", ZPX}, {"RMB3", ZP},
1421
+ {"SEC", IMP}, {"AND", ABY}, {"DEC", ACC}, {"???", IMP},
1422
+ {"BIT", ABX}, {"AND", ABX}, {"ROL", ABX}, {"BBR3", ZPR},
1423
+ // 0x40-0x4F
1424
+ {"RTI", IMP}, {"EOR", IZX}, {"???", IMP}, {"???", IMP},
1425
+ {"???", IMP}, {"EOR", ZP}, {"LSR", ZP}, {"RMB4", ZP},
1426
+ {"PHA", IMP}, {"EOR", IMM}, {"LSR", ACC}, {"???", IMP},
1427
+ {"JMP", ABS}, {"EOR", ABS}, {"LSR", ABS}, {"BBR4", ZPR},
1428
+ // 0x50-0x5F
1429
+ {"BVC", REL}, {"EOR", IZY}, {"EOR", ZPI}, {"???", IMP},
1430
+ {"???", IMP}, {"EOR", ZPX}, {"LSR", ZPX}, {"RMB5", ZP},
1431
+ {"CLI", IMP}, {"EOR", ABY}, {"PHY", IMP}, {"???", IMP},
1432
+ {"???", IMP}, {"EOR", ABX}, {"LSR", ABX}, {"BBR5", ZPR},
1433
+ // 0x60-0x6F
1434
+ {"RTS", IMP}, {"ADC", IZX}, {"???", IMP}, {"???", IMP},
1435
+ {"STZ", ZP}, {"ADC", ZP}, {"ROR", ZP}, {"RMB6", ZP},
1436
+ {"PLA", IMP}, {"ADC", IMM}, {"ROR", ACC}, {"???", IMP},
1437
+ {"JMP", IND}, {"ADC", ABS}, {"ROR", ABS}, {"BBR6", ZPR},
1438
+ // 0x70-0x7F
1439
+ {"BVS", REL}, {"ADC", IZY}, {"ADC", ZPI}, {"???", IMP},
1440
+ {"STZ", ZPX}, {"ADC", ZPX}, {"ROR", ZPX}, {"RMB7", ZP},
1441
+ {"SEI", IMP}, {"ADC", ABY}, {"PLY", IMP}, {"???", IMP},
1442
+ {"JMP", AIX}, {"ADC", ABX}, {"ROR", ABX}, {"BBR7", ZPR},
1443
+ // 0x80-0x8F
1444
+ {"BRA", REL}, {"STA", IZX}, {"???", IMP}, {"???", IMP},
1445
+ {"STY", ZP}, {"STA", ZP}, {"STX", ZP}, {"SMB0", ZP},
1446
+ {"DEY", IMP}, {"BIT", IMM}, {"TXA", IMP}, {"???", IMP},
1447
+ {"STY", ABS}, {"STA", ABS}, {"STX", ABS}, {"BBS0", ZPR},
1448
+ // 0x90-0x9F
1449
+ {"BCC", REL}, {"STA", IZY}, {"STA", ZPI}, {"???", IMP},
1450
+ {"STY", ZPX}, {"STA", ZPX}, {"STX", ZPY}, {"SMB1", ZP},
1451
+ {"TYA", IMP}, {"STA", ABY}, {"TXS", IMP}, {"???", IMP},
1452
+ {"STZ", ABS}, {"STA", ABX}, {"STZ", ABX}, {"BBS1", ZPR},
1453
+ // 0xA0-0xAF
1454
+ {"LDY", IMM}, {"LDA", IZX}, {"LDX", IMM}, {"???", IMP},
1455
+ {"LDY", ZP}, {"LDA", ZP}, {"LDX", ZP}, {"SMB2", ZP},
1456
+ {"TAY", IMP}, {"LDA", IMM}, {"TAX", IMP}, {"???", IMP},
1457
+ {"LDY", ABS}, {"LDA", ABS}, {"LDX", ABS}, {"BBS2", ZPR},
1458
+ // 0xB0-0xBF
1459
+ {"BCS", REL}, {"LDA", IZY}, {"LDA", ZPI}, {"???", IMP},
1460
+ {"LDY", ZPX}, {"LDA", ZPX}, {"LDX", ZPY}, {"SMB3", ZP},
1461
+ {"CLV", IMP}, {"LDA", ABY}, {"TSX", IMP}, {"???", IMP},
1462
+ {"LDY", ABX}, {"LDA", ABX}, {"LDX", ABY}, {"BBS3", ZPR},
1463
+ // 0xC0-0xCF
1464
+ {"CPY", IMM}, {"CMP", IZX}, {"???", IMP}, {"???", IMP},
1465
+ {"CPY", ZP}, {"CMP", ZP}, {"DEC", ZP}, {"SMB4", ZP},
1466
+ {"INY", IMP}, {"CMP", IMM}, {"DEX", IMP}, {"WAI", IMP},
1467
+ {"CPY", ABS}, {"CMP", ABS}, {"DEC", ABS}, {"BBS4", ZPR},
1468
+ // 0xD0-0xDF
1469
+ {"BNE", REL}, {"CMP", IZY}, {"CMP", ZPI}, {"???", IMP},
1470
+ {"???", IMP}, {"CMP", ZPX}, {"DEC", ZPX}, {"SMB5", ZP},
1471
+ {"CLD", IMP}, {"CMP", ABY}, {"PHX", IMP}, {"STP", IMP},
1472
+ {"???", IMP}, {"CMP", ABX}, {"DEC", ABX}, {"BBS5", ZPR},
1473
+ // 0xE0-0xEF
1474
+ {"CPX", IMM}, {"SBC", IZX}, {"???", IMP}, {"???", IMP},
1475
+ {"CPX", ZP}, {"SBC", ZP}, {"INC", ZP}, {"SMB6", ZP},
1476
+ {"INX", IMP}, {"SBC", IMM}, {"NOP", IMP}, {"???", IMP},
1477
+ {"CPX", ABS}, {"SBC", ABS}, {"INC", ABS}, {"BBS6", ZPR},
1478
+ // 0xF0-0xFF
1479
+ {"BEQ", REL}, {"SBC", IZY}, {"SBC", ZPI}, {"???", IMP},
1480
+ {"???", IMP}, {"SBC", ZPX}, {"INC", ZPX}, {"SMB7", ZP},
1481
+ {"SED", IMP}, {"SBC", ABY}, {"PLX", IMP}, {"???", IMP},
1482
+ {"???", IMP}, {"SBC", ABX}, {"INC", ABX}, {"BBS7", ZPR}
1483
+ };
1484
+
1485
+ std::ostringstream ss;
1486
+ ss << std::hex << std::uppercase << std::setfill('0');
1487
+
1488
+ uint8_t opcode = read_(address);
1489
+ const OpcodeInfo &info = opcodes[opcode];
1490
+
1491
+ // Read operand bytes based on addressing mode
1492
+ uint8_t lo = 0, hi = 0;
1493
+ int instrLen = 1;
1494
+
1495
+ switch (info.mode) {
1496
+ case IMP:
1497
+ case ACC:
1498
+ instrLen = 1;
1499
+ break;
1500
+ case IMM:
1501
+ case ZP:
1502
+ case ZPX:
1503
+ case ZPY:
1504
+ case IZX:
1505
+ case IZY:
1506
+ case ZPI:
1507
+ case REL:
1508
+ lo = read_(address + 1);
1509
+ instrLen = 2;
1510
+ break;
1511
+ case ABS:
1512
+ case ABX:
1513
+ case ABY:
1514
+ case IND:
1515
+ case AIX:
1516
+ lo = read_(address + 1);
1517
+ hi = read_(address + 2);
1518
+ instrLen = 3;
1519
+ break;
1520
+ case ZPR:
1521
+ lo = read_(address + 1);
1522
+ hi = read_(address + 2);
1523
+ instrLen = 3;
1524
+ break;
1525
+ }
1526
+
1527
+ // Format: "AAAA: BB BB BB MMM OPERAND"
1528
+ ss << std::setw(4) << address << ": ";
1529
+
1530
+ // Output instruction bytes
1531
+ ss << std::setw(2) << static_cast<int>(opcode);
1532
+ if (instrLen >= 2) {
1533
+ ss << " " << std::setw(2) << static_cast<int>(lo);
1534
+ } else {
1535
+ ss << " ";
1536
+ }
1537
+ if (instrLen >= 3) {
1538
+ ss << " " << std::setw(2) << static_cast<int>(hi);
1539
+ } else {
1540
+ ss << " ";
1541
+ }
1542
+
1543
+ // Mnemonic
1544
+ ss << " " << info.mnemonic;
1545
+
1546
+ // Format operand based on addressing mode
1547
+ switch (info.mode) {
1548
+ case IMP:
1549
+ break;
1550
+ case ACC:
1551
+ ss << " A";
1552
+ break;
1553
+ case IMM:
1554
+ ss << " #$" << std::setw(2) << static_cast<int>(lo);
1555
+ break;
1556
+ case ZP:
1557
+ ss << " $" << std::setw(2) << static_cast<int>(lo);
1558
+ break;
1559
+ case ZPX:
1560
+ ss << " $" << std::setw(2) << static_cast<int>(lo) << ",X";
1561
+ break;
1562
+ case ZPY:
1563
+ ss << " $" << std::setw(2) << static_cast<int>(lo) << ",Y";
1564
+ break;
1565
+ case ABS:
1566
+ ss << " $" << std::setw(4) << static_cast<int>((hi << 8) | lo);
1567
+ break;
1568
+ case ABX:
1569
+ ss << " $" << std::setw(4) << static_cast<int>((hi << 8) | lo) << ",X";
1570
+ break;
1571
+ case ABY:
1572
+ ss << " $" << std::setw(4) << static_cast<int>((hi << 8) | lo) << ",Y";
1573
+ break;
1574
+ case IND:
1575
+ ss << " ($" << std::setw(4) << static_cast<int>((hi << 8) | lo) << ")";
1576
+ break;
1577
+ case IZX:
1578
+ ss << " ($" << std::setw(2) << static_cast<int>(lo) << ",X)";
1579
+ break;
1580
+ case IZY:
1581
+ ss << " ($" << std::setw(2) << static_cast<int>(lo) << "),Y";
1582
+ break;
1583
+ case ZPI:
1584
+ ss << " ($" << std::setw(2) << static_cast<int>(lo) << ")";
1585
+ break;
1586
+ case AIX:
1587
+ ss << " ($" << std::setw(4) << static_cast<int>((hi << 8) | lo) << ",X)";
1588
+ break;
1589
+ case REL: {
1590
+ // Calculate branch target
1591
+ int8_t offset = static_cast<int8_t>(lo);
1592
+ uint16_t target = address + 2 + offset;
1593
+ ss << " $" << std::setw(4) << static_cast<int>(target);
1594
+ break;
1595
+ }
1596
+ case ZPR: {
1597
+ // BBR/BBS: zero page address and relative branch
1598
+ int8_t offset = static_cast<int8_t>(hi);
1599
+ uint16_t target = address + 3 + offset;
1600
+ ss << " $" << std::setw(2) << static_cast<int>(lo) << ",$"
1601
+ << std::setw(4) << static_cast<int>(target);
1602
+ break;
1603
+ }
1604
+ }
1605
+
1606
+ return ss.str();
1607
+ }
1608
+
1609
+ } // namespace a2e