stella/src/emucore/Cart4A50.cxx

430 lines
14 KiB
C++

//============================================================================
//
// 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-2018 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.
//============================================================================
#include "System.hxx"
#include "M6532.hxx"
#include "TIA.hxx"
#include "Cart4A50.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Cartridge4A50::Cartridge4A50(const BytePtr& image, uInt32 size,
const Settings& settings)
: Cartridge(settings),
mySize(size),
mySliceLow(0),
mySliceMiddle(0),
mySliceHigh(0),
myIsRomLow(true),
myIsRomMiddle(true),
myIsRomHigh(true),
myLastAddress(0),
myLastData(0)
{
// Copy the ROM image into my buffer
// Supported file sizes are 32/64/128K, which are duplicated if necessary
if(size < 65536) size = 32768;
else if(size < 131072) size = 65536;
else size = 131072;
for(uInt32 slice = 0; slice < 131072 / size; ++slice)
memcpy(myImage + (slice*size), image.get(), size);
// We use System::PageAccess.codeAccessBase, but don't allow its use
// through a pointer, since the address space of 4A50 carts can change
// at the instruction level, and PageAccess is normally defined at an
// interval of 64 bytes
//
// Instead, access will be through the getAccessFlags and setAccessFlags
// methods below
createCodeAccessBase(131072 + 32768);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Cartridge4A50::reset()
{
initializeRAM(myRAM, 32768);
mySliceLow = mySliceMiddle = mySliceHigh = 0;
myIsRomLow = myIsRomMiddle = myIsRomHigh = true;
myLastData = 0xff;
myLastAddress = 0xffff;
myBankChanged = true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Cartridge4A50::install(System& system)
{
mySystem = &system;
// Map all of the accesses to call peek and poke (We don't yet indicate RAM areas)
System::PageAccess access(this, System::PA_READ);
for(uInt16 addr = 0x1000; addr < 0x2000; addr += System::PAGE_SIZE)
mySystem->setPageAccess(addr, access);
// Mirror all access in TIA and RIOT; by doing so we're taking responsibility
// for that address space in peek and poke below.
mySystem->tia().installDelegate(system, *this);
mySystem->m6532().installDelegate(system, *this);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt8 Cartridge4A50::peek(uInt16 address)
{
uInt8 value = 0;
if(!(address & 0x1000)) // Hotspots below 0x1000
{
// Check for RAM or TIA mirroring
uInt16 lowAddress = address & 0x3ff;
if(lowAddress & 0x80)
value = mySystem->m6532().peek(address);
else if(!(lowAddress & 0x200))
value = mySystem->tia().peek(address);
checkBankSwitch(address, value);
}
else
{
if((address & 0x1800) == 0x1000) // 2K region from 0x1000 - 0x17ff
{
value = myIsRomLow ? myImage[(address & 0x7ff) + mySliceLow]
: myRAM[(address & 0x7ff) + mySliceLow];
}
else if(((address & 0x1fff) >= 0x1800) && // 1.5K region from 0x1800 - 0x1dff
((address & 0x1fff) <= 0x1dff))
{
value = myIsRomMiddle ? myImage[(address & 0x7ff) + mySliceMiddle + 0x10000]
: myRAM[(address & 0x7ff) + mySliceMiddle];
}
else if((address & 0x1f00) == 0x1e00) // 256B region from 0x1e00 - 0x1eff
{
value = myIsRomHigh ? myImage[(address & 0xff) + mySliceHigh + 0x10000]
: myRAM[(address & 0xff) + mySliceHigh];
}
else if((address & 0x1f00) == 0x1f00) // 256B region from 0x1f00 - 0x1fff
{
value = myImage[(address & 0xff) + 0x1ff00];
if(!bankLocked() && ((myLastData & 0xe0) == 0x60) &&
((myLastAddress >= 0x1000) || (myLastAddress < 0x200)))
mySliceHigh = (mySliceHigh & 0xf0ff) | ((address & 0x8) << 8) |
((address & 0x70) << 4);
}
}
myLastData = value;
myLastAddress = address & 0x1fff;
return value;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool Cartridge4A50::poke(uInt16 address, uInt8 value)
{
if(!(address & 0x1000)) // Hotspots below 0x1000
{
// Check for RAM or TIA mirroring
uInt16 lowAddress = address & 0x3ff;
if(lowAddress & 0x80)
mySystem->m6532().poke(address, value);
else if(!(lowAddress & 0x200))
mySystem->tia().poke(address, value);
checkBankSwitch(address, value);
}
else
{
if((address & 0x1800) == 0x1000) // 2K region at 0x1000 - 0x17ff
{
if(!myIsRomLow)
{
myRAM[(address & 0x7ff) + mySliceLow] = value;
myBankChanged = true;
}
}
else if(((address & 0x1fff) >= 0x1800) && // 1.5K region at 0x1800 - 0x1dff
((address & 0x1fff) <= 0x1dff))
{
if(!myIsRomMiddle)
{
myRAM[(address & 0x7ff) + mySliceMiddle] = value;
myBankChanged = true;
}
}
else if((address & 0x1f00) == 0x1e00) // 256B region at 0x1e00 - 0x1eff
{
if(!myIsRomHigh)
{
myRAM[(address & 0xff) + mySliceHigh] = value;
myBankChanged = true;
}
}
else if((address & 0x1f00) == 0x1f00) // 256B region at 0x1f00 - 0x1fff
{
if(!bankLocked() && ((myLastData & 0xe0) == 0x60) &&
((myLastAddress >= 0x1000) || (myLastAddress < 0x200)))
{
mySliceHigh = (mySliceHigh & 0xf0ff) | ((address & 0x8) << 8) |
((address & 0x70) << 4);
myBankChanged = true;
}
}
}
myLastData = value;
myLastAddress = address & 0x1fff;
return myBankChanged;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt8 Cartridge4A50::getAccessFlags(uInt16 address) const
{
if((address & 0x1800) == 0x1000) // 2K region from 0x1000 - 0x17ff
{
if(myIsRomLow)
return myCodeAccessBase[(address & 0x7ff) + mySliceLow];
else
return myCodeAccessBase[131072 + (address & 0x7ff) + mySliceLow];
}
else if(((address & 0x1fff) >= 0x1800) && // 1.5K region from 0x1800 - 0x1dff
((address & 0x1fff) <= 0x1dff))
{
if(myIsRomMiddle)
return myCodeAccessBase[(address & 0x7ff) + mySliceMiddle + 0x10000];
else
return myCodeAccessBase[131072 + (address & 0x7ff) + mySliceMiddle];
}
else if((address & 0x1f00) == 0x1e00) // 256B region from 0x1e00 - 0x1eff
{
if(myIsRomHigh)
return myCodeAccessBase[(address & 0xff) + mySliceHigh + 0x10000];
else
return myCodeAccessBase[131072 + (address & 0xff) + mySliceHigh];
}
else if((address & 0x1f00) == 0x1f00) // 256B region from 0x1f00 - 0x1fff
{
return myCodeAccessBase[(address & 0xff) + 0x1ff00];
}
return 0;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Cartridge4A50::setAccessFlags(uInt16 address, uInt8 flags)
{
if((address & 0x1800) == 0x1000) // 2K region from 0x1000 - 0x17ff
{
if(myIsRomLow)
myCodeAccessBase[(address & 0x7ff) + mySliceLow] |= flags;
else
myCodeAccessBase[131072 + (address & 0x7ff) + mySliceLow] |= flags;
}
else if(((address & 0x1fff) >= 0x1800) && // 1.5K region from 0x1800 - 0x1dff
((address & 0x1fff) <= 0x1dff))
{
if(myIsRomMiddle)
myCodeAccessBase[(address & 0x7ff) + mySliceMiddle + 0x10000] |= flags;
else
myCodeAccessBase[131072 + (address & 0x7ff) + mySliceMiddle] |= flags;
}
else if((address & 0x1f00) == 0x1e00) // 256B region from 0x1e00 - 0x1eff
{
if(myIsRomHigh)
myCodeAccessBase[(address & 0xff) + mySliceHigh + 0x10000] |= flags;
else
myCodeAccessBase[131072 + (address & 0xff) + mySliceHigh] |= flags;
}
else if((address & 0x1f00) == 0x1f00) // 256B region from 0x1f00 - 0x1fff
{
myCodeAccessBase[(address & 0xff) + 0x1ff00] |= flags;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Cartridge4A50::checkBankSwitch(uInt16 address, uInt8 value)
{
if(bankLocked()) return;
// This scheme contains so many hotspots that it's easier to just check
// all of them
if(((myLastData & 0xe0) == 0x60) && // Switch lower/middle/upper bank
((myLastAddress >= 0x1000) || (myLastAddress < 0x200)))
{
if((address & 0x0f00) == 0x0c00) // Enable 256B of ROM at 0x1e00 - 0x1eff
bankROMHigh(address & 0xff);
else if((address & 0x0f00) == 0x0d00) // Enable 256B of RAM at 0x1e00 - 0x1eff
bankRAMHigh(address & 0x7f);
else if((address & 0x0f40) == 0x0e00) // Enable 2K of ROM at 0x1000 - 0x17ff
bankROMLower(address & 0x1f);
else if((address & 0x0f40) == 0x0e40) // Enable 2K of RAM at 0x1000 - 0x17ff
bankRAMLower(address & 0xf);
else if((address & 0x0f40) == 0x0f00) // Enable 1.5K of ROM at 0x1800 - 0x1dff
bankROMMiddle(address & 0x1f);
else if((address & 0x0f50) == 0x0f40) // Enable 1.5K of RAM at 0x1800 - 0x1dff
bankRAMMiddle(address & 0xf);
// Stella helper functions
else if((address & 0x0f00) == 0x0400) // Toggle bit A11 of lower block address
{
mySliceLow = mySliceLow ^ 0x800;
myBankChanged = true;
}
else if((address & 0x0f00) == 0x0500) // Toggle bit A12 of lower block address
{
mySliceLow = mySliceLow ^ 0x1000;
myBankChanged = true;
}
else if((address & 0x0f00) == 0x0800) // Toggle bit A11 of middle block address
{
mySliceMiddle = mySliceMiddle ^ 0x800;
myBankChanged = true;
}
else if((address & 0x0f00) == 0x0900) // Toggle bit A12 of middle block address
{
mySliceMiddle = mySliceMiddle ^ 0x1000;
myBankChanged = true;
}
}
// Zero-page hotspots for upper page
// 0xf4, 0xf6, 0xfc, 0xfe for ROM
// 0xf5, 0xf7, 0xfd, 0xff for RAM
// 0x74 - 0x7f (0x80 bytes lower)
if((address & 0xf75) == 0x74) // Enable 256B of ROM at 0x1e00 - 0x1eff
bankROMHigh(value);
else if((address & 0xf75) == 0x75) // Enable 256B of RAM at 0x1e00 - 0x1eff
bankRAMHigh(value & 0x7f);
// Zero-page hotspots for lower and middle blocks
// 0xf8, 0xf9, 0xfa, 0xfb
// 0x78, 0x79, 0x7a, 0x7b (0x80 bytes lower)
else if((address & 0xf7c) == 0x78)
{
if((value & 0xf0) == 0) // Enable 2K of ROM at 0x1000 - 0x17ff
bankROMLower(value & 0xf);
else if((value & 0xf0) == 0x40) // Enable 2K of RAM at 0x1000 - 0x17ff
bankRAMLower(value & 0xf);
else if((value & 0xf0) == 0x90) // Enable 1.5K of ROM at 0x1800 - 0x1dff
bankROMMiddle((value & 0xf) | 0x10);
else if((value & 0xf0) == 0xc0) // Enable 1.5K of RAM at 0x1800 - 0x1dff
bankRAMMiddle(value & 0xf);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool Cartridge4A50::patch(uInt16 address, uInt8 value)
{
if((address & 0x1800) == 0x1000) // 2K region from 0x1000 - 0x17ff
{
if(myIsRomLow)
myImage[(address & 0x7ff) + mySliceLow] = value;
else
myRAM[(address & 0x7ff) + mySliceLow] = value;
}
else if(((address & 0x1fff) >= 0x1800) && // 1.5K region from 0x1800 - 0x1dff
((address & 0x1fff) <= 0x1dff))
{
if(myIsRomMiddle)
myImage[(address & 0x7ff) + mySliceMiddle + 0x10000] = value;
else
myRAM[(address & 0x7ff) + mySliceMiddle] = value;
}
else if((address & 0x1f00) == 0x1e00) // 256B region from 0x1e00 - 0x1eff
{
if(myIsRomHigh)
myImage[(address & 0xff) + mySliceHigh + 0x10000] = value;
else
myRAM[(address & 0xff) + mySliceHigh] = value;
}
else if((address & 0x1f00) == 0x1f00) // 256B region from 0x1f00 - 0x1fff
{
myImage[(address & 0xff) + 0x1ff00] = value;
}
return myBankChanged = true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const uInt8* Cartridge4A50::getImage(uInt32& size) const
{
size = mySize;
return myImage;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool Cartridge4A50::save(Serializer& out) const
{
try
{
out.putString(name());
// The 32K bytes of RAM
out.putByteArray(myRAM, 32768);
// Index pointers
out.putShort(mySliceLow);
out.putShort(mySliceMiddle);
out.putShort(mySliceHigh);
// Whether index pointers are for ROM or RAM
out.putBool(myIsRomLow);
out.putBool(myIsRomMiddle);
out.putBool(myIsRomHigh);
// Last address and data values
out.putByte(myLastData);
out.putShort(myLastAddress);
}
catch(...)
{
cerr << "ERROR: Cartridge4A40::save" << endl;
return false;
}
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool Cartridge4A50::load(Serializer& in)
{
try
{
if(in.getString() != name())
return false;
in.getByteArray(myRAM, 32768);
// Index pointers
mySliceLow = in.getShort();
mySliceMiddle = in.getShort();
mySliceHigh = in.getShort();
// Whether index pointers are for ROM or RAM
myIsRomLow = in.getBool();
myIsRomMiddle = in.getBool();
myIsRomHigh = in.getBool();
// Last address and data values
myLastData = in.getByte();
myLastAddress = in.getShort();
}
catch(...)
{
cerr << "ERROR: Cartridge4A50::load" << endl;
return false;
}
return true;
}