Skip to content

Commit 98a0971

Browse files
committed
Support for EFF Cartridge type (Grizzards)
The game _Grizzards_ (2022, myself/AtariAge) uses a new board type developed by Fred “batari” Quimby which features a 2kiB “save-to-cartridge” EEPROM of the same general family as the AtariVox. This patch enables Stella to interpret the address bus accesses which in turn control the I²C interface to the EEPROM. Feel free to reach out to me @brpocock for test ROMs or details.
1 parent 2da2e05 commit 98a0971

17 files changed

+983
-482
lines changed

src/debugger/gui/CartEFFWidget.cxx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//============================================================================
2+
//
3+
// SSSS tt lll lll
4+
// SS SS tt ll ll
5+
// SS tttttt eeee ll ll aaaa
6+
// SSSS tt ee ee ll ll aa
7+
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
8+
// SS SS tt ee ll ll aa aa
9+
// SSSS ttt eeeee llll llll aaaaa
10+
//
11+
// Copyright (c) 1995-2026 by Bradford W. Mott, Stephen Anthony
12+
// and the Stella Team
13+
//
14+
// See the file "License.txt" for information on usage and redistribution of
15+
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
16+
//============================================================================
17+
18+
#include "CartEFF.hxx"
19+
#include "CartEFFWidget.hxx"
20+
21+
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
22+
CartridgeEFFWidget::CartridgeEFFWidget(
23+
GuiObject* boss, const GUI::Font& lfont, const GUI::Font& nfont,
24+
int x, int y, int w, int h, CartridgeEFF& cart)
25+
: CartridgeEnhancedWidget(boss, lfont, nfont, x, y, w, h, cart)
26+
{
27+
initialize();
28+
}
29+
30+
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
31+
string CartridgeEFFWidget::description()
32+
{
33+
ostringstream info;
34+
35+
info << "64k AtariAge EFF cartridge, 16 4k banks + 2k flash\n"
36+
<< CartridgeEnhancedWidget::description();
37+
38+
return info.str();
39+
}

src/debugger/gui/CartEFFWidget.hxx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//============================================================================
2+
//
3+
// SSSS tt lll lll
4+
// SS SS tt ll ll
5+
// SS tttttt eeee ll ll aaaa
6+
// SSSS tt ee ee ll ll aa
7+
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
8+
// SS SS tt ee ll ll aa aa
9+
// SSSS ttt eeeee llll llll aaaaa
10+
//
11+
// Copyright (c) 1995-2026 by Bradford W. Mott, Stephen Anthony
12+
// and the Stella Team
13+
//
14+
// See the file "License.txt" for information on usage and redistribution of
15+
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
16+
//============================================================================
17+
18+
#ifndef CARTRIDGEEFF_WIDGET_HXX
19+
#define CARTRIDGEEFF_WIDGET_HXX
20+
21+
class CartridgeEFF;
22+
23+
#include "CartEnhancedWidget.hxx"
24+
25+
class CartridgeEFFWidget : public CartridgeEnhancedWidget
26+
{
27+
public:
28+
CartridgeEFFWidget(GuiObject* boss, const GUI::Font& lfont,
29+
const GUI::Font& nfont,
30+
int x, int y, int w, int h,
31+
CartridgeEFF& cart);
32+
~CartridgeEFFWidget() override = default;
33+
34+
private:
35+
string manufacturer() override { return "Fred Quimby/AtariAge"; }
36+
37+
string description() override;
38+
39+
private:
40+
// Following constructors and assignment operators not supported
41+
CartridgeEFFWidget() = delete;
42+
CartridgeEFFWidget(const CartridgeEFFWidget&) = delete;
43+
CartridgeEFFWidget(CartridgeEFFWidget&&) = delete;
44+
CartridgeEFFWidget& operator=(const CartridgeEFFWidget&) = delete;
45+
CartridgeEFFWidget& operator=(CartridgeEFFWidget&&) = delete;
46+
};
47+
48+
#endif

src/debugger/gui/module.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ MODULE_OBJS := \
3838
src/debugger/gui/CartE7Widget.o \
3939
src/debugger/gui/CartEFSCWidget.o \
4040
src/debugger/gui/CartEFWidget.o \
41+
src/debugger/gui/CartEFFWidget.o \
4142
src/debugger/gui/CartF0Widget.o \
4243
src/debugger/gui/CartF4SCWidget.o \
4344
src/debugger/gui/CartF4Widget.o \

src/emucore/Bankswitch.cxx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ Bankswitch::BSList = {{
108108
{ "E0" , "E0 (8K Parker Bros)" },
109109
{ "E7" , "E7 (8-16K M Network)" },
110110
{ "EF" , "EF (64K H. Runner)" },
111+
{ "EFF" , "EFF (64K+2Kee/Grizzards)" },
111112
{ "EFSC" , "EFSC (64K H. Runner + RAM)" },
112113
{ "ELF" , "ELF (ARM bus stuffing)" },
113114
{ "F0" , "F0 (Dynacom Megaboy)" },
@@ -175,6 +176,7 @@ Bankswitch::Sizes = {{
175176
{ 8_KB, 8_KB }, // _E0
176177
{ 8_KB, 16_KB }, // _E7
177178
{ 64_KB, 64_KB }, // _EF
179+
{ 64_KB, 64_KB }, // _EFF
178180
{ 64_KB, 64_KB }, // _EFSC
179181
{ Bankswitch::any_KB, Bankswitch::any_KB }, // _ELF
180182
{ 64_KB, 64_KB }, // _F0
@@ -266,6 +268,7 @@ Bankswitch::ExtensionMap Bankswitch::ourExtensions = {
266268
{ "E78" , Bankswitch::Type::_E7 },
267269
{ "E78K" , Bankswitch::Type::_E7 },
268270
{ "EF" , Bankswitch::Type::_EF },
271+
{ "EFF" , Bankswitch::Type::_EFF },
269272
{ "EFS" , Bankswitch::Type::_EFSC },
270273
{ "EFSC" , Bankswitch::Type::_EFSC },
271274
{ "ELF" , Bankswitch::Type::_ELF },
@@ -335,6 +338,7 @@ Bankswitch::NameToTypeMap Bankswitch::ourNameToTypes = {
335338
{ "E0" , Bankswitch::Type::_E0 },
336339
{ "E7" , Bankswitch::Type::_E7 },
337340
{ "EF" , Bankswitch::Type::_EF },
341+
{ "EFF" , Bankswitch::Type::_EFF },
338342
{ "EFSC" , Bankswitch::Type::_EFSC },
339343
{ "ELF" , Bankswitch::Type::_ELF },
340344
{ "F0" , Bankswitch::Type::_F0 },

src/emucore/Bankswitch.hxx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,10 @@ class Bankswitch
4242
_32IN1, _64IN1, _128IN1, _2K, _3E, _3EX, _3EP, _3F,
4343
_4A50, _4K, _4KSC, _AR, _BF, _BFSC, _BUS, _CDF,
4444
_CM, _CTY, _CV, _DF, _DFSC, _DPC, _DPCP, _E0,
45-
_E7, _EF, _EFSC, _ELF, _F0, _F4, _F4SC, _F6,
46-
_F6SC, _F8, _F8SC, _FA, _FA2, _FC, _FE, _GL,
47-
_JANE, _MDM, _MVC, _SB, _TVBOY, _UA, _UASW, _WD,
48-
_WDSW, _WF8, _X07,
45+
_E7, _EF, _EFF, _EFSC, _ELF, _F0, _F4, _F4SC,
46+
_F6, _F6SC, _F8, _F8SC, _FA, _FA2, _FC, _FE,
47+
_GL, _JANE, _MDM, _MVC, _SB, _TVBOY,_UA, _UASW,
48+
_WD, _WDSW, _WF8, _X07,
4949
#ifdef CUSTOM_ARM
5050
_CUSTOM,
5151
#endif

src/emucore/CartCreator.cxx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
#include "CartE7.hxx"
4545
#include "CartEF.hxx"
4646
#include "CartEFSC.hxx"
47+
#include "CartEFF.hxx"
4748
#include "CartF0.hxx"
4849
#include "CartF4.hxx"
4950
#include "CartF4SC.hxx"
@@ -262,6 +263,7 @@ CartCreator::createFromImage(const ByteBuffer& image, size_t size,
262263
case _E0: return make_unique<CartridgeE0>(image, size, md5, settings);
263264
case _E7: return make_unique<CartridgeE7>(image, size, md5, settings);
264265
case _EF: return make_unique<CartridgeEF>(image, size, md5, settings);
266+
case _EFF: return make_unique<CartridgeEFF>(image, size, md5, settings);
265267
case _EFSC: return make_unique<CartridgeEFSC>(image, size, md5, settings);
266268
case _F0: return make_unique<CartridgeF0>(image, size, md5, settings);
267269
case _F4: return make_unique<CartridgeF4>(image, size, md5, settings);

src/emucore/CartDetector.cxx

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,9 @@ Bankswitch::Type CartDetector::autodetectType(const ByteBuffer& image, size_t si
178178
}
179179
else if(size == 64_KB)
180180
{
181-
if (isProbablyCDF(image, size))
181+
if(isProbablyEFF(image, size, type))
182+
; // type has been set directly in the function
183+
else if (isProbablyCDF(image, size))
182184
type = Bankswitch::Type::_CDF;
183185
else if(isProbably3EX(image, size))
184186
type = Bankswitch::Type::_3EX;
@@ -679,6 +681,43 @@ bool CartDetector::isProbablyEF(const ByteBuffer& image, size_t size,
679681
return false;
680682
}
681683

684+
685+
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
686+
bool CartDetector::isProbablyEFF(const ByteBuffer& image, size_t size,
687+
Bankswitch::Type& type)
688+
{
689+
static constexpr uInt8 effb[] = { 'E', 'F', 'F', 'B' };
690+
if(searchForBytes(image.get()+size-8, 8, effb, 4))
691+
{
692+
type = Bankswitch::Type::_EFF;
693+
return true;
694+
}
695+
696+
// Otherwise, EF cart bankswitching switches banks by accessing
697+
// addresses 0xFE0 to 0xFEF, usually with a NOP, as well as the 0xFF0
698+
// to 0xFF3 for save-to-cart and read from 0xFF4 (for the EEPROM).
699+
// Unlike EF, we require to find all three.
700+
bool isEFF = true;
701+
static constexpr uInt8 signature[4][3] = {
702+
{ 0x0C, 0xE0, 0xFF }, // NOP $FFE0
703+
{ 0x0C, 0xF0, 0x1F }, // NOP $1FF0
704+
{ 0xAD, 0xF4, 0x1F } // LDA $1FF4
705+
};
706+
for(const auto* const sig: signature)
707+
{
708+
if(!searchForBytes(image, size, sig, 3))
709+
{
710+
isEFF = false;
711+
break;
712+
}
713+
}
714+
715+
if (isEFF) {
716+
type = Bankswitch::Type::_EFF;
717+
}
718+
return isEFF;
719+
}
720+
682721
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
683722
bool CartDetector::isProbablyFA2(const ByteBuffer& image, size_t)
684723
{

src/emucore/CartDetector.hxx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,10 +182,16 @@ class CartDetector
182182
static bool isProbablyE78K(const ByteBuffer& image, size_t size);
183183

184184
/**
185-
Returns true if the image is probably an EF/EFSC bankswitching cartridge
185+
Returns true (and sets “type”) if the image is probably an EF/EFSC bankswitching cartridge
186186
*/
187187
static bool isProbablyEF(const ByteBuffer& image, size_t size, Bankswitch::Type& type);
188188

189+
/**
190+
Returns true (and sets “type”) if the image is probably an EFF (Grizzards)
191+
bankswitching+eeprom cartridge
192+
*/
193+
static bool isProbablyEFF(const ByteBuffer& image, size_t size, Bankswitch::Type& type);
194+
189195
/**
190196
Returns true if the image is probably an F6 bankswitching cartridge
191197
*/

src/emucore/CartEFF.cxx

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
//============================================================================
2+
//
3+
// SSSS tt lll lll
4+
// SS SS tt ll ll
5+
// SS tttttt eeee ll ll aaaa
6+
// SSSS tt ee ee ll ll aa
7+
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
8+
// SS SS tt ee ll ll aa aa
9+
// SSSS ttt eeeee llll llll aaaaa
10+
//
11+
// Copyright (c) 1995-2026 by Bradford W. Mott, Stephen Anthony
12+
// and the Stella Team
13+
//
14+
// See the file "License.txt" for information on usage and redistribution of
15+
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
16+
//============================================================================
17+
18+
#include "CartEFF.hxx"
19+
20+
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
21+
CartridgeEFF::CartridgeEFF(const ByteBuffer& image, size_t size,
22+
string_view md5, const Settings& settings,
23+
size_t bsSize)
24+
: CartridgeEF(image, size, md5, settings, bsSize),
25+
myEEPROM{nullptr}
26+
{
27+
myRamSize = 0;
28+
myRamBankCount = 0;
29+
myRAM = nullptr;
30+
}
31+
32+
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
33+
bool CartridgeEFF::checkSwitchBank(uInt16 address, uInt8)
34+
{
35+
address &= ROM_MASK;
36+
37+
// Switch banks if necessary
38+
if((address >= 0x0FE0) && (address <= 0x0FEF))
39+
{
40+
bank(address - 0x0FE0);
41+
return true;
42+
}
43+
return false;
44+
}
45+
46+
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
47+
int CartridgeEFF::descriptionLines()
48+
{
49+
return 24; // this can be pretty verbose
50+
}
51+
52+
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
53+
string CartridgeEFF::ramDescription()
54+
{
55+
ostringstream info;
56+
57+
info << (myRamSize / 1024) << "kiB EEPROM\n"
58+
<< "i²c serial interface @ $FFF0 - $FFF3\n"
59+
<< "i²c read port @ $FFF4 ($FFF5)\n";
60+
61+
return info.str();
62+
}
63+
64+
uInt8 CartridgeEFF::peek(uInt16 address)
65+
{
66+
const uInt16 romAddress = address & ROM_MASK;
67+
if ((address & 0x1000) && (romAddress >= 0x0FF0) && (romAddress <= 0x0FF4))
68+
{
69+
switch (romAddress) {
70+
case 0x0ff0:
71+
setI2CClock(false);
72+
break;
73+
case 0x0ff1:
74+
setI2CClock(true);
75+
break;
76+
case 0x0ff2:
77+
setI2CData(false);
78+
break;
79+
case 0x0ff3:
80+
setI2CData(true);
81+
break;
82+
case 0x0ff4:
83+
return readI2C();
84+
};
85+
}
86+
return CartridgeEF::peek(address);
87+
}
88+
89+
bool CartridgeEFF::poke(uInt16 address, uInt8 value)
90+
{
91+
const uInt16 romAddress = address & ROM_MASK;
92+
if ((address & 0x1000) && (romAddress >= 0x0FF0) && (romAddress <= 0x0FF3))
93+
{
94+
switch (romAddress) {
95+
case 0x0ff0:
96+
setI2CClock(false);
97+
setI2CClock(false);
98+
return false;
99+
100+
case 0x0ff1:
101+
setI2CClock(true);
102+
setI2CClock(true);
103+
return false;
104+
105+
case 0x0ff2:
106+
setI2CData(false);
107+
setI2CData(false);
108+
return false;
109+
110+
case 0x0ff3:
111+
setI2CData(true);
112+
setI2CData(true);
113+
return false;
114+
};
115+
}
116+
return CartridgeEF::poke(address, value);
117+
}
118+
119+
void CartridgeEFF::setI2CClock(bool value) {
120+
myI2CClock = value;
121+
if (myEEPROM)
122+
myEEPROM->writeSCL(myI2CClock);
123+
else
124+
cerr << " (No EEPROM)\n";
125+
}
126+
127+
void CartridgeEFF::setI2CData(bool value) {
128+
myI2CData = value;
129+
if (myEEPROM)
130+
myEEPROM->writeSDA(myI2CData);
131+
else
132+
cerr << " (No EEPROM)\n";
133+
}
134+
135+
uInt8 CartridgeEFF::readI2C() {
136+
const bool sda = myEEPROM->readSDA();
137+
return myImage[0x0ff4 + (sda ? 1 : 0)];
138+
}
139+
140+
void CartridgeEFF::setNVRamFile(string_view pathname)
141+
{
142+
myEEPROMFile = std::string(pathname) + std::string("_eeprom.dat");
143+
if (mySystem)
144+
myEEPROM = make_unique<MT24LC16B>(FSNode(myEEPROMFile), (const System&)mySystem, nullptr);
145+
else
146+
cerr << "ERROR asked to set up eeprom before cartridge installed in system\n";
147+
}

0 commit comments

Comments
 (0)