From e81f2b0ffd4af1329fdfa57fe3514cb5f7afaa79 Mon Sep 17 00:00:00 2001 From: stephena Date: Mon, 2 Jun 2014 20:41:19 +0000 Subject: [PATCH] Added preliminary support for DASH bankswitching scheme by A. Davie. This hasn't been tested yet, since no ROMs currently exist. git-svn-id: svn://svn.code.sf.net/p/stella/code/trunk@2895 8b62c5a3-ac7e-4cc8-8f21-d9a121418aba --- Changes.txt | 2 + docs/index.html | 1 + src/emucore/Cart.cxx | 11 +- src/emucore/Cart.hxx | 5 + src/emucore/CartDASH.cxx | 366 ++++++++++++++++++++++++++++++++++ src/emucore/CartDASH.hxx | 306 ++++++++++++++++++++++++++++ src/emucore/module.mk | 1 + src/gui/GameInfoDialog.cxx | 1 + src/gui/GlobalPropsDialog.cxx | 1 + 9 files changed, 693 insertions(+), 1 deletion(-) create mode 100644 src/emucore/CartDASH.cxx create mode 100644 src/emucore/CartDASH.hxx 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" );