diff --git a/Changes.txt b/Changes.txt
index c6ff79bd5..f434f6f20 100644
--- a/Changes.txt
+++ b/Changes.txt
@@ -14,6 +14,8 @@
3.9.3 to 4.0: (xxxx xx, 2014)
+ * Added preliminary support for 'DASH' bankswitching scheme by A. Davie.
+
* Added 'savesnap' debugger prompt command, and also associated
context menu item to the debugger TIA output area. This saves the
current TIA image to a PNG file.
diff --git a/docs/index.html b/docs/index.html
index ad4f97572..9c8b95104 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -3199,6 +3199,7 @@ Ms Pac-Man (Stella extended codes):
BFSC | CPUWIZ 256K + ram |
CM ¹ | Spectravideo CompuMate |
CV | Commavid extra ram |
+ DASH | Boulder Dash 2 |
DF | CPUWIZ 128K |
DFSC | CPUWIZ 128K + ram |
DPC | Pitfall II |
diff --git a/src/emucore/Cart.cxx b/src/emucore/Cart.cxx
index 0a7b729b5..e69de393a 100644
--- a/src/emucore/Cart.cxx
+++ b/src/emucore/Cart.cxx
@@ -34,6 +34,7 @@
#include "CartCM.hxx"
#include "CartCTY.hxx"
#include "CartCV.hxx"
+#include "CartDASH.hxx"
#include "CartDPC.hxx"
#include "CartDPCPlus.hxx"
#include "CartE0.hxx"
@@ -199,6 +200,8 @@ Cartridge* Cartridge::create(const uInt8* image, uInt32 size, string& md5,
cartridge = new CartridgeCTY(image, size, osystem);
else if(type == "CV")
cartridge = new CartridgeCV(image, size, settings);
+ else if(type == "DASH")
+ cartridge = new CartridgeDASH(image, size, settings);
else if(type == "DPC")
cartridge = new CartridgeDPC(image, size, settings);
else if(type == "DPC+")
@@ -579,6 +582,7 @@ bool Cartridge::isProbablySC(const uInt8* image, uInt32 size)
return true;
}
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool Cartridge::isProbably4KSC(const uInt8* image, uInt32 size)
{
// We check if the first 256 bytes are identical *and* if there's
@@ -595,7 +599,6 @@ bool Cartridge::isProbably4KSC(const uInt8* image, uInt32 size)
return false;
}
-
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool Cartridge::isProbablyARM(const uInt8* image, uInt32 size)
{
@@ -696,6 +699,12 @@ bool Cartridge::isProbablyCV(const uInt8* image, uInt32 size)
return searchForBytes(image, size, signature[1], 3, 1);
}
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+bool Cartridge::isProbablyDASH(const uInt8* image, uInt32 size)
+{
+ return false; // TODO - add autodetection
+}
+
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool Cartridge::isProbablyDPCplus(const uInt8* image, uInt32 size)
{
diff --git a/src/emucore/Cart.hxx b/src/emucore/Cart.hxx
index c2b9ba06c..cd377f291 100644
--- a/src/emucore/Cart.hxx
+++ b/src/emucore/Cart.hxx
@@ -323,6 +323,11 @@ class Cartridge : public Device
*/
static bool isProbablyCV(const uInt8* image, uInt32 size);
+ /**
+ Returns true if the image is probably a DASH bankswitching cartridge
+ */
+ static bool isProbablyDASH(const uInt8* image, uInt32 size);
+
/**
Returns true if the image is probably a DPC+ bankswitching cartridge
*/
diff --git a/src/emucore/CartDASH.cxx b/src/emucore/CartDASH.cxx
new file mode 100644
index 000000000..5ea100ddf
--- /dev/null
+++ b/src/emucore/CartDASH.cxx
@@ -0,0 +1,366 @@
+//============================================================================
+//
+// SSSS tt lll lll
+// SS SS tt ll ll
+// SS tttttt eeee ll ll aaaa
+// SSSS tt ee ee ll ll aa
+// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
+// SS SS tt ee ll ll aa aa
+// SSSS ttt eeeee llll llll aaaaa
+//
+// Copyright (c) 1995-2014 by Bradford W. Mott, Stephen Anthony
+// and the Stella Team
+//
+// See the file "License.txt" for information on usage and redistribution of
+// this file, and for a DISCLAIMER OF ALL WARRANTIES.
+//
+// $Id$
+//============================================================================
+
+#include
+#include
+
+#include "System.hxx"
+#include "TIA.hxx"
+#include "CartDASH.hxx"
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+CartridgeDASH::CartridgeDASH(const uInt8* image, uInt32 size, const Settings& settings)
+ : Cartridge(settings),
+ mySize(size)
+{
+ // Allocate array for the ROM image
+ myImage = new uInt8[mySize];
+
+ // Copy the ROM image into my buffer
+ memcpy(myImage, image, mySize);
+ createCodeAccessBase(mySize + RAM_TOTAL_SIZE);
+
+ // This cart can address 4 banks of RAM, each 512 bytes @ 1000, 1200, 1400, 1600
+ // However, it may not be addressable all the time (it may be swapped out)
+
+ // TODO: Stephen -- is this correct for defining 4 separate RAM areas, or can it be done as one block?
+
+ registerRamArea(0x1000, RAM_BANK_SIZE, 0x00, RAM_WRITE_OFFSET); // 512 bytes RAM @ 0x1000
+ registerRamArea(0x1200, RAM_BANK_SIZE, 0x00, RAM_WRITE_OFFSET); // 512 bytes RAM @ 0x1200
+ registerRamArea(0x1400, RAM_BANK_SIZE, 0x00, RAM_WRITE_OFFSET); // 512 bytes RAM @ 0x1400
+ registerRamArea(0x1600, RAM_BANK_SIZE, 0x00, RAM_WRITE_OFFSET); // 512 bytes RAM @ 0x1600
+
+ // Remember startup bank (0 per spec, rather than last per 3E scheme).
+ // Set this to go to 3rd 1K Bank.
+ myStartBank = (3 << BANK_BITS) | 0;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+CartridgeDASH::~CartridgeDASH()
+{
+ delete[] myImage;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void CartridgeDASH::reset()
+{
+ // Initialize RAM
+ if (mySettings.getBool("ramrandom"))
+ for (uInt32 i = 0; i < RAM_TOTAL_SIZE; ++i)
+ myRAM[i] = mySystem->randGenerator().next();
+ else
+ memset(myRAM, 0, RAM_TOTAL_SIZE);
+
+ // We'll map the startup bank (0) from the image into the third 1K bank upon reset
+
+ bank(myStartBank);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void CartridgeDASH::install(System& system)
+{
+ mySystem = &system;
+
+ uInt16 shift = mySystem->pageShift();
+ uInt16 mask = mySystem->pageMask();
+
+ // Make sure the system we're being installed in has a page size that'll work
+ assert((0x1800 & mask) == 0);
+
+ System::PageAccess access(0, 0, 0, this, System::PA_READWRITE);
+
+ // Set the page accessing methods for the hot spots (for 100% emulation
+ // we need to chain any accesses below 0x40 to the TIA. Our poke() method
+ // does this via mySystem->tiaPoke(...), at least until we come up with a
+ // cleaner way to do it).
+ for (uInt32 i = 0x00; i < 0x40; i += (1 << shift))
+ mySystem->setPageAccess(i >> shift, access);
+
+ // Setup the last segment (of 4, each 1K) to point to the first ROM slice
+ // Actually we DO NOT want "always". It's just on bootup, and can be out switched later
+ access.type = System::PA_READ;
+ for (uInt32 byte = 0; byte < ROM_BANK_SIZE; byte++)
+ {
+ uInt32 address = (0x1000 - ROM_BANK_SIZE) + (byte<setPageAccess(address>>shift, access);
+
+ // TODO: Stephen: in this and other implementations we appear to be using "shift" as a system-dependant mangle for
+ // different access types (byte/int/32bit) on different architectures. I think I understand that much. However,
+ // I have an issue with how these loops are encoded in all the bank schemes I've seen. The issue being that
+ // the loop inits set some address (e.g., 0x1800) and then add a shifted value to it every loop. But when
+ // they want to get the downshifted value, they just shift down the whole value. Which will also shift down that base address
+ // and that makes no sense at all to me. I don't understand it.
+ }
+
+ // Initialise bank values for the 4x 1K bank areas
+ // This is used to reverse-lookup from address to bank location
+ for(uInt32 b = 0; b < 3; b++)
+ bankInUse[b] = BANK_UNDEFINED; // bank is undefined and inaccessible!
+
+ // Install pages for the startup bank into the first segment
+ bank(myStartBank);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+uInt8 CartridgeDASH::peek(uInt16 address)
+{
+ uInt8 value = 0;
+ uInt32 bank = (address >> 10) & 3; // convert to 1K bank index (0-3)
+ Int16 imageBank = bankInUse[bank]; // the ROM/RAM bank that's here
+
+ if(imageBank <= BANK_UNDEFINED)
+ {
+ // accessing invalid bank, so return should be... random?
+ // TODO: Stephen -- throw some sort of error; looking at undefined data
+
+ assert(false);
+ value = mySystem->randGenerator().next();
+ }
+
+ else if(imageBank < ROM_BANK_COUNT) // accessing ROM
+ {
+ Int32 offset = imageBank << ROM_BANK_TO_POWER; // base bank address in image
+ offset += (address & (ROM_BANK_SIZE-1)); // + byte offset in image bank
+ value = myImage[offset];
+ }
+ else // must be a RAM bank
+ {
+ Int32 ramBank = imageBank - ROM_BANK_COUNT;
+ assert(ramBank < RAM_BANK_COUNT); // would be bad, otherwise
+
+ Int32 offset = ramBank << RAM_BANK_TO_POWER; // base bank address in RAM
+ offset += (address & (RAM_BANK_SIZE-1)); // + byte offset in RAM bank
+ value = myRAM[offset];
+ }
+
+ return value;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+bool CartridgeDASH::poke(uInt16 address, uInt8 value)
+{
+ address &= 0x0FFF; // restrict to 4K address range
+
+ // Check for write to 3E (RAM switching) or 3F (ROM switching) and switch
+ // banks if necessary. There are NO mirrored hotspots.
+
+ switch (address)
+ {
+ case 0x3F: // a ROM switch
+ assert(value < ROM_BANK_COUNT);
+ bank(value);
+ break;
+ case 0x3E: // a RAM switch
+ assert(value < RAM_BANK_COUNT);
+ bank(ROMRAM|value);
+ break;
+ }
+
+ // @THOMAS -- well, really we don't need to use 3E and 3F -- we can just use (say) 3E
+ // and the value determines if we're doing RAM(64+bank) or ROM(bank) writes.
+ // No need to have two addresses at all, right?
+
+ // Pass the poke through to the TIA. In a real Atari, both the cart and the
+ // TIA see the address lines, and both react accordingly. In Stella, each
+ // 64-byte chunk of address space is "owned" by only one device. If we
+ // don't chain the poke to the TIA, then the TIA can't see it...
+ mySystem->tia().poke(address, value);
+
+ return false;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+bool CartridgeDASH::bank(uInt16 bank)
+{
+ if(bankLocked())
+ return false; // TODO: Stephen -- ? no idea
+
+ uInt16 shift = mySystem->pageShift();
+
+ uInt16 bankNumber = (bank >> BANK_BITS) & 3; // which bank # we are switching TO (BITS D6,D7)
+ uInt16 bankID = bank & BIT_BANK_MASK; // The actual bank # to switch in (BITS D5-D0)
+
+ if(bank & ROMRAM) // switching to a 512 byte RAM bank
+ {
+ // Wrap around/restrict to valid range
+ uInt16 currentBank = (bank & BIT_BANK_MASK) % RAM_BANK_COUNT;
+ // Record which bank switched in (marked as RAM)
+ myCurrentBank = bankInUse[bankNumber] = (Int16) (ROM_BANK_COUNT + currentBank);
+ // Effectively * 512 bytes
+ uInt32 startCurrentBank = currentBank << RAM_BANK_TO_POWER;
+
+ // Setup the page access methods for the current bank
+ System::PageAccess access(0, 0, 0, this, System::PA_READ);
+
+ // Map read-port RAM image into the system
+ for(uInt32 byte = 0; byte < RAM_BANK_SIZE; byte += (1 << shift))
+ {
+ access.directPeekBase = &myRAM[startCurrentBank + byte];
+
+ // TODO: Stephen please explain/review the use of mySize as an offset for RAM access here....
+ access.codeAccessBase = &myCodeAccessBase[mySize + startCurrentBank + byte]; //?eh
+
+ mySystem->setPageAccess((startCurrentBank + byte) >> shift, access);
+ }
+
+ access.directPeekBase = 0;
+ access.type = System::PA_WRITE;
+
+ // Map write-port RAM image into the system
+ for (uInt32 byte = 0; byte < RAM_BANK_SIZE; byte += (1 << shift))
+ {
+ access.directPokeBase = &myRAM[startCurrentBank + RAM_WRITE_OFFSET + byte];
+ access.codeAccessBase = &myCodeAccessBase[mySize + startCurrentBank + RAM_WRITE_OFFSET + byte];
+ mySystem->setPageAccess((startCurrentBank + byte) >> shift, access);
+ }
+ }
+ else // ROM 1K banks
+ {
+ // Map ROM bank image into the system into the correct slot
+ // Memory map is 1K slots at 0x1000, 0x1400, 0x1800, 0x1C00
+
+ // Record which bank switched in (as ROM)
+ myCurrentBank = bankInUse[bankNumber] = (Int16) bankID;
+ // Effectively *1K
+ uInt32 startCurrentBank = bankID << ROM_BANK_TO_POWER;
+
+ // Setup the page access methods for the current bank
+ System::PageAccess access(0, 0, 0, this, System::PA_READ);
+
+ uInt32 bankStart = 0x1000 + (bankNumber << ROM_BANK_TO_POWER); // *1K
+
+ for (uInt32 byte = 0; byte < ROM_BANK_SIZE; byte += (1 << shift))
+ {
+ access.directPeekBase = &myImage[startCurrentBank + byte];
+ access.codeAccessBase = &myCodeAccessBase[startCurrentBank + byte];
+ mySystem->setPageAccess((bankStart + byte) >> shift, access);
+ }
+ }
+
+ return myBankChanged = true;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+uInt16 CartridgeDASH::bank() const
+{
+ // TODO: Stephen -- what to do here? We don't really HAVE a "current bank"; we have 4 banks
+ // and they are defined in bankInUse[...].
+ // What I've done is kept track of the last switched bank, and return that. But that doesn't tell us WHERE. :(
+
+ return myCurrentBank;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+uInt16 CartridgeDASH::bankCount() const
+{
+ // Because the RAM banks always start above the ROM banks (see ROM_BANK_COUNT) for value,
+ // we require the number of ROM banks to be == ROM_BANK_COUNT. Banks are therefore 0-63 ROM 64-127 RAM
+ // TODO: Stephen -- ROM banks are 1K. RAM banks are 512 bytes. How does this affect what this routine should return?
+ return ROM_BANK_COUNT + RAM_BANK_COUNT;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+bool CartridgeDASH::patch(uInt16 address, uInt8 value)
+{
+ // Patch the cartridge ROM
+ // TODO: Stephen... I assume this is for some sort of debugger support....?
+
+ myBankChanged = true;
+
+ uInt32 bankNumber = (address >> 10) & 3; // now 1K bank # (ie: 0-3)
+ Int32 whichBankIsThere = bankInUse[bankNumber]; // ROM or RAM bank reference
+
+ if(whichBankIsThere <= BANK_UNDEFINED)
+ {
+ // We're trying to access undefined memory (no bank here yet)
+
+ // TODO: Stephen -- what to do here? Fail silently?
+ // want to throw some sort of Stella error -- trying to patch an unswitched in bank!
+
+ assert(false);
+ myBankChanged = false;
+ }
+ else if(whichBankIsThere < ROM_BANK_COUNT) // patching ROM (1K banks)
+ {
+ uInt32 byteOffset = address & (ROM_BANK_SIZE-1);
+ uInt32 baseAddress = (whichBankIsThere << ROM_BANK_TO_POWER) + byteOffset;
+ myImage[baseAddress] = value; // write to the image
+ }
+ else // patching RAM (512 byte banks)
+ {
+ uInt32 byteOffset = address & (RAM_BANK_SIZE-1);
+ uInt32 baseAddress = ((whichBankIsThere - ROM_BANK_COUNT) << RAM_BANK_TO_POWER) + byteOffset;
+ myRAM[baseAddress] = value; // write to RAM
+ }
+
+ return myBankChanged;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+const uInt8* CartridgeDASH::getImage(int& size) const
+{
+ size = mySize;
+ return myImage;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+bool CartridgeDASH::save(Serializer& out) const
+{
+ try
+ {
+ out.putString(name());
+ out.putShort(myCurrentBank);
+ for(uInt32 bank = 0; bank < 4; bank++)
+ out.putShort(bankInUse[bank]);
+ out.putByteArray(myRAM, RAM_TOTAL_SIZE);
+ }
+ catch (...)
+ {
+ cerr << "ERROR: CartridgeDASH::save" << endl;
+ return false;
+ }
+
+ return true;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+bool CartridgeDASH::load(Serializer& in)
+{
+ try
+ {
+ if(in.getString() != name())
+ return false;
+ myCurrentBank = in.getShort();
+ for(uInt32 bank = 0; bank < 4; bank++)
+ bankInUse[bank] = in.getShort();
+ in.getByteArray(myRAM, RAM_TOTAL_SIZE);
+ }
+ catch (...)
+ {
+ cerr << "ERROR: CartridgeDASH::load" << endl;
+ return false;
+ }
+
+ // Now, go to the current bank
+ bank(myCurrentBank);
+
+ return true;
+}
diff --git a/src/emucore/CartDASH.hxx b/src/emucore/CartDASH.hxx
new file mode 100644
index 000000000..59d40138a
--- /dev/null
+++ b/src/emucore/CartDASH.hxx
@@ -0,0 +1,306 @@
+//============================================================================
+//
+// SSSS tt lll lll
+// SS SS tt ll ll
+// SS tttttt eeee ll ll aaaa
+// SSSS tt ee ee ll ll aa
+// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
+// SS SS tt ee ll ll aa aa
+// SSSS ttt eeeee llll llll aaaaa
+//
+// Copyright (c) 1995-2014 by Bradford W. Mott, Stephen Anthony
+// and the Stella Team
+//
+// See the file "License.txt" for information on usage and redistribution of
+// this file, and for a DISCLAIMER OF ALL WARRANTIES.
+//
+// $Id$
+//============================================================================
+
+#ifndef CARTRIDGEDASH_HXX
+#define CARTRIDGEDASH_HXX
+
+class System;
+
+#include "bspf.hxx"
+#include "Cart.hxx"
+
+#ifdef DEBUGGER_SUPPORT
+ class CartridgeDASHWidget;
+// #include "CartDASHWidget.hxx"
+#endif
+
+/**
+ Cartridge class for new tiling engine "Boulder Dash" format games with RAM.
+ Kind of a combination of 3F and 3E, with better switchability.
+ This code is B.Watson's Cart3E modified to new specs by Andrew Davie.
+
+ Note: because a single bank number is used to define both the destination (0-3)
+ AND the type (ROM/RAM) there are only 5 bits left to indicate the actual bank
+ number. This sets the limits of 32K ROM and 16K RAM.
+
+ D7 is that RAM/ROM flag (1=RAM)
+ D6D5 indciates the bank number (0-3)
+ D4D0 indicate the actual # (0-31) from the image/ram
+
+ ROM:
+
+ In this scheme, the 4K address space is broken into four 1K ROM segments.
+ living at 0x1000, 0x1400, 0x1800, 0x1C00 (or, same thing, 0xF000... etc.),
+ and four 512 byte RAM segments, living at 0x1000, 0x1200, 0x1400, 0x1600
+ with write-mirrors +0x800 of these. The last 1K ROM ($FC00-$FFFF) segment
+ is initialised to point to the FIRST 1K of the ROM image, but it may be
+ switched out at any time. Note, this is DIFFERENT to 3E which switches in
+ the UPPER bank and this bank is fixed. This allows variable sized ROM
+ without having to detect size. First bank (0) in ROM is the default fixed
+ bank mapped to $FC00.
+
+ The system requires the reset vectors to be valid on a reset, so either the
+ hardware first switches in the first bank, or the programmer must ensure
+ that the reset vector is present in ALL ROM banks which might be switched
+ into the last bank area. Currently the latter (programmer onus) is required,
+ but it would be nice for the cartridge hardware to auto-switch on reset.
+
+ For both ROM (write to 0x3F) and RAM (write to 0x3E) bank switching, the
+ top two bits indicate the physical address segment which is being switched,
+ and the low 6 bits indicate the bank number, as per the following ...
+
+ ROM switching (write of block+bank number to $3F) upper 2 bits of bank #
+ indicates the destination segment (0-3, corresponding to $F000, $F400,
+ $F800, $FC00), and lower 6 bits indicate the 1K bank to switch in. Can
+ handle 64 x 1K ROM banks (64K total).
+
+ BITS ACTION
+ D7D6 0xxxxx
+ 0 0 -- switch a 1K ROM bank 0xxxxx to $F000
+ 0 1 -- switch a 1K ROM bank 0xxxxx to $F400
+ 1 0 -- switch 1K ROM bank 0xxxxx to $F800
+ 1 1 -- switch 1K ROM bank 0xxxxx to $FC00
+
+ can handle 32K ROM maximum
+
+ RAM switching (write of segment+bank number to $3E) upper 2 bits of bank #
+ indicates the destination RAM segment (0-3, corresponding to $F000, $F200,
+ $F400, $F600). Note that this allows contiguous 2K of RAM to be configured
+ by setting 4 consecutive RAM segments with consecutive addresses. However,
+ as the write address of RAM is +0x800, this invalidates ROM access as
+ described below.
+
+ write access uses +$800
+ can handle 32 x 512 byte RAM banks (16K total)
+
+ BITS ACTION
+ D7D6 1xxxxx
+ 0 0 -- switch a 512 byte RAM bank xxxxx to $F000 with write @ $F800
+ 0 1 -- switch a 512 byte RAM bank xxxxx to $F200 with write @ $FA00
+ 1 0 -- switch a 512 byte RAM bank xxxxx to $F400 with write @ $FC00
+ 1 1 -- switch a 512 byte RAM bank xxxxx to $F600 with write @ $FE00
+
+ It is possible to switch multiple RAM banks and ROM banks together
+
+ For example,
+ F000-F1FF RAM bank A (512 byte READ)
+ F200-F3FF high 512 bytes of ROM bank previously loaded at F000
+ F400 ROM bank 0 (1K)
+ F800 RAM bank A (512 byte WRITE)
+ FA00-FBFF high 512 bytes of ROM bank previously loaded at F400
+ FC00 ROM bank 1
+
+ This example shows 512 bytes of RAM, and 2 1K ROM banks and two 512 byte ROM
+ bank halves.
+
+ Switching RAM blocks (D7D6 of $3E) partially invalidates ROM blocks, as below...
+
+ RAM block Invalidates ROM block
+ 0 0 (lower half), 2 (lower half)
+ 1 0 (upper half), 2 (upper half)
+ 2 1 (lower half), 3 (upper half)
+ 3 1 (upper half), 3 (lower half)
+
+ For example, RAM block 1 uses address $F200-$F3FF and $FA00-$FBFF
+ ROM block 0 uses address $F000-$F3FF, and ROM block 2 uses address $F800-$FBFF
+ Switching in RAM block 1 makes F200-F3FF ROM inaccessible, however F000-F1FF is
+ still readable. So, care must be paid.
+
+ This crazy RAM layout is useful as it allows contiguous RAM to be switched in,
+ up to 2K in one sequentially accessible block. This means you CAN have 2K of
+ consecutive RAM. If you don't detect ROM write area, then you would have NO ROM
+ switched in (don't forget to copy your reset vectors!)
+
+ NOTE:
+ We could consider the 4A50 method where the cart detects read/writes and we
+ use the same address for reading/writing. magic writes. If we could avoid
+ the memory doubling for RAM, that would make it easier to code -- use the
+ same variables for read/write This would then mean that ROM banks 2,3 would
+ never be bothered by RAM. I like this.
+
+ ----------------------------------------------------------------------------------------------
+
+ This implementation of DASH bankswitching numbers the ROM banks 0 to 63, and
+ the RAM banks 64 to 127. This is done because the public bankswitching
+ interface requires us to use one bank number, not one bank number plus the
+ knowledge of whether it's RAM or ROM.
+
+ All 32K of potential RAM is available to a game using this class, even though
+ real cartridges might not have the full 32K: We have no way to tell how much
+ RAM the game expects. This may change in the future (we may add a stella.pro
+ property for this), but for now it shouldn't cause any problems.
+ (Famous last words...)
+
+ @author A. Davie
+*/
+
+class CartridgeDASH: public Cartridge
+{
+ friend class CartridgeDASHWidget;
+
+ public:
+ /**
+ Create a new cartridge using the specified image and size
+
+ @param image Pointer to the ROM image
+ @param size The size of the ROM image
+ @param settings A reference to the various settings (read-only)
+ */
+ CartridgeDASH(const uInt8* image, uInt32 size, const Settings& settings);
+
+ /**
+ Destructor
+ */
+ virtual ~CartridgeDASH();
+
+ public:
+ /**
+ Reset device to its power-on state
+ */
+ void reset();
+
+ /**
+ Install cartridge in the specified system. Invoked by the system
+ when the cartridge is attached to it.
+
+ @param system The system the device should install itself in
+ */
+ void install(System& system);
+
+ /**
+ Install pages for the specified bank in the system.
+
+ @param bank The bank that should be installed in the system
+ */
+ bool bank(uInt16 bank);
+
+ /**
+ Get the current bank.
+ */
+ uInt16 bank() const;
+
+ /**
+ Query the number of banks supported by the cartridge.
+ */
+ uInt16 bankCount() const;
+
+ /**
+ Patch the cartridge ROM.
+
+ @param address The ROM address to patch
+ @param value The value to place into the address
+ @return Success or failure of the patch operation
+ */
+ bool patch(uInt16 address, uInt8 value);
+
+ /**
+ Access the internal ROM image for this cartridge.
+
+ @param size Set to the size of the internal ROM image data
+ @return A pointer to the internal ROM image data
+ */
+ const uInt8* getImage(int& size) const;
+
+ /**
+ Save the current state of this cart to the given Serializer.
+
+ @param out The Serializer object to use
+ @return False on any errors, else true
+ */
+ bool save(Serializer& out) const;
+
+ /**
+ Load the current state of this cart from the given Serializer.
+
+ @param in The Serializer object to use
+ @return False on any errors, else true
+ */
+ bool load(Serializer& in);
+
+ /**
+ Get a descriptor for the device name (used in error checking).
+
+ @return The name of the object
+ */
+ string name() const { return "CartridgeDASH"; }
+
+#ifdef DEBUGGER_SUPPORT
+ /**
+ Get debugger widget responsible for accessing the inner workings
+ of the cart.
+ */
+ CartDebugWidget* debugWidget(GuiObject* boss, const GUI::Font& lfont,
+ const GUI::Font& nfont, int x, int y, int w, int h)
+ {
+ return 0;//new CartridgeDASHWidget(boss, lfont, nfont, x, y, w, h, *this);
+ }
+#endif
+
+ public:
+ /**
+ Get the byte at the specified address
+
+ @return The byte at the specified address
+ */
+ uInt8 peek(uInt16 address);
+
+ /**
+ Change the byte at the specified address to the given value
+
+ @param address The address where the value should be stored
+ @param value The value to be stored at the address
+ @return True if the poke changed the device address space, else false
+ */
+ bool poke(uInt16 address, uInt8 value);
+
+ private:
+ uInt16 myCurrentBank; // whatever the LAST switched bank was...
+
+ uInt32 mySize; // Size of the ROM image
+ uInt8* myImage; // Pointer to a dynamically allocated ROM image of the cartridge
+
+ Int16 bankInUse[4]; // bank being used for ROM/RAM (-1 = undefined)
+
+ // RAM contents. RAM banks are 512 bytes, and there are a maximum of 64 of them
+
+ enum {
+ BANK_BITS = 5, // # bits for bank
+ BIT_BANK_MASK = (1 << BANK_BITS) - 1, // mask for those bits
+ ROMRAM = 0x80, // flags ROM or RAM bank switching (1==RAM)
+
+ RAM_BANK_COUNT = 32,
+ RAM_BANK_TO_POWER = 9, // 2^n = 512
+ RAM_BANK_SIZE = (1 << RAM_BANK_TO_POWER),
+ RAM_TOTAL_SIZE = RAM_BANK_COUNT * RAM_BANK_SIZE,
+
+ ROM_BANK_TO_POWER = 10, // 2^n = 1024
+ ROM_BANK_SIZE = (1 << ROM_BANK_TO_POWER),
+
+ ROM_BANK_COUNT = 32,
+ ROM_BANK_MASK = (ROM_BANK_COUNT - 1),
+
+ RAM_WRITE_OFFSET = 0x800,
+
+ BANK_UNDEFINED = -1 // bank is undefined and inaccessible
+ };
+
+ uInt8 myRAM[RAM_TOTAL_SIZE];
+};
+
+#endif
diff --git a/src/emucore/module.mk b/src/emucore/module.mk
index 2422de2b0..71ce40efa 100644
--- a/src/emucore/module.mk
+++ b/src/emucore/module.mk
@@ -15,6 +15,7 @@ MODULE_OBJS := \
src/emucore/CartCM.o \
src/emucore/CartCTY.o \
src/emucore/CartCV.o \
+ src/emucore/CartDASH.o \
src/emucore/CartDPC.o \
src/emucore/CartDPCPlus.o \
src/emucore/CartE0.o \
diff --git a/src/gui/GameInfoDialog.cxx b/src/gui/GameInfoDialog.cxx
index 74b78371c..f6a31b6b0 100644
--- a/src/gui/GameInfoDialog.cxx
+++ b/src/gui/GameInfoDialog.cxx
@@ -152,6 +152,7 @@ GameInfoDialog::GameInfoDialog(
items.push_back("BFSC (CPUWIZ 256K + ram)", "BFSC" );
items.push_back("CV (Commavid extra ram)", "CV" );
items.push_back("CM (SpectraVideo CompuMate)", "CM" );
+ items.push_back("DASH (Boulder Dash 2)", "DASH" );
items.push_back("DF (CPUWIZ 128K)", "DF" );
items.push_back("DFSC (CPUWIZ 128K + ram)", "DFSC" );
items.push_back("DPC (Pitfall II)", "DPC" );
diff --git a/src/gui/GlobalPropsDialog.cxx b/src/gui/GlobalPropsDialog.cxx
index 26b17e7af..6702332e1 100644
--- a/src/gui/GlobalPropsDialog.cxx
+++ b/src/gui/GlobalPropsDialog.cxx
@@ -83,6 +83,7 @@ GlobalPropsDialog::GlobalPropsDialog(GuiObject* boss, const GUI::Font& font)
items.push_back("BFSC (CPUWIZ 256K + ram)", "BFSC" );
items.push_back("CV (Commavid extra ram)", "CV" );
items.push_back("CM (SpectraVideo CompuMate)", "CM" );
+ items.push_back("DASH (Boulder Dash 2)", "DASH" );
items.push_back("DF (CPUWIZ 128K)", "DF" );
items.push_back("DFSC (CPUWIZ 128K + ram)", "DFSC" );
items.push_back("DPC (Pitfall II)", "DPC" );