From 4b70f37f72ec557edb8b7c958a0a5934e8742125 Mon Sep 17 00:00:00 2001 From: Kelly Fox Date: Wed, 14 Oct 2026 19:43:32 -0500 Subject: [PATCH 1/7] firmware: launch C128 .crt files laid out to the CRT spec A "C128 CARTRIDGE" file was routed by its first chip's load address, and only $0000 meant C128 mode. The CRT spec puts a generic C128 cart's ROML at $8000 and ROMH at $C000, so a spec .crt with an 8k ROML matched the C64 8k rule and started in C64 mode, and 16k or 32k ones never reached C128 mode at all. x128 refuses the $0000 layout. The signature now decides. A C128 crt maps an 8k or 16k chip at $8000 to ROML and one at $C000 to ROMH, with the ROM masks sized to each chip, and anything else is refused with a message. Nonzero C128 hardware types are refused instead of being looked up in the C64 list, where 1 means Action Replay rather than Warpspeed128. Both the main and minimal parsers change. The 789010 diagnostic cart in the menu moves its ROML to $8000, which is the copy of it the VICE manual uses as its generic C128 example. --- Source/Teensy/DriveDirLoad.ino | 10 ++++- Source/Teensy/FileParsers.ino | 30 ++++++++++---- .../Teensy/MinimalBoot/Common/DriveDirLoad.h | 22 ++++++++++ .../Teensy/MinimalBoot/Min_DriveDirLoad.ino | 40 ++++++++++++++----- .../Teensy/TRMenuFiles/ROMs/C128_789010.crt.h | 2 +- 5 files changed, 83 insertions(+), 21 deletions(-) diff --git a/Source/Teensy/DriveDirLoad.ino b/Source/Teensy/DriveDirLoad.ino index 253a32ee1..63c849617 100644 --- a/Source/Teensy/DriveDirLoad.ino +++ b/Source/Teensy/DriveDirLoad.ino @@ -392,12 +392,18 @@ FLASHMEM void HandleExecution() CartLoaded=true; break; case rtBinC128: + { + const StructCrtChip *ROML, *ROMH; + FindC128Chips(CrtChips, NumCrtChips, &ROML, &ROMH); SetGameDeassert; SetExROMDeassert; - LOROM_Image = MenuSelCpy.Code_Image; - HIROM_Image = NULL; + LOROM_Image = ROML ? ROML->ChipROM : NULL; + HIROM_Image = ROMH ? ROMH->ChipROM : NULL; + if (ROML) LOROM_Mask = ROML->ROMSize - 1; + if (ROMH) HIROM_Mask = ROMH->ROMSize - 1; CartLoaded=true; break; + } case rtFilePrg: //set up for transfer SendMsgPrintfln("PRG xfer %luK to $%04x:$%04x\n", diff --git a/Source/Teensy/FileParsers.ino b/Source/Teensy/FileParsers.ino index ca18e0d22..47b1dcb90 100644 --- a/Source/Teensy/FileParsers.ino +++ b/Source/Teensy/FileParsers.ino @@ -19,6 +19,7 @@ uint8_t *ptrRAM_ImageEnd = NULL; // Made global for REU access uint8_t NumCrtChips = 0; +bool CrtIsC128 = false; StructCrtChip CrtChips[MAX_CRT_CHIPS]; char* StrSIDInfo; // allocated to RAM2 via StrSIDInfoSize char* LatestSIDLoaded; // allocated to RAM2 via MaxPathLength @@ -57,7 +58,8 @@ bool ParseCRTHeader(StructMenuItem* MyMenuItem, uint8_t *EXROM, uint8_t *GAME) SendMsgPrintfln("Parsing CRT File"); SendMsgPrintfln("CRT image size: %luK $%08x", MyMenuItem->Size/1024, MyMenuItem->Size); - if (memcmp(CRT_Image, "C128 CARTRIDGE", 14)==0) SendMsgPrintfln("C128 crt"); + CrtIsC128 = (memcmp(CRT_Image, "C128 CARTRIDGE", 14)==0); + if (CrtIsC128) SendMsgPrintfln("C128 crt"); else if (memcmp(CRT_Image, "C64 CARTRIDGE", 13)==0) SendMsgPrintfln("C64 crt"); else { @@ -76,6 +78,12 @@ bool ParseCRTHeader(StructMenuItem* MyMenuItem, uint8_t *EXROM, uint8_t *GAME) uint16_t HWType = toU16(CRT_Image+0x16); SendMsgPrintfln("HW Type: %d ($%04x)", (int16_t)HWType, HWType); + if (CrtIsC128 && HWType != Cart_Generic) + { //C128 crts number their HW types separately: 1 is Warpspeed128, not Action Replay + SendMsgPrintfln("Unsupported C128 HW Type"); + return false; + } + #ifdef Fab04_Freezers // Create pseudo-CRT number (real HWType + 200) to select dual REU & CRT handler @@ -465,6 +473,19 @@ bool SetTypeFromCRT(StructMenuItem* MyMenuItem, uint8_t EXROM, uint8_t GAME) //check configuration + if (CrtIsC128) + { + const StructCrtChip *ROML, *ROMH; + if (!FindC128Chips(CrtChips, NumCrtChips, &ROML, &ROMH)) + { + SendMsgPrintfln("C128 chip layout unsupported"); + return false; + } + MyMenuItem->ItemType = rtBinC128; + SendMsgPrintfln(" C128 config"); + return true; + } + #ifdef Fab04_Freezers if (IO1[rwRegNextIOHndlr] == IOH_SuperSnapshotV5 || IO1[rwRegNextIOHndlr] == IOH_FinalCartridgeIII || @@ -501,13 +522,6 @@ bool SetTypeFromCRT(StructMenuItem* MyMenuItem, uint8_t EXROM, uint8_t GAME) return true; } - if(CrtChips[0].LoadAddress == 0x0000 && CrtChips[0].ROMSize == 0x2000 && EXROM==0 && GAME==0) - { - MyMenuItem->ItemType = rtBinC128; - SendMsgPrintfln(" C128 config"); - return true; - } - SendMsgPrintfln("HW config unknown"); return false; } diff --git a/Source/Teensy/MinimalBoot/Common/DriveDirLoad.h b/Source/Teensy/MinimalBoot/Common/DriveDirLoad.h index 59360f592..e6c3813b8 100644 --- a/Source/Teensy/MinimalBoot/Common/DriveDirLoad.h +++ b/Source/Teensy/MinimalBoot/Common/DriveDirLoad.h @@ -192,6 +192,28 @@ struct StructCrtChip uint16_t BankNum; }; +//Generic C128 cart (VICE CRT spec, C128 type 0): ROML is $8000-$BFFF and ROMH $C000-$FFFF, each +// one 8k (mirrored) or 16k chip. +inline bool FindC128Chips(const StructCrtChip *Chips, uint8_t NumChips, + const StructCrtChip **ROML, const StructCrtChip **ROMH) +{ + *ROML = *ROMH = NULL; + for (uint8_t ChipNum = 0; ChipNum < NumChips; ChipNum++) + { + const StructCrtChip *Chip = &Chips[ChipNum]; + if (Chip->BankNum != 0 || (Chip->ROMSize != 0x2000 && Chip->ROMSize != 0x4000)) return false; + + const StructCrtChip **Slot; + if (Chip->LoadAddress == 0x8000) Slot = ROML; + else if (Chip->LoadAddress == 0xC000) Slot = ROMH; + else return false; + + if (*Slot != NULL) return false; + *Slot = Chip; + } + return *ROML != NULL || *ROMH != NULL; +} + #ifndef MinimumBuild //only needed in full build: struct StructExt_ItemType_Assoc diff --git a/Source/Teensy/MinimalBoot/Min_DriveDirLoad.ino b/Source/Teensy/MinimalBoot/Min_DriveDirLoad.ino index 7b54cca12..caeaec4cb 100644 --- a/Source/Teensy/MinimalBoot/Min_DriveDirLoad.ino +++ b/Source/Teensy/MinimalBoot/Min_DriveDirLoad.ino @@ -19,6 +19,7 @@ uint8_t NumCrtChips = 0; +bool CrtIsC128 = false; StructCrtChip CrtChips[MAX_CRT_CHIPS]; File myFile = NULL; @@ -62,12 +63,18 @@ void HandleExecution() CartLoaded=true; break; case rtBinC128: + { + const StructCrtChip *ROML, *ROMH; + FindC128Chips(CrtChips, NumCrtChips, &ROML, &ROMH); SetGameDeassert; SetExROMDeassert; - LOROM_Image = MenuSelCpy.Code_Image; - HIROM_Image = NULL; + LOROM_Image = ROML ? ROML->ChipROM : NULL; + HIROM_Image = ROMH ? ROMH->ChipROM : NULL; + if (ROML) LOROM_Mask = ROML->ROMSize - 1; + if (ROMH) HIROM_Mask = ROMH->ROMSize - 1; CartLoaded=true; break; + } case rtUnknown: //had to have been marked unknown after check at start //SendMsgFailed(); SendMsgPrintfln(" :("); @@ -237,7 +244,8 @@ bool ParseCRTHeader(StructMenuItem* MyMenuItem, uint8_t *EXROM, uint8_t *GAME) SendMsgPrintfln("Parsing CRT File"); SendMsgPrintfln("CRT image size: %luK $%08x", MyMenuItem->Size/1024, MyMenuItem->Size); - if (memcmp(CRT_Image, "C128 CARTRIDGE", 14)==0) SendMsgPrintfln("C128 crt"); + CrtIsC128 = (memcmp(CRT_Image, "C128 CARTRIDGE", 14)==0); + if (CrtIsC128) SendMsgPrintfln("C128 crt"); else if (memcmp(CRT_Image, "C64 CARTRIDGE", 13)==0) SendMsgPrintfln("C64 crt"); else { @@ -256,6 +264,12 @@ bool ParseCRTHeader(StructMenuItem* MyMenuItem, uint8_t *EXROM, uint8_t *GAME) uint16_t HWType = toU16(CRT_Image+0x16); SendMsgPrintfln("HW Type: %d ($%04x)", (int16_t)HWType, HWType); + if (CrtIsC128 && HWType != Cart_Generic) + { //C128 crts number their HW types separately: 1 is Warpspeed128, not Action Replay + SendMsgPrintfln("Unsupported C128 HW Type"); + return false; + } + if (HWType != Cart_Generic) //leave IOH as default/user set for generic { if (!AssocHWID_IOH(HWType)) @@ -339,6 +353,19 @@ bool SetTypeFromCRT(StructMenuItem* MyMenuItem, uint8_t EXROM, uint8_t GAME) MyMenuItem->Code_Image = CrtChips[0].ChipROM; //check configuration + + if (CrtIsC128) + { + const StructCrtChip *ROML, *ROMH; + if (!FindC128Chips(CrtChips, NumCrtChips, &ROML, &ROMH)) + { + SendMsgPrintfln("C128 chip layout unsupported"); + return false; + } + MyMenuItem->ItemType = rtBinC128; + SendMsgPrintfln(" C128 config"); + return true; + } if(CrtChips[0].LoadAddress == 0x8000 && CrtChips[0].ROMSize == 0x2000) //Usually GAME ==1 and EXROM==0 @@ -364,13 +391,6 @@ bool SetTypeFromCRT(StructMenuItem* MyMenuItem, uint8_t EXROM, uint8_t GAME) return true; } - if(CrtChips[0].LoadAddress == 0x0000 && CrtChips[0].ROMSize == 0x2000 && EXROM==0 && GAME==0) - { - MyMenuItem->ItemType = rtBinC128; - SendMsgPrintfln(" C128 config"); - return true; - } - SendMsgPrintfln("HW config unknown"); return false; } diff --git a/Source/Teensy/TRMenuFiles/ROMs/C128_789010.crt.h b/Source/Teensy/TRMenuFiles/ROMs/C128_789010.crt.h index 922fb8b7c..da9943b46 100644 --- a/Source/Teensy/TRMenuFiles/ROMs/C128_789010.crt.h +++ b/Source/Teensy/TRMenuFiles/ROMs/C128_789010.crt.h @@ -8,7 +8,7 @@ PROGMEM static const unsigned char C128_789010_crt[] = { 0x72, 0x69, 0x63, 0x20, 0x43, 0x31, 0x32, 0x38, 0x20, 0x43, 0x61, 0x72, 0x74, 0x72, 0x69, 0x64, 0x67, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x48, 0x49, 0x50, 0x00, 0x00, 0x20, 0x10, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x4c, 0x0a, 0x80, 0x4c, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x20, 0x00, 0x4c, 0x0a, 0x80, 0x4c, 0x0a, 0x80, 0x02, 0x43, 0x42, 0x4d, 0x78, 0xa2, 0xff, 0x9a, 0xd8, 0xa9, 0x73, 0x85, 0x01, 0xa9, 0x37, 0x85, 0x00, 0xa9, 0x3a, 0x8d, 0x00, 0xff, 0xa9, 0x06, 0x8d, 0x06, 0xd5, 0xa2, 0x04, 0xbd, 0xf1, 0x98, 0x9d, 0x07, From c5666a9b36b25c60165eb9034265a458ba37bf8e Mon Sep 17 00:00:00 2001 From: Kelly Fox Date: Wed, 14 Oct 2026 20:00:30 -0500 Subject: [PATCH 2/7] firmware: fixes from the review of 4b70f37f The C128 layout rules move with StructCrtChip into a pure header, CrtChips.h, as MapC128Cart, which returns both images and their masks. The launch in both builds checks its result and refuses with a message instead of resetting into an empty cart. Both builds reset the ROM masks before every launch, since a direct remote launch skips SetUpMainMenuROM and would otherwise inherit a C128 cart's 16k masks. vm/tests/c128cart_test holds the accepted and refused layouts, run by verify:extensions. --- Source/Teensy/DriveDirLoad.ino | 18 ++++-- Source/Teensy/FileParsers.ino | 4 +- Source/Teensy/MinimalBoot/Common/CrtChips.h | 46 ++++++++++++++ .../Teensy/MinimalBoot/Common/DriveDirLoad.h | 30 +-------- .../Teensy/MinimalBoot/Min_DriveDirLoad.ino | 22 ++++--- tools/verify-extensions.mjs | 3 +- vm/tests/c128cart_test.cpp | 63 +++++++++++++++++++ 7 files changed, 140 insertions(+), 46 deletions(-) create mode 100644 Source/Teensy/MinimalBoot/Common/CrtChips.h create mode 100644 vm/tests/c128cart_test.cpp diff --git a/Source/Teensy/DriveDirLoad.ino b/Source/Teensy/DriveDirLoad.ino index 63c849617..1cfbf7247 100644 --- a/Source/Teensy/DriveDirLoad.ino +++ b/Source/Teensy/DriveDirLoad.ino @@ -303,6 +303,8 @@ FLASHMEM void HandleExecution() //has to be distilled down to one of these by this point, only ones supported so far. //Emulate ROM or prep for transfer uint8_t CartLoaded = false; + //a C128 cart widens these, and a direct remote launch skips SetUpMainMenuROM, which resets them + LOROM_Mask = HIROM_Mask = 0x1fff; switch(MenuSelCpy.ItemType) { @@ -393,14 +395,18 @@ FLASHMEM void HandleExecution() break; case rtBinC128: { - const StructCrtChip *ROML, *ROMH; - FindC128Chips(CrtChips, NumCrtChips, &ROML, &ROMH); + C128CartMap Map; + if (!MapC128Cart(CrtChips, NumCrtChips, &Map)) + { + SendMsgPrintfln("C128 chip layout unsupported"); + break; + } SetGameDeassert; SetExROMDeassert; - LOROM_Image = ROML ? ROML->ChipROM : NULL; - HIROM_Image = ROMH ? ROMH->ChipROM : NULL; - if (ROML) LOROM_Mask = ROML->ROMSize - 1; - if (ROMH) HIROM_Mask = ROMH->ROMSize - 1; + LOROM_Image = Map.ROML; + HIROM_Image = Map.ROMH; + LOROM_Mask = Map.ROMLMask; + HIROM_Mask = Map.ROMHMask; CartLoaded=true; break; } diff --git a/Source/Teensy/FileParsers.ino b/Source/Teensy/FileParsers.ino index 47b1dcb90..24f4d71db 100644 --- a/Source/Teensy/FileParsers.ino +++ b/Source/Teensy/FileParsers.ino @@ -475,8 +475,8 @@ bool SetTypeFromCRT(StructMenuItem* MyMenuItem, uint8_t EXROM, uint8_t GAME) if (CrtIsC128) { - const StructCrtChip *ROML, *ROMH; - if (!FindC128Chips(CrtChips, NumCrtChips, &ROML, &ROMH)) + C128CartMap Map; + if (!MapC128Cart(CrtChips, NumCrtChips, &Map)) { SendMsgPrintfln("C128 chip layout unsupported"); return false; diff --git a/Source/Teensy/MinimalBoot/Common/CrtChips.h b/Source/Teensy/MinimalBoot/Common/CrtChips.h new file mode 100644 index 000000000..66591334e --- /dev/null +++ b/Source/Teensy/MinimalBoot/Common/CrtChips.h @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +#pragma once +#include +#include + +// One CHIP packet of a loaded .crt. Pure, so the native tests can hold the layout rules below. +struct StructCrtChip +{ + uint8_t *ChipROM; + uint16_t LoadAddress; + uint16_t ROMSize; + uint16_t BankNum; +}; + +struct C128CartMap +{ + const uint8_t *ROML, *ROMH; + uint16_t ROMLMask, ROMHMask; +}; + +// Generic C128 cart (VICE CRT spec, C128 type 0): ROML is $8000-$BFFF and ROMH $C000-$FFFF, each +// one 8k (mirrored) or 16k chip. +static inline bool MapC128Cart(const StructCrtChip *Chips, uint8_t NumChips, C128CartMap *Map) +{ + const StructCrtChip *ROML = NULL, *ROMH = NULL; + for (uint8_t ChipNum = 0; ChipNum < NumChips; ChipNum++) + { + const StructCrtChip *Chip = &Chips[ChipNum]; + if (Chip->BankNum != 0 || (Chip->ROMSize != 0x2000 && Chip->ROMSize != 0x4000)) return false; + + const StructCrtChip **Slot; + if (Chip->LoadAddress == 0x8000) Slot = &ROML; + else if (Chip->LoadAddress == 0xC000) Slot = &ROMH; + else return false; + + if (*Slot != NULL) return false; + *Slot = Chip; + } + if (ROML == NULL && ROMH == NULL) return false; + + Map->ROML = ROML ? ROML->ChipROM : NULL; + Map->ROMH = ROMH ? ROMH->ChipROM : NULL; + Map->ROMLMask = ROML ? ROML->ROMSize - 1 : 0x1fff; + Map->ROMHMask = ROMH ? ROMH->ROMSize - 1 : 0x1fff; + return true; +} diff --git a/Source/Teensy/MinimalBoot/Common/DriveDirLoad.h b/Source/Teensy/MinimalBoot/Common/DriveDirLoad.h index e6c3813b8..99ddf0baf 100644 --- a/Source/Teensy/MinimalBoot/Common/DriveDirLoad.h +++ b/Source/Teensy/MinimalBoot/Common/DriveDirLoad.h @@ -184,35 +184,7 @@ StructHWID_IOH_Assoc HWID_IOH_Assoc[]= #define SwapSeekAddrMask 0xef000000 //virtual address mask/marker for swap-out blocks, or-ed with file seek address (minimal build only) -struct StructCrtChip -{ - uint8_t *ChipROM; - uint16_t LoadAddress; - uint16_t ROMSize; - uint16_t BankNum; -}; - -//Generic C128 cart (VICE CRT spec, C128 type 0): ROML is $8000-$BFFF and ROMH $C000-$FFFF, each -// one 8k (mirrored) or 16k chip. -inline bool FindC128Chips(const StructCrtChip *Chips, uint8_t NumChips, - const StructCrtChip **ROML, const StructCrtChip **ROMH) -{ - *ROML = *ROMH = NULL; - for (uint8_t ChipNum = 0; ChipNum < NumChips; ChipNum++) - { - const StructCrtChip *Chip = &Chips[ChipNum]; - if (Chip->BankNum != 0 || (Chip->ROMSize != 0x2000 && Chip->ROMSize != 0x4000)) return false; - - const StructCrtChip **Slot; - if (Chip->LoadAddress == 0x8000) Slot = ROML; - else if (Chip->LoadAddress == 0xC000) Slot = ROMH; - else return false; - - if (*Slot != NULL) return false; - *Slot = Chip; - } - return *ROML != NULL || *ROMH != NULL; -} +#include "CrtChips.h" #ifndef MinimumBuild //only needed in full build: diff --git a/Source/Teensy/MinimalBoot/Min_DriveDirLoad.ino b/Source/Teensy/MinimalBoot/Min_DriveDirLoad.ino index caeaec4cb..7682d24c3 100644 --- a/Source/Teensy/MinimalBoot/Min_DriveDirLoad.ino +++ b/Source/Teensy/MinimalBoot/Min_DriveDirLoad.ino @@ -34,6 +34,8 @@ void HandleExecution() //has to be distilled down to one of these by this point, only ones supported so far. //Emulate ROM or prep for transfer uint8_t CartLoaded = false; + //a C128 cart widens these, and a direct remote launch skips SetUpMainMenuROM, which resets them + LOROM_Mask = HIROM_Mask = 0x1fff; switch(MenuSelCpy.ItemType) { @@ -64,14 +66,18 @@ void HandleExecution() break; case rtBinC128: { - const StructCrtChip *ROML, *ROMH; - FindC128Chips(CrtChips, NumCrtChips, &ROML, &ROMH); + C128CartMap Map; + if (!MapC128Cart(CrtChips, NumCrtChips, &Map)) + { + SendMsgPrintfln("C128 chip layout unsupported"); + break; + } SetGameDeassert; SetExROMDeassert; - LOROM_Image = ROML ? ROML->ChipROM : NULL; - HIROM_Image = ROMH ? ROMH->ChipROM : NULL; - if (ROML) LOROM_Mask = ROML->ROMSize - 1; - if (ROMH) HIROM_Mask = ROMH->ROMSize - 1; + LOROM_Image = Map.ROML; + HIROM_Image = Map.ROMH; + LOROM_Mask = Map.ROMLMask; + HIROM_Mask = Map.ROMHMask; CartLoaded=true; break; } @@ -356,8 +362,8 @@ bool SetTypeFromCRT(StructMenuItem* MyMenuItem, uint8_t EXROM, uint8_t GAME) if (CrtIsC128) { - const StructCrtChip *ROML, *ROMH; - if (!FindC128Chips(CrtChips, NumCrtChips, &ROML, &ROMH)) + C128CartMap Map; + if (!MapC128Cart(CrtChips, NumCrtChips, &Map)) { SendMsgPrintfln("C128 chip layout unsupported"); return false; diff --git a/tools/verify-extensions.mjs b/tools/verify-extensions.mjs index 36e498fac..e745f690d 100644 --- a/tools/verify-extensions.mjs +++ b/tools/verify-extensions.mjs @@ -503,8 +503,9 @@ native('fail_test'); native('hello_module_test', [sandbox('hello-sandbox-')]); native('host_install_test', [hostPackageFixture(sandbox('host-package-'))]); native('c64spans_test'); +native('c128cart_test'); native('transfer_test'); if (keep) console.log(`Artifacts kept in ${output}`); else fs.rmSync(output, { recursive: true, force: true }); -console.log('PASS: extension loader conformance (published host contract, package format, file services, image validation, registry, settings package list, launch routing, listing invalidation, packet scheduler, failure reporting, host-package installation, reference module, C64 span rules)'); +console.log('PASS: extension loader conformance (published host contract, package format, file services, image validation, registry, settings package list, launch routing, listing invalidation, packet scheduler, failure reporting, host-package installation, reference module, C64 span rules, C128 cart layouts)'); diff --git a/vm/tests/c128cart_test.cpp b/vm/tests/c128cart_test.cpp new file mode 100644 index 000000000..dbd5b60f6 --- /dev/null +++ b/vm/tests/c128cart_test.cpp @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT +// Which chip layouts a "C128 CARTRIDGE" .crt may have, and where each chip lands. +#include +#include "../../Source/Teensy/MinimalBoot/Common/CrtChips.h" + +static uint8_t RomA[1], RomB[1]; + +static StructCrtChip chip(uint8_t *Rom, uint16_t Load, uint16_t Size, uint16_t Bank = 0) +{ + return StructCrtChip{Rom, Load, Size, Bank}; +} + +static bool map(const StructCrtChip *Chips, uint8_t Num, C128CartMap *Out) +{ + *Out = C128CartMap{RomA, RomA, 0, 0}; // a refusal must not count on a clean Map + return MapC128Cart(Chips, Num, Out); +} + +int main() { + C128CartMap Map; + + const StructCrtChip Lo8k[] = {chip(RomA, 0x8000, 0x2000)}; + assert(map(Lo8k, 1, &Map)); + assert(Map.ROML == RomA && Map.ROMLMask == 0x1fff && Map.ROMH == NULL); + + const StructCrtChip Lo16k[] = {chip(RomA, 0x8000, 0x4000)}; + assert(map(Lo16k, 1, &Map)); + assert(Map.ROML == RomA && Map.ROMLMask == 0x3fff && Map.ROMH == NULL); + + const StructCrtChip Both[] = {chip(RomB, 0xC000, 0x4000), chip(RomA, 0x8000, 0x2000)}; + assert(map(Both, 2, &Map)); + assert(Map.ROML == RomA && Map.ROMLMask == 0x1fff); + assert(Map.ROMH == RomB && Map.ROMHMask == 0x3fff); + + const StructCrtChip HiOnly[] = {chip(RomB, 0xC000, 0x2000)}; + assert(map(HiOnly, 1, &Map)); + assert(Map.ROML == NULL && Map.ROMH == RomB && Map.ROMHMask == 0x1fff); + + // The load address TeensyROM once required, and nowhere else in the spec. + const StructCrtChip Old0000[] = {chip(RomA, 0x0000, 0x2000)}; + assert(!map(Old0000, 1, &Map)); + + const StructCrtChip TwoLo[] = {chip(RomA, 0x8000, 0x2000), chip(RomB, 0x8000, 0x2000)}; + assert(!map(TwoLo, 2, &Map)); + const StructCrtChip TwoHi[] = {chip(RomA, 0xC000, 0x2000), chip(RomB, 0xC000, 0x2000)}; + assert(!map(TwoHi, 2, &Map)); + + const StructCrtChip Banked[] = {chip(RomA, 0x8000, 0x4000, 1)}; + assert(!map(Banked, 1, &Map)); + + const StructCrtChip Small[] = {chip(RomA, 0x8000, 0x1000)}; + assert(!map(Small, 1, &Map)); + const StructCrtChip Big[] = {chip(RomA, 0x8000, 0x8000)}; + assert(!map(Big, 1, &Map)); + + const StructCrtChip A000[] = {chip(RomA, 0xA000, 0x2000)}; + assert(!map(A000, 1, &Map)); + const StructCrtChip E000[] = {chip(RomA, 0xE000, 0x2000)}; + assert(!map(E000, 1, &Map)); + + assert(!map(Lo8k, 0, &Map)); + return 0; +} From c4ca8c38d3bfb710a62a8c4185e405fc5257c31b Mon Sep 17 00:00:00 2001 From: Kelly Fox Date: Wed, 14 Oct 2026 20:08:42 -0500 Subject: [PATCH 3/7] firmware: fixes from the review of c5666a9b MinimalBoot has no direct remote launch and its setup already sets the masks, so the per-launch reset there goes. A test comment claimed a check the test does not make. --- Source/Teensy/MinimalBoot/Min_DriveDirLoad.ino | 2 -- vm/tests/c128cart_test.cpp | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Source/Teensy/MinimalBoot/Min_DriveDirLoad.ino b/Source/Teensy/MinimalBoot/Min_DriveDirLoad.ino index 7682d24c3..cc039bb1f 100644 --- a/Source/Teensy/MinimalBoot/Min_DriveDirLoad.ino +++ b/Source/Teensy/MinimalBoot/Min_DriveDirLoad.ino @@ -34,8 +34,6 @@ void HandleExecution() //has to be distilled down to one of these by this point, only ones supported so far. //Emulate ROM or prep for transfer uint8_t CartLoaded = false; - //a C128 cart widens these, and a direct remote launch skips SetUpMainMenuROM, which resets them - LOROM_Mask = HIROM_Mask = 0x1fff; switch(MenuSelCpy.ItemType) { diff --git a/vm/tests/c128cart_test.cpp b/vm/tests/c128cart_test.cpp index dbd5b60f6..a264386c8 100644 --- a/vm/tests/c128cart_test.cpp +++ b/vm/tests/c128cart_test.cpp @@ -12,7 +12,7 @@ static StructCrtChip chip(uint8_t *Rom, uint16_t Load, uint16_t Size, uint16_t B static bool map(const StructCrtChip *Chips, uint8_t Num, C128CartMap *Out) { - *Out = C128CartMap{RomA, RomA, 0, 0}; // a refusal must not count on a clean Map + *Out = C128CartMap{RomA, RomA, 0, 0}; return MapC128Cart(Chips, Num, Out); } From b4e58b5f84f14fa2010ad0b9c61e411e1dcb42f2 Mon Sep 17 00:00:00 2001 From: Kelly Fox Date: Wed, 14 Oct 2026 20:54:11 -0500 Subject: [PATCH 4/7] firmware: one shared C128 crt helper for both images The C128 signature, HW-type refusal, layout check and launch mapping move into MinimalBoot/Common/C128Crt.h, so the main and MinimalBoot images call the same code instead of carrying copies. The launch applies the map SetTypeFromCRT saved rather than checking the layout a second time. --- Source/Teensy/DriveDirLoad.ino | 16 +------ Source/Teensy/FileParsers.ino | 23 ++------- Source/Teensy/MinimalBoot/Common/C128Crt.h | 48 +++++++++++++++++++ .../Teensy/MinimalBoot/Min_DriveDirLoad.ino | 39 ++------------- 4 files changed, 58 insertions(+), 68 deletions(-) create mode 100644 Source/Teensy/MinimalBoot/Common/C128Crt.h diff --git a/Source/Teensy/DriveDirLoad.ino b/Source/Teensy/DriveDirLoad.ino index 1cfbf7247..698fde015 100644 --- a/Source/Teensy/DriveDirLoad.ino +++ b/Source/Teensy/DriveDirLoad.ino @@ -18,6 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#include "MinimalBoot/Common/C128Crt.h" #ifdef VM_EXTENSIONS_ENABLED #include "MinimalBoot/Common/VMLaunch.h" #include "MinimalBoot/Common/VMPackageList.h" @@ -394,22 +395,9 @@ FLASHMEM void HandleExecution() CartLoaded=true; break; case rtBinC128: - { - C128CartMap Map; - if (!MapC128Cart(CrtChips, NumCrtChips, &Map)) - { - SendMsgPrintfln("C128 chip layout unsupported"); - break; - } - SetGameDeassert; - SetExROMDeassert; - LOROM_Image = Map.ROML; - HIROM_Image = Map.ROMH; - LOROM_Mask = Map.ROMLMask; - HIROM_Mask = Map.ROMHMask; + LaunchC128Cart(); CartLoaded=true; break; - } case rtFilePrg: //set up for transfer SendMsgPrintfln("PRG xfer %luK to $%04x:$%04x\n", diff --git a/Source/Teensy/FileParsers.ino b/Source/Teensy/FileParsers.ino index 24f4d71db..082585e77 100644 --- a/Source/Teensy/FileParsers.ino +++ b/Source/Teensy/FileParsers.ino @@ -19,7 +19,6 @@ uint8_t *ptrRAM_ImageEnd = NULL; // Made global for REU access uint8_t NumCrtChips = 0; -bool CrtIsC128 = false; StructCrtChip CrtChips[MAX_CRT_CHIPS]; char* StrSIDInfo; // allocated to RAM2 via StrSIDInfoSize char* LatestSIDLoaded; // allocated to RAM2 via MaxPathLength @@ -58,8 +57,7 @@ bool ParseCRTHeader(StructMenuItem* MyMenuItem, uint8_t *EXROM, uint8_t *GAME) SendMsgPrintfln("Parsing CRT File"); SendMsgPrintfln("CRT image size: %luK $%08x", MyMenuItem->Size/1024, MyMenuItem->Size); - CrtIsC128 = (memcmp(CRT_Image, "C128 CARTRIDGE", 14)==0); - if (CrtIsC128) SendMsgPrintfln("C128 crt"); + if (DetectC128Crt(CRT_Image)) SendMsgPrintfln("C128 crt"); else if (memcmp(CRT_Image, "C64 CARTRIDGE", 13)==0) SendMsgPrintfln("C64 crt"); else { @@ -78,11 +76,7 @@ bool ParseCRTHeader(StructMenuItem* MyMenuItem, uint8_t *EXROM, uint8_t *GAME) uint16_t HWType = toU16(CRT_Image+0x16); SendMsgPrintfln("HW Type: %d ($%04x)", (int16_t)HWType, HWType); - if (CrtIsC128 && HWType != Cart_Generic) - { //C128 crts number their HW types separately: 1 is Warpspeed128, not Action Replay - SendMsgPrintfln("Unsupported C128 HW Type"); - return false; - } + if (!C128HWTypeSupported(HWType)) return false; #ifdef Fab04_Freezers @@ -473,18 +467,7 @@ bool SetTypeFromCRT(StructMenuItem* MyMenuItem, uint8_t EXROM, uint8_t GAME) //check configuration - if (CrtIsC128) - { - C128CartMap Map; - if (!MapC128Cart(CrtChips, NumCrtChips, &Map)) - { - SendMsgPrintfln("C128 chip layout unsupported"); - return false; - } - MyMenuItem->ItemType = rtBinC128; - SendMsgPrintfln(" C128 config"); - return true; - } + if (CrtIsC128) return SetC128Type(MyMenuItem); #ifdef Fab04_Freezers if (IO1[rwRegNextIOHndlr] == IOH_SuperSnapshotV5 || diff --git a/Source/Teensy/MinimalBoot/Common/C128Crt.h b/Source/Teensy/MinimalBoot/Common/C128Crt.h new file mode 100644 index 000000000..8f821cc96 --- /dev/null +++ b/Source/Teensy/MinimalBoot/Common/C128Crt.h @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +#pragma once +#include "CrtChips.h" + +// Included after IOHandlers.h and Common_Defs.h, ahead of the .ino code that calls it: it sets the +// cart lines and ROM pointers those define. + +extern StructCrtChip CrtChips[]; +extern uint8_t NumCrtChips; + +bool CrtIsC128 = false; +static C128CartMap C128Map; + +static bool DetectC128Crt(const uint8_t *CrtHeader) +{ + CrtIsC128 = (memcmp(CrtHeader, "C128 CARTRIDGE", 14) == 0); + return CrtIsC128; +} + +static bool C128HWTypeSupported(uint16_t HWType) +{ //C128 crts number their HW types separately: 1 is Warpspeed128, not Action Replay + if (!CrtIsC128 || HWType == Cart_Generic) return true; + SendMsgPrintfln("Unsupported C128 HW Type"); + return false; +} + +// The C128 half of SetTypeFromCRT; keeps the map for LaunchC128Cart. +static bool SetC128Type(StructMenuItem *MyMenuItem) +{ + if (!MapC128Cart(CrtChips, NumCrtChips, &C128Map)) + { + SendMsgPrintfln("C128 chip layout unsupported"); + return false; + } + MyMenuItem->ItemType = rtBinC128; + SendMsgPrintfln(" C128 config"); + return true; +} + +static void LaunchC128Cart() +{ + SetGameDeassert; + SetExROMDeassert; + LOROM_Image = C128Map.ROML; + HIROM_Image = C128Map.ROMH; + LOROM_Mask = C128Map.ROMLMask; + HIROM_Mask = C128Map.ROMHMask; +} diff --git a/Source/Teensy/MinimalBoot/Min_DriveDirLoad.ino b/Source/Teensy/MinimalBoot/Min_DriveDirLoad.ino index cc039bb1f..ab6ed7128 100644 --- a/Source/Teensy/MinimalBoot/Min_DriveDirLoad.ino +++ b/Source/Teensy/MinimalBoot/Min_DriveDirLoad.ino @@ -19,8 +19,8 @@ uint8_t NumCrtChips = 0; -bool CrtIsC128 = false; StructCrtChip CrtChips[MAX_CRT_CHIPS]; +#include "Common/C128Crt.h" File myFile = NULL; void HandleExecution() @@ -63,22 +63,9 @@ void HandleExecution() CartLoaded=true; break; case rtBinC128: - { - C128CartMap Map; - if (!MapC128Cart(CrtChips, NumCrtChips, &Map)) - { - SendMsgPrintfln("C128 chip layout unsupported"); - break; - } - SetGameDeassert; - SetExROMDeassert; - LOROM_Image = Map.ROML; - HIROM_Image = Map.ROMH; - LOROM_Mask = Map.ROMLMask; - HIROM_Mask = Map.ROMHMask; + LaunchC128Cart(); CartLoaded=true; break; - } case rtUnknown: //had to have been marked unknown after check at start //SendMsgFailed(); SendMsgPrintfln(" :("); @@ -248,8 +235,7 @@ bool ParseCRTHeader(StructMenuItem* MyMenuItem, uint8_t *EXROM, uint8_t *GAME) SendMsgPrintfln("Parsing CRT File"); SendMsgPrintfln("CRT image size: %luK $%08x", MyMenuItem->Size/1024, MyMenuItem->Size); - CrtIsC128 = (memcmp(CRT_Image, "C128 CARTRIDGE", 14)==0); - if (CrtIsC128) SendMsgPrintfln("C128 crt"); + if (DetectC128Crt(CRT_Image)) SendMsgPrintfln("C128 crt"); else if (memcmp(CRT_Image, "C64 CARTRIDGE", 13)==0) SendMsgPrintfln("C64 crt"); else { @@ -268,11 +254,7 @@ bool ParseCRTHeader(StructMenuItem* MyMenuItem, uint8_t *EXROM, uint8_t *GAME) uint16_t HWType = toU16(CRT_Image+0x16); SendMsgPrintfln("HW Type: %d ($%04x)", (int16_t)HWType, HWType); - if (CrtIsC128 && HWType != Cart_Generic) - { //C128 crts number their HW types separately: 1 is Warpspeed128, not Action Replay - SendMsgPrintfln("Unsupported C128 HW Type"); - return false; - } + if (!C128HWTypeSupported(HWType)) return false; if (HWType != Cart_Generic) //leave IOH as default/user set for generic { @@ -358,18 +340,7 @@ bool SetTypeFromCRT(StructMenuItem* MyMenuItem, uint8_t EXROM, uint8_t GAME) //check configuration - if (CrtIsC128) - { - C128CartMap Map; - if (!MapC128Cart(CrtChips, NumCrtChips, &Map)) - { - SendMsgPrintfln("C128 chip layout unsupported"); - return false; - } - MyMenuItem->ItemType = rtBinC128; - SendMsgPrintfln(" C128 config"); - return true; - } + if (CrtIsC128) return SetC128Type(MyMenuItem); if(CrtChips[0].LoadAddress == 0x8000 && CrtChips[0].ROMSize == 0x2000) //Usually GAME ==1 and EXROM==0 From 9a798ca6ce22f1e242129cf5b74c30205451b3b0 Mon Sep 17 00:00:00 2001 From: Kelly Fox Date: Thu, 15 Oct 2026 17:10:22 -0500 Subject: [PATCH 5/7] firmware: keep a C128 crt's clock record at the TR's local time A C128 crt can carry a TRCLOCK record (tools/c128/README.md). At launch the TR finds it in the cart's ROM image and, while the cart runs, writes its RTC's local date and time there every 100 ms, so a GEOS 128 crt built with geos128crt.py boots with the TR's clock. --- Source/Teensy/DriveDirLoad.ino | 1 + Source/Teensy/MinimalBoot/Common/C128Crt.h | 42 +++++++++++++++++++ .../Teensy/MinimalBoot/Min_DriveDirLoad.ino | 1 + Source/Teensy/MinimalBoot/MinimalBoot.ino | 3 ++ Source/Teensy/Teensy.ino | 3 ++ 5 files changed, 50 insertions(+) diff --git a/Source/Teensy/DriveDirLoad.ino b/Source/Teensy/DriveDirLoad.ino index 698fde015..218dbe47b 100644 --- a/Source/Teensy/DriveDirLoad.ino +++ b/Source/Teensy/DriveDirLoad.ino @@ -696,6 +696,7 @@ void FreeCrtChips() for(uint16_t cnt=0; cnt < NumCrtChips; cnt++) if((uint32_t)CrtChips[cnt].ChipROM >= 0x20200000) free(CrtChips[cnt].ChipROM); NumCrtChips = 0; + C128Clock = NULL; } uint8_t Assoc_Ext_ItemType(char * FileName) diff --git a/Source/Teensy/MinimalBoot/Common/C128Crt.h b/Source/Teensy/MinimalBoot/Common/C128Crt.h index 8f821cc96..e073da6f0 100644 --- a/Source/Teensy/MinimalBoot/Common/C128Crt.h +++ b/Source/Teensy/MinimalBoot/Common/C128Crt.h @@ -1,5 +1,7 @@ // SPDX-License-Identifier: MIT #pragma once +#include +#include #include "CrtChips.h" // Included after IOHandlers.h and Common_Defs.h, ahead of the .ino code that calls it: it sets the @@ -11,6 +13,13 @@ extern uint8_t NumCrtChips; bool CrtIsC128 = false; static C128CartMap C128Map; +// tools/c128/README.md, "The boot clock": the seven bytes after the tag are century, year (0-99), +// month, day, hour (0-23), minute and second, kept at local time while the cart runs. +static const uint8_t C128ClockTag[8] = {'T', 'R', 'C', 'L', 'O', 'C', 'K', 1}; +static const uint8_t C128ClockLen = 7; +static uint8_t *C128Clock = NULL; +static int8_t C128ClockTimezone; + static bool DetectC128Crt(const uint8_t *CrtHeader) { CrtIsC128 = (memcmp(CrtHeader, "C128 CARTRIDGE", 14) == 0); @@ -37,6 +46,33 @@ static bool SetC128Type(StructMenuItem *MyMenuItem) return true; } +static uint8_t *FindC128Clock(const uint8_t *Image, uint16_t Mask) +{ + if (Image == NULL || ((uint32_t)Image & SwapSeekAddrMask) == SwapSeekAddrMask) return NULL; + for (uint32_t Offset = 0; Offset + sizeof(C128ClockTag) + C128ClockLen <= (uint32_t)Mask + 1; Offset++) + if (memcmp(Image + Offset, C128ClockTag, sizeof(C128ClockTag)) == 0) + return (uint8_t *)Image + Offset + sizeof(C128ClockTag); + return NULL; +} + +static void WriteC128Clock() +{ + tmElements_t tm; + breakTime(Teensy3Clock.get() + (int16_t)C128ClockTimezone*30*60, tm); //30 min increments + const uint16_t Year = tmYearToCalendar(tm.Year); + const uint8_t Clock[C128ClockLen] = {(uint8_t)(Year / 100), (uint8_t)(Year % 100), + tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second}; + memcpy(C128Clock, Clock, C128ClockLen); +} + +void UpdateC128Clock() +{ + static uint32_t LastWrite; + if (C128Clock == NULL || millis() - LastWrite < 100) return; + LastWrite = millis(); + WriteC128Clock(); +} + static void LaunchC128Cart() { SetGameDeassert; @@ -45,4 +81,10 @@ static void LaunchC128Cart() HIROM_Image = C128Map.ROMH; LOROM_Mask = C128Map.ROMLMask; HIROM_Mask = C128Map.ROMHMask; + + C128Clock = FindC128Clock(C128Map.ROML, C128Map.ROMLMask); + if (C128Clock == NULL) C128Clock = FindC128Clock(C128Map.ROMH, C128Map.ROMHMask); + if (C128Clock == NULL) return; + C128ClockTimezone = (int8_t)EEPROM.read(eepAdTimezone); + WriteC128Clock(); } diff --git a/Source/Teensy/MinimalBoot/Min_DriveDirLoad.ino b/Source/Teensy/MinimalBoot/Min_DriveDirLoad.ino index ab6ed7128..2e48b5725 100644 --- a/Source/Teensy/MinimalBoot/Min_DriveDirLoad.ino +++ b/Source/Teensy/MinimalBoot/Min_DriveDirLoad.ino @@ -326,6 +326,7 @@ void FreeCrtChips() for(uint16_t cnt=0; cnt < NumCrtChips; cnt++) if((uint32_t)CrtChips[cnt].ChipROM >= 0x20200000 && (uint32_t)CrtChips[cnt].ChipROM < SwapSeekAddrMask) free(CrtChips[cnt].ChipROM); NumCrtChips = 0; + C128Clock = NULL; } bool PathIsRoot() diff --git a/Source/Teensy/MinimalBoot/MinimalBoot.ino b/Source/Teensy/MinimalBoot/MinimalBoot.ino index c70e85b10..9642fad8a 100644 --- a/Source/Teensy/MinimalBoot/MinimalBoot.ino +++ b/Source/Teensy/MinimalBoot/MinimalBoot.ino @@ -44,6 +44,7 @@ StructMenuItem DriveDirMenu; char DriveDirPath[MaxPathLength]; uint16_t LOROM_Mask, HIROM_Mask; Stream *CmdChannel = &Serial; +void UpdateC128Clock(); //C128Crt.h, which needs the globals here and comes in with the .ino code #ifdef FeatTCPListen #include @@ -246,6 +247,8 @@ void loop() runMainTRApp_FromMin(); #endif } + + UpdateC128Clock(); if (doReset) { diff --git a/Source/Teensy/Teensy.ino b/Source/Teensy/Teensy.ino index 99d051452..8e464624c 100644 --- a/Source/Teensy/Teensy.ino +++ b/Source/Teensy/Teensy.ino @@ -67,6 +67,7 @@ uint8_t nfcState = nfcStateBitDisabled; //default disabled unless set in eeprom Stream *CmdChannel = &Serial; bool isFrozen = false; void (*fSpecialBtnChange)(bool Up_nDn); //Pointer to function called when Special Button Changes +void UpdateC128Clock(); //C128Crt.h, which needs the globals here and comes in with the .ino code #ifdef Fab04_SpecialButton Bounce SpecialBtnBounce = Bounce(Special_Btn_In_PIN, 35); //35mS debounce time @@ -285,6 +286,8 @@ void loop() if (IO1[rwRegPwrUpDefaults2] & rpud2NFCEnabled) nfcInit(); //connect to nfc scanner SetUpMainMenuROM(); //back to main menu, also sets doReset } + + UpdateC128Clock(); if (doReset) { From 3793585bbb86fd858e5879c70d95a67fb8edc3a5 Mon Sep 17 00:00:00 2001 From: Kelly Fox Date: Thu, 15 Oct 2026 17:14:33 -0500 Subject: [PATCH 6/7] firmware: C128 clock comment points at its contract instead of restating it --- Source/Teensy/MinimalBoot/Common/C128Crt.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/Teensy/MinimalBoot/Common/C128Crt.h b/Source/Teensy/MinimalBoot/Common/C128Crt.h index e073da6f0..8d1b8def0 100644 --- a/Source/Teensy/MinimalBoot/Common/C128Crt.h +++ b/Source/Teensy/MinimalBoot/Common/C128Crt.h @@ -13,8 +13,7 @@ extern uint8_t NumCrtChips; bool CrtIsC128 = false; static C128CartMap C128Map; -// tools/c128/README.md, "The boot clock": the seven bytes after the tag are century, year (0-99), -// month, day, hour (0-23), minute and second, kept at local time while the cart runs. +// Record layout: tools/c128/README.md, "The boot clock". static const uint8_t C128ClockTag[8] = {'T', 'R', 'C', 'L', 'O', 'C', 'K', 1}; static const uint8_t C128ClockLen = 7; static uint8_t *C128Clock = NULL; From 63c44ba545230488a1b6556833bd380ef0584dc3 Mon Sep 17 00:00:00 2001 From: Kelly Fox Date: Thu, 15 Oct 2026 17:15:10 -0500 Subject: [PATCH 7/7] firmware: C128 clock keeps its local-time offset in seconds --- Source/Teensy/MinimalBoot/Common/C128Crt.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Source/Teensy/MinimalBoot/Common/C128Crt.h b/Source/Teensy/MinimalBoot/Common/C128Crt.h index 8d1b8def0..774b90773 100644 --- a/Source/Teensy/MinimalBoot/Common/C128Crt.h +++ b/Source/Teensy/MinimalBoot/Common/C128Crt.h @@ -17,7 +17,8 @@ static C128CartMap C128Map; static const uint8_t C128ClockTag[8] = {'T', 'R', 'C', 'L', 'O', 'C', 'K', 1}; static const uint8_t C128ClockLen = 7; static uint8_t *C128Clock = NULL; -static int8_t C128ClockTimezone; +static const int32_t TimezoneStepSecs = 30 * 60; +static int32_t C128ClockLocalOffset; static bool DetectC128Crt(const uint8_t *CrtHeader) { @@ -57,7 +58,7 @@ static uint8_t *FindC128Clock(const uint8_t *Image, uint16_t Mask) static void WriteC128Clock() { tmElements_t tm; - breakTime(Teensy3Clock.get() + (int16_t)C128ClockTimezone*30*60, tm); //30 min increments + breakTime(Teensy3Clock.get() + C128ClockLocalOffset, tm); const uint16_t Year = tmYearToCalendar(tm.Year); const uint8_t Clock[C128ClockLen] = {(uint8_t)(Year / 100), (uint8_t)(Year % 100), tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second}; @@ -84,6 +85,6 @@ static void LaunchC128Cart() C128Clock = FindC128Clock(C128Map.ROML, C128Map.ROMLMask); if (C128Clock == NULL) C128Clock = FindC128Clock(C128Map.ROMH, C128Map.ROMHMask); if (C128Clock == NULL) return; - C128ClockTimezone = (int8_t)EEPROM.read(eepAdTimezone); + C128ClockLocalOffset = (int8_t)EEPROM.read(eepAdTimezone) * TimezoneStepSecs; WriteC128Clock(); }