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,120 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* smartport_card.hpp - SmartPort expansion card for ProDOS block devices
|
|
3
|
+
*
|
|
4
|
+
* Written by
|
|
5
|
+
* Mike Daley <michael_daley@icloud.com>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include "../expansion_card.hpp"
|
|
11
|
+
#include "block_device.hpp"
|
|
12
|
+
#include <cstdint>
|
|
13
|
+
#include <functional>
|
|
14
|
+
#include <array>
|
|
15
|
+
|
|
16
|
+
namespace a2e {
|
|
17
|
+
|
|
18
|
+
class SmartPortCard : public ExpansionCard {
|
|
19
|
+
public:
|
|
20
|
+
static constexpr int MAX_DEVICES = 2;
|
|
21
|
+
|
|
22
|
+
using MemReadCallback = std::function<uint8_t(uint16_t)>;
|
|
23
|
+
using MemWriteCallback = std::function<void(uint16_t, uint8_t)>;
|
|
24
|
+
using RegGetCallback8 = std::function<uint8_t()>;
|
|
25
|
+
using RegSetCallback8 = std::function<void(uint8_t)>;
|
|
26
|
+
using RegGetCallback16 = std::function<uint16_t()>;
|
|
27
|
+
using RegSetCallback16 = std::function<void(uint16_t)>;
|
|
28
|
+
|
|
29
|
+
SmartPortCard();
|
|
30
|
+
~SmartPortCard() override = default;
|
|
31
|
+
|
|
32
|
+
// ExpansionCard interface
|
|
33
|
+
uint8_t readIO(uint8_t offset) override;
|
|
34
|
+
void writeIO(uint8_t offset, uint8_t value) override;
|
|
35
|
+
uint8_t peekIO(uint8_t offset) const override { return 0xFF; }
|
|
36
|
+
|
|
37
|
+
uint8_t readROM(uint8_t offset) override;
|
|
38
|
+
bool hasROM() const override { return hasAnyDevice(); }
|
|
39
|
+
|
|
40
|
+
void reset() override;
|
|
41
|
+
const char* getName() const override { return "SmartPort"; }
|
|
42
|
+
uint8_t getPreferredSlot() const override { return 7; }
|
|
43
|
+
|
|
44
|
+
// State serialization
|
|
45
|
+
size_t getStateSize() const override;
|
|
46
|
+
size_t serialize(uint8_t* buffer, size_t maxSize) const override;
|
|
47
|
+
size_t deserialize(const uint8_t* buffer, size_t size) override;
|
|
48
|
+
|
|
49
|
+
// Slot configuration
|
|
50
|
+
void setSlotNumber(uint8_t slot);
|
|
51
|
+
uint8_t getSlotNumber() const { return slotNum_; }
|
|
52
|
+
|
|
53
|
+
// Device management
|
|
54
|
+
bool insertImage(int device, const uint8_t* data, size_t size, const std::string& filename);
|
|
55
|
+
void ejectImage(int device);
|
|
56
|
+
bool isImageInserted(int device) const;
|
|
57
|
+
const std::string& getImageFilename(int device) const;
|
|
58
|
+
bool isImageModified(int device) const;
|
|
59
|
+
const uint8_t* exportImageData(int device, size_t* size) const;
|
|
60
|
+
const uint8_t* getBlockData(int device, size_t* size) const;
|
|
61
|
+
BlockDevice* getDevice(int device);
|
|
62
|
+
const BlockDevice* getDevice(int device) const;
|
|
63
|
+
|
|
64
|
+
// Callbacks for memory and CPU access
|
|
65
|
+
void setMemReadCallback(MemReadCallback cb) { memRead_ = cb; }
|
|
66
|
+
void setMemWriteCallback(MemWriteCallback cb) { memWrite_ = cb; }
|
|
67
|
+
void setGetA(RegGetCallback8 cb) { getA_ = cb; }
|
|
68
|
+
void setSetA(RegSetCallback8 cb) { setA_ = cb; }
|
|
69
|
+
void setGetP(RegGetCallback8 cb) { getP_ = cb; }
|
|
70
|
+
void setSetP(RegSetCallback8 cb) { setP_ = cb; }
|
|
71
|
+
void setGetSP(RegGetCallback8 cb) { getSP_ = cb; }
|
|
72
|
+
void setSetSP(RegSetCallback8 cb) { setSP_ = cb; }
|
|
73
|
+
void setGetPC(RegGetCallback16 cb) { getPC_ = cb; }
|
|
74
|
+
void setSetPC(RegSetCallback16 cb) { setPC_ = cb; }
|
|
75
|
+
void setSetX(RegSetCallback8 cb) { setX_ = cb; }
|
|
76
|
+
void setSetY(RegSetCallback8 cb) { setY_ = cb; }
|
|
77
|
+
|
|
78
|
+
// Activity tracking for UI
|
|
79
|
+
bool hasActivity() const { return activity_; }
|
|
80
|
+
bool isActivityWrite() const { return activityWrite_; }
|
|
81
|
+
void clearActivity() { activity_ = false; }
|
|
82
|
+
|
|
83
|
+
private:
|
|
84
|
+
void buildROM();
|
|
85
|
+
bool hasAnyDevice() const;
|
|
86
|
+
bool handleBoot();
|
|
87
|
+
void handleProDOSBlock();
|
|
88
|
+
void handleSmartPort();
|
|
89
|
+
void setErrorResult(uint8_t errorCode);
|
|
90
|
+
|
|
91
|
+
uint8_t slotNum_ = 7;
|
|
92
|
+
std::array<uint8_t, 256> rom_;
|
|
93
|
+
std::array<BlockDevice, MAX_DEVICES> devices_;
|
|
94
|
+
|
|
95
|
+
// Boot state: false until first boot completes, then ProDOS calls are handled
|
|
96
|
+
bool booted_ = false;
|
|
97
|
+
|
|
98
|
+
// Activity LED state
|
|
99
|
+
bool activity_ = false;
|
|
100
|
+
bool activityWrite_ = false;
|
|
101
|
+
|
|
102
|
+
// Static empty string for when no device is loaded
|
|
103
|
+
static const std::string emptyString_;
|
|
104
|
+
|
|
105
|
+
// Callbacks
|
|
106
|
+
MemReadCallback memRead_;
|
|
107
|
+
MemWriteCallback memWrite_;
|
|
108
|
+
RegGetCallback8 getA_;
|
|
109
|
+
RegSetCallback8 setA_;
|
|
110
|
+
RegGetCallback8 getP_;
|
|
111
|
+
RegSetCallback8 setP_;
|
|
112
|
+
RegGetCallback8 getSP_;
|
|
113
|
+
RegSetCallback8 setSP_;
|
|
114
|
+
RegGetCallback16 getPC_;
|
|
115
|
+
RegSetCallback16 setPC_;
|
|
116
|
+
RegSetCallback8 setX_;
|
|
117
|
+
RegSetCallback8 setY_;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
} // namespace a2e
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* thunderclock_card.cpp - Thunderclock Plus real-time clock card implementation
|
|
3
|
+
*
|
|
4
|
+
* Written by
|
|
5
|
+
* Mike Daley <michael_daley@icloud.com>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#include "thunderclock_card.hpp"
|
|
9
|
+
#include "roms.cpp" // For embedded ROM data
|
|
10
|
+
#include <ctime>
|
|
11
|
+
#include <cstring>
|
|
12
|
+
|
|
13
|
+
namespace a2e {
|
|
14
|
+
|
|
15
|
+
// I/O offsets (masked with 0x8F)
|
|
16
|
+
static constexpr uint8_t LOC_CONTROL = 0x00; // $C0n0 - Control register
|
|
17
|
+
static constexpr uint8_t LOC_AUX = 0x08; // $C0n8 - Aux register (unused)
|
|
18
|
+
|
|
19
|
+
// Control register flags
|
|
20
|
+
static constexpr uint8_t FLAG_CLOCK = 0x02; // Clock signal
|
|
21
|
+
static constexpr uint8_t FLAG_STROBE = 0x04; // Strobe signal
|
|
22
|
+
|
|
23
|
+
// Commands (bits 3-5 of control register, directly from uPD1990C C0-C2 pins)
|
|
24
|
+
// uPD1990C command encoding:
|
|
25
|
+
// 000 = Register Hold
|
|
26
|
+
// 001 = Register Shift
|
|
27
|
+
// 010 = Time Set
|
|
28
|
+
// 011 = Time Read
|
|
29
|
+
// 100 = TP 64Hz
|
|
30
|
+
// 101 = TP 256Hz
|
|
31
|
+
// 110 = TP 2048Hz
|
|
32
|
+
// 111 = Test Mode
|
|
33
|
+
static constexpr uint8_t CMD_REGHOLD = 0x00; // 000 - Hold register
|
|
34
|
+
static constexpr uint8_t CMD_REGSHIFT = 0x01; // 001 - Shift register
|
|
35
|
+
static constexpr uint8_t CMD_TIMESET = 0x02; // 010 - Time set
|
|
36
|
+
static constexpr uint8_t CMD_TIMEREAD = 0x03; // 011 - Time read
|
|
37
|
+
|
|
38
|
+
ThunderclockCard::ThunderclockCard()
|
|
39
|
+
: rom_(roms::ROM_THUNDERCLOCK)
|
|
40
|
+
, romSize_(roms::ROM_THUNDERCLOCK_SIZE)
|
|
41
|
+
{
|
|
42
|
+
latches_.fill(0);
|
|
43
|
+
reset();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
void ThunderclockCard::updateLatches() {
|
|
47
|
+
// Get current time
|
|
48
|
+
std::time_t now = std::time(nullptr);
|
|
49
|
+
std::tm* tm = std::localtime(&now);
|
|
50
|
+
|
|
51
|
+
if (!tm) {
|
|
52
|
+
// Fallback if time unavailable
|
|
53
|
+
latches_.fill(0);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
int month = tm->tm_mon + 1; // 1-12 (convert from 0-11)
|
|
58
|
+
int dayOfWeek = tm->tm_wday; // 0-6 (Sunday=0)
|
|
59
|
+
int dayOfMonth = tm->tm_mday; // 1-31
|
|
60
|
+
int hour = tm->tm_hour; // 0-23
|
|
61
|
+
int minute = tm->tm_min; // 0-59
|
|
62
|
+
int second = tm->tm_sec; // 0-59
|
|
63
|
+
|
|
64
|
+
// Clear bits array
|
|
65
|
+
bitIndex_ = 0;
|
|
66
|
+
|
|
67
|
+
// Thunderclock Plus format (from AppleWin):
|
|
68
|
+
// 10 BCD nibbles, LSB-first within each nibble
|
|
69
|
+
// Order: sec_ones, sec_tens, min_ones, min_tens, hr_ones, hr_tens, day_ones, day_tens, dow, month
|
|
70
|
+
auto shiftNibbleLSB = [this](int value) {
|
|
71
|
+
for (int i = 0; i < 4; i++) {
|
|
72
|
+
if (bitIndex_ < 64) {
|
|
73
|
+
bits_[bitIndex_++] = (value >> i) & 1;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
shiftNibbleLSB(second % 10); // Nibble 0: Second ones
|
|
79
|
+
shiftNibbleLSB(second / 10); // Nibble 1: Second tens
|
|
80
|
+
shiftNibbleLSB(minute % 10); // Nibble 2: Minute ones
|
|
81
|
+
shiftNibbleLSB(minute / 10); // Nibble 3: Minute tens
|
|
82
|
+
shiftNibbleLSB(hour % 10); // Nibble 4: Hour ones
|
|
83
|
+
shiftNibbleLSB(hour / 10); // Nibble 5: Hour tens
|
|
84
|
+
shiftNibbleLSB(dayOfMonth % 10); // Nibble 6: Day ones
|
|
85
|
+
shiftNibbleLSB(dayOfMonth / 10); // Nibble 7: Day tens
|
|
86
|
+
shiftNibbleLSB(dayOfWeek); // Nibble 8: Day of week (0-6)
|
|
87
|
+
shiftNibbleLSB(month); // Nibble 9: Month (1-12)
|
|
88
|
+
|
|
89
|
+
// Total: 10 * 4 = 40 bits
|
|
90
|
+
currentBitIndex_ = 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
uint8_t ThunderclockCard::readIO(uint8_t offset) {
|
|
94
|
+
// The Thunderclock uses offset 0 ($C0n0) for the control/data register
|
|
95
|
+
// All 16 I/O addresses appear to return the same register value
|
|
96
|
+
(void)offset;
|
|
97
|
+
|
|
98
|
+
// Return current register value
|
|
99
|
+
// Bit 7 contains the current data bit
|
|
100
|
+
return register_;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
void ThunderclockCard::writeIO(uint8_t offset, uint8_t value) {
|
|
104
|
+
// The Thunderclock uses offset 0 ($C0n0) for the control register
|
|
105
|
+
// All 16 I/O addresses appear to access the same register
|
|
106
|
+
(void)offset;
|
|
107
|
+
|
|
108
|
+
// Extract control signals
|
|
109
|
+
bool strobe = (value & FLAG_STROBE) != 0;
|
|
110
|
+
bool clock = (value & FLAG_CLOCK) != 0;
|
|
111
|
+
|
|
112
|
+
// Check for strobe rising edge
|
|
113
|
+
if (strobe && !strobe_) {
|
|
114
|
+
// Capture command from bits 3-5 (C0, C1, C2 of uPD1990C)
|
|
115
|
+
command_ = (value >> 3) & 0x07;
|
|
116
|
+
|
|
117
|
+
if (command_ == CMD_TIMEREAD) {
|
|
118
|
+
// Time read command - refresh the bit stream
|
|
119
|
+
updateLatches();
|
|
120
|
+
// First bit should be immediately available on DATA OUT after load
|
|
121
|
+
if (bitIndex_ > 0) {
|
|
122
|
+
register_ = bits_[0] ? 0x80 : 0x00;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Check for clock rising edge
|
|
128
|
+
if (clock && !clock_) {
|
|
129
|
+
// Shift out next bit - increment FIRST, then read
|
|
130
|
+
// This is because bit 0 is already presented after strobe/load
|
|
131
|
+
if (command_ == CMD_REGSHIFT || command_ == CMD_TIMEREAD) {
|
|
132
|
+
currentBitIndex_++; // Advance to next bit position
|
|
133
|
+
if (currentBitIndex_ < bitIndex_) {
|
|
134
|
+
// Set bit 7 of register based on current bit
|
|
135
|
+
if (bits_[currentBitIndex_]) {
|
|
136
|
+
register_ |= 0x80;
|
|
137
|
+
} else {
|
|
138
|
+
register_ &= ~0x80;
|
|
139
|
+
}
|
|
140
|
+
} else {
|
|
141
|
+
register_ &= ~0x80;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Save current state
|
|
147
|
+
strobe_ = strobe;
|
|
148
|
+
clock_ = clock;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
uint8_t ThunderclockCard::peekIO(uint8_t offset) const {
|
|
152
|
+
(void)offset;
|
|
153
|
+
return register_;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
uint8_t ThunderclockCard::readROM(uint8_t offset) {
|
|
157
|
+
// Slot ROM is the first 256 bytes
|
|
158
|
+
if (rom_ && romSize_ >= 256) {
|
|
159
|
+
return rom_[offset];
|
|
160
|
+
}
|
|
161
|
+
return 0xFF;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
uint8_t ThunderclockCard::readExpansionROM(uint16_t offset) {
|
|
165
|
+
// Expansion ROM maps the ENTIRE 2KB ROM at $C800-$CFFF
|
|
166
|
+
// On real Thunderclock Plus hardware, the same 2KB ROM chip is used for both:
|
|
167
|
+
// - Slot ROM ($Cn00-$CnFF): reads bytes 0-255
|
|
168
|
+
// - Expansion ROM ($C800-$CFFF): reads bytes 0-2047 (full ROM)
|
|
169
|
+
if (rom_ && offset < romSize_) {
|
|
170
|
+
return rom_[offset];
|
|
171
|
+
}
|
|
172
|
+
return 0xFF;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
void ThunderclockCard::reset() {
|
|
176
|
+
// Reset all state
|
|
177
|
+
strobe_ = false;
|
|
178
|
+
clock_ = false;
|
|
179
|
+
command_ = 0;
|
|
180
|
+
register_ = 0;
|
|
181
|
+
bitIndex_ = 0;
|
|
182
|
+
currentBitIndex_ = 0;
|
|
183
|
+
|
|
184
|
+
// Initialize time data
|
|
185
|
+
updateLatches();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
size_t ThunderclockCard::serialize(uint8_t* buffer, size_t maxSize) const {
|
|
189
|
+
if (maxSize < STATE_SIZE) return 0;
|
|
190
|
+
|
|
191
|
+
size_t offset = 0;
|
|
192
|
+
|
|
193
|
+
// Control state
|
|
194
|
+
buffer[offset++] = strobe_ ? 1 : 0;
|
|
195
|
+
buffer[offset++] = clock_ ? 1 : 0;
|
|
196
|
+
buffer[offset++] = command_;
|
|
197
|
+
buffer[offset++] = register_;
|
|
198
|
+
buffer[offset++] = static_cast<uint8_t>(bitIndex_);
|
|
199
|
+
buffer[offset++] = static_cast<uint8_t>(currentBitIndex_);
|
|
200
|
+
|
|
201
|
+
// Bit stream data (64 bytes)
|
|
202
|
+
for (int i = 0; i < 64; i++) {
|
|
203
|
+
buffer[offset++] = bits_[i];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Reserved (2 bytes for future use)
|
|
207
|
+
buffer[offset++] = 0;
|
|
208
|
+
buffer[offset++] = 0;
|
|
209
|
+
|
|
210
|
+
return offset; // Should be 72 bytes
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
size_t ThunderclockCard::deserialize(const uint8_t* buffer, size_t size) {
|
|
214
|
+
if (size < STATE_SIZE) return 0;
|
|
215
|
+
|
|
216
|
+
size_t offset = 0;
|
|
217
|
+
|
|
218
|
+
// Control state
|
|
219
|
+
strobe_ = buffer[offset++] != 0;
|
|
220
|
+
clock_ = buffer[offset++] != 0;
|
|
221
|
+
command_ = buffer[offset++];
|
|
222
|
+
register_ = buffer[offset++];
|
|
223
|
+
bitIndex_ = buffer[offset++];
|
|
224
|
+
currentBitIndex_ = buffer[offset++];
|
|
225
|
+
|
|
226
|
+
// Bit stream data (64 bytes)
|
|
227
|
+
for (int i = 0; i < 64; i++) {
|
|
228
|
+
bits_[i] = buffer[offset++];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Skip reserved bytes
|
|
232
|
+
offset += 2;
|
|
233
|
+
|
|
234
|
+
return offset; // Should be 72 bytes
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
} // namespace a2e
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* thunderclock_card.hpp - Thunderclock Plus real-time clock card
|
|
3
|
+
*
|
|
4
|
+
* Written by
|
|
5
|
+
* Mike Daley <michael_daley@icloud.com>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include "expansion_card.hpp"
|
|
11
|
+
#include <array>
|
|
12
|
+
#include <cstdint>
|
|
13
|
+
|
|
14
|
+
namespace a2e {
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* ThunderclockCard - ProDOS-compatible clock card
|
|
18
|
+
*
|
|
19
|
+
* Implements the Thunderclock Plus clock card that provides automatic
|
|
20
|
+
* date/time stamping for ProDOS applications. This eliminates the
|
|
21
|
+
* "Enter today's date" prompts and enables proper file timestamps.
|
|
22
|
+
*
|
|
23
|
+
* ProDOS Detection:
|
|
24
|
+
* ProDOS scans slots looking for specific ROM signature bytes:
|
|
25
|
+
* - $Cn00: $08 (PHP instruction)
|
|
26
|
+
* - $Cn02: $28 (signature byte)
|
|
27
|
+
* - $Cn04: $58 (signature byte)
|
|
28
|
+
* - $Cn06: $70 (signature byte)
|
|
29
|
+
*
|
|
30
|
+
* When ProDOS finds these bytes, it:
|
|
31
|
+
* 1. Sets bit 0 of MACHID ($BF98) to indicate clock present
|
|
32
|
+
* 2. Patches the clock driver address at $BF07-$BF08
|
|
33
|
+
* 3. Changes $BF06 from $60 (RTS) to $4C (JMP)
|
|
34
|
+
*
|
|
35
|
+
* Hardware Interface:
|
|
36
|
+
* The Thunderclock Plus uses a serial interface via the control register
|
|
37
|
+
* at $C0n0 (where n = slot + 8). Time data is shifted out bit by bit.
|
|
38
|
+
*
|
|
39
|
+
* Control Register ($C0n0):
|
|
40
|
+
* - Bit 2 (STROBE): Rising edge triggers command execution
|
|
41
|
+
* - Bit 1 (CLOCK): Rising edge shifts next data bit
|
|
42
|
+
* - Bits 3-5: Command (from uPD1990C C0-C2 pins)
|
|
43
|
+
*
|
|
44
|
+
* Time Data Format (40 bits, 10 BCD nibbles, LSB-first within each nibble):
|
|
45
|
+
* - Second ones, Second tens (8 bits)
|
|
46
|
+
* - Minute ones, Minute tens (8 bits)
|
|
47
|
+
* - Hour ones, Hour tens (8 bits)
|
|
48
|
+
* - Day ones, Day tens (8 bits)
|
|
49
|
+
* - Day of week (4 bits, 0-6)
|
|
50
|
+
* - Month (4 bits, 1-12)
|
|
51
|
+
*
|
|
52
|
+
* ROM Space:
|
|
53
|
+
* - Slot ROM ($Cn00-$CnFF): 256 bytes - contains ProDOS driver
|
|
54
|
+
* - Expansion ROM ($C800-$CFFF): 1792 bytes - contains utility routines
|
|
55
|
+
*
|
|
56
|
+
* Typically installed in Slot 5 or Slot 7.
|
|
57
|
+
*/
|
|
58
|
+
class ThunderclockCard : public ExpansionCard {
|
|
59
|
+
public:
|
|
60
|
+
ThunderclockCard();
|
|
61
|
+
~ThunderclockCard() override = default;
|
|
62
|
+
|
|
63
|
+
// Delete copy
|
|
64
|
+
ThunderclockCard(const ThunderclockCard&) = delete;
|
|
65
|
+
ThunderclockCard& operator=(const ThunderclockCard&) = delete;
|
|
66
|
+
|
|
67
|
+
// Allow move
|
|
68
|
+
ThunderclockCard(ThunderclockCard&&) = default;
|
|
69
|
+
ThunderclockCard& operator=(ThunderclockCard&&) = default;
|
|
70
|
+
|
|
71
|
+
// ===== ExpansionCard Interface =====
|
|
72
|
+
|
|
73
|
+
// I/O space ($C0n0-$C0nF)
|
|
74
|
+
uint8_t readIO(uint8_t offset) override;
|
|
75
|
+
void writeIO(uint8_t offset, uint8_t value) override;
|
|
76
|
+
uint8_t peekIO(uint8_t offset) const override;
|
|
77
|
+
|
|
78
|
+
// ROM space ($Cn00-$CnFF)
|
|
79
|
+
uint8_t readROM(uint8_t offset) override;
|
|
80
|
+
bool hasROM() const override { return true; }
|
|
81
|
+
|
|
82
|
+
// Expansion ROM ($C800-$CFFF)
|
|
83
|
+
bool hasExpansionROM() const override { return true; }
|
|
84
|
+
uint8_t readExpansionROM(uint16_t offset) override;
|
|
85
|
+
|
|
86
|
+
void reset() override;
|
|
87
|
+
|
|
88
|
+
const char* getName() const override { return "Thunderclock"; }
|
|
89
|
+
uint8_t getPreferredSlot() const override { return 5; }
|
|
90
|
+
|
|
91
|
+
// State serialization
|
|
92
|
+
static constexpr size_t STATE_SIZE = 72; // 6 bytes state + 64 bytes bits + 2 reserved
|
|
93
|
+
size_t getStateSize() const override { return STATE_SIZE; }
|
|
94
|
+
size_t serialize(uint8_t* buffer, size_t maxSize) const override;
|
|
95
|
+
size_t deserialize(const uint8_t* buffer, size_t size) override;
|
|
96
|
+
|
|
97
|
+
private:
|
|
98
|
+
// ROM data is loaded from embedded ROM file
|
|
99
|
+
const uint8_t* rom_; // Pointer to embedded ROM data
|
|
100
|
+
size_t romSize_; // Size of ROM (should be 2048)
|
|
101
|
+
|
|
102
|
+
// Serial interface state
|
|
103
|
+
bool strobe_ = false; // Previous strobe state
|
|
104
|
+
bool clock_ = false; // Previous clock state
|
|
105
|
+
uint8_t command_ = 0; // Current command
|
|
106
|
+
uint8_t register_ = 0; // Output register (bit 7 is data out)
|
|
107
|
+
|
|
108
|
+
// Time data as bit stream
|
|
109
|
+
std::array<uint8_t, 64> bits_; // Bit buffer
|
|
110
|
+
int bitIndex_ = 0; // Number of valid bits
|
|
111
|
+
int currentBitIndex_ = 0; // Current bit being read
|
|
112
|
+
|
|
113
|
+
// Unused but kept for API compatibility
|
|
114
|
+
std::array<uint8_t, 16> latches_;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Update the bit stream with current system time
|
|
118
|
+
*/
|
|
119
|
+
void updateLatches();
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
} // namespace a2e
|