mirror of https://github.com/stella-emu/stella.git
refactored Cart3E+
This commit is contained in:
parent
00e67f1a51
commit
a823fad32c
|
@ -30,8 +30,8 @@ Cartridge3EPlusWidget::Cartridge3EPlusWidget(
|
|||
size_t size = cart.mySize;
|
||||
|
||||
ostringstream info;
|
||||
info << "3EPlus cartridge - (64K ROM + RAM)\n"
|
||||
<< " 4-64K ROM (1K banks), 32K RAM (512b banks)\n"
|
||||
info << "3EPlus cartridge - (4..64K ROM + RAM)\n"
|
||||
<< " 4..64K ROM (1K banks), ..32K RAM (512b banks)\n"
|
||||
<< "Each 1K ROM selected by writing to $3F\n"
|
||||
"Each 512b RAM selected by writing to $3E\n"
|
||||
" Lower 512b of bank x (R)\n"
|
||||
|
@ -39,83 +39,78 @@ Cartridge3EPlusWidget::Cartridge3EPlusWidget(
|
|||
<< "Startup bank = 0/-1/-1/0 (ROM)\n";
|
||||
|
||||
// Eventually, we should query this from the debugger/disassembler
|
||||
//uInt16 start = (cart.myImage[size-3] << 8) | cart.myImage[size-4];
|
||||
// Currently the cart starts at bank 0. If we change that, we have to change this too.
|
||||
uInt16 start = (cart.myImage[0x400-3] << 8) | cart.myImage[0x400 - 4];
|
||||
start -= start % 0x1000;
|
||||
info << "Bank RORG" << " = $" << Common::Base::HEX4 << start << "\n";
|
||||
start &= 0xF000;
|
||||
info << "Bank RORG = $" << Common::Base::HEX4 << start << "\n";
|
||||
|
||||
int xpos = 2,
|
||||
ypos = addBaseInformation(size, "T. Jentzsch", info.str()) +
|
||||
myLineHeight;
|
||||
ypos = addBaseInformation(size, "Thomas Jentzsch", info.str()) + 8;
|
||||
|
||||
VariantList bankno;
|
||||
for(uInt32 i = 0; i < myCart.ROM_BANK_COUNT; ++i)
|
||||
for(uInt32 i = 0; i < myCart.romBankCount(); ++i)
|
||||
VarList::push_back(bankno, i, i);
|
||||
|
||||
VariantList banktype;
|
||||
VarList::push_back(banktype, "ROM", "ROM");
|
||||
VarList::push_back(banktype, "RAM", "RAM");
|
||||
|
||||
for(uInt32 i = 0; i < 4; ++i)
|
||||
for(uInt32 seg = 0; seg < myCart.myBankSegs; ++seg)
|
||||
{
|
||||
int xpos_s, ypos_s = ypos;
|
||||
int xpos_s, ypos_s = ypos + 1;
|
||||
|
||||
ostringstream label;
|
||||
label << "Set segment " << i << " as ";
|
||||
label << "Set segment " << seg << " as ";
|
||||
|
||||
new StaticTextWidget(boss, _font, xpos, ypos, _font.getStringWidth(label.str()),
|
||||
myFontHeight, label.str(), TextAlign::Left);
|
||||
new StaticTextWidget(boss, _font, xpos, ypos, label.str());
|
||||
ypos += myLineHeight + 8;
|
||||
|
||||
xpos += 20;
|
||||
myBankNumber[i] =
|
||||
new PopUpWidget(boss, _font, xpos, ypos-2, _font.getStringWidth("Slot "),
|
||||
myLineHeight, bankno, "Slot ",
|
||||
6*_font.getMaxCharWidth());
|
||||
addFocusWidget(myBankNumber[i]);
|
||||
xpos += _font.getMaxCharWidth() * 2;
|
||||
myBankNumber[seg] =
|
||||
new PopUpWidget(boss, _font, xpos, ypos-2, 2 *_font.getMaxCharWidth(),
|
||||
myLineHeight, bankno, "Bank ");
|
||||
addFocusWidget(myBankNumber[seg]);
|
||||
|
||||
xpos += myBankNumber[i]->getWidth();
|
||||
myBankType[i] =
|
||||
new PopUpWidget(boss, _font, xpos, ypos-2, 5*_font.getMaxCharWidth(),
|
||||
myLineHeight, banktype, " of ", _font.getStringWidth(" of "));
|
||||
addFocusWidget(myBankType[i]);
|
||||
xpos += myBankNumber[seg]->getWidth();
|
||||
myBankType[seg] =
|
||||
new PopUpWidget(boss, _font, xpos, ypos-2, 3 *_font.getMaxCharWidth(),
|
||||
myLineHeight, banktype, " of ");
|
||||
addFocusWidget(myBankType[seg]);
|
||||
|
||||
xpos += myBankType[i]->getWidth() + 10;
|
||||
xpos = myBankType[seg]->getRight() + _font.getMaxCharWidth();
|
||||
|
||||
myBankCommit[i] = new ButtonWidget(boss, _font, xpos, ypos-4,
|
||||
// add "Commit" button (why required?)
|
||||
myBankCommit[seg] = new ButtonWidget(boss, _font, xpos, ypos-4,
|
||||
_font.getStringWidth(" Commit "), myButtonHeight,
|
||||
"Commit", bankEnum[i]);
|
||||
myBankCommit[i]->setTarget(this);
|
||||
addFocusWidget(myBankCommit[i]);
|
||||
"Commit", bankEnum[seg]);
|
||||
myBankCommit[seg]->setTarget(this);
|
||||
addFocusWidget(myBankCommit[seg]);
|
||||
|
||||
xpos_s = xpos + myBankCommit[i]->getWidth() + 20;
|
||||
xpos_s = myBankCommit[seg]->getRight() + _font.getMaxCharWidth() * 2;
|
||||
|
||||
StaticTextWidget* t;
|
||||
int addr1 = start + (i*0x400), addr2 = addr1 + 0x1FF;
|
||||
int addr1 = start + (seg * 0x400), addr2 = addr1 + 0x200;
|
||||
|
||||
label.str("");
|
||||
label << Common::Base::HEX4 << addr1 << "-" << Common::Base::HEX4 << addr2;
|
||||
t = new StaticTextWidget(boss, _font, xpos_s, ypos_s+2,
|
||||
_font.getStringWidth(label.str()), myFontHeight, label.str(), TextAlign::Left);
|
||||
label << "$" << Common::Base::HEX4 << addr1 << "-$" << Common::Base::HEX4 << (addr1 + 0x1FF);
|
||||
t = new StaticTextWidget(boss, _font, xpos_s, ypos_s+2, label.str());
|
||||
|
||||
int xoffset = xpos_s+t->getWidth() + 10;
|
||||
myBankState[2*i] = new EditTextWidget(boss, _font, xoffset, ypos_s,
|
||||
int xoffset = t->getRight() + _font.getMaxCharWidth();
|
||||
myBankState[2*seg] = new EditTextWidget(boss, _font, xoffset, ypos_s,
|
||||
w - xoffset - 10, myLineHeight, "");
|
||||
myBankState[2*i]->setEditable(false, true);
|
||||
myBankState[2*seg]->setEditable(false, true);
|
||||
ypos_s += myLineHeight + 4;
|
||||
|
||||
label.str("");
|
||||
label << Common::Base::HEX4 << (addr2 + 1) << "-" << Common::Base::HEX4 << (addr2 + 1 + 0x1FF);
|
||||
new StaticTextWidget(boss, _font, xpos_s, ypos_s+2,
|
||||
_font.getStringWidth(label.str()), myFontHeight, label.str(), TextAlign::Left);
|
||||
label << "$" << Common::Base::HEX4 << addr2 << "-$" << Common::Base::HEX4 << (addr2 + 0x1FF);
|
||||
new StaticTextWidget(boss, _font, xpos_s, ypos_s+2, label.str());
|
||||
|
||||
myBankState[2*i+1] = new EditTextWidget(boss, _font, xoffset, ypos_s,
|
||||
myBankState[2*seg+1] = new EditTextWidget(boss, _font, xoffset, ypos_s,
|
||||
w - xoffset - 10, myLineHeight, "");
|
||||
myBankState[2*i+1]->setEditable(false, true);
|
||||
myBankState[2*seg+1]->setEditable(false, true);
|
||||
|
||||
xpos = 10;
|
||||
ypos+= 2 * myLineHeight;
|
||||
xpos = 2;
|
||||
ypos += 2 * myLineHeight;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -163,15 +158,14 @@ void Cartridge3EPlusWidget::handleCommand(CommandSender* sender,
|
|||
myBankType[segment]->getSelected() < 0)
|
||||
return;
|
||||
|
||||
uInt8 bank = (segment << myCart.BANK_BITS) |
|
||||
(myBankNumber[segment]->getSelected() & myCart.BIT_BANK_MASK);
|
||||
uInt8 bank = myBankNumber[segment]->getSelected();
|
||||
|
||||
myCart.unlockBank();
|
||||
|
||||
if(myBankType[segment]->getSelectedTag() == "ROM")
|
||||
myCart.bankROM(bank);
|
||||
myCart.bank(bank, segment);
|
||||
else
|
||||
myCart.bankRAM(bank);
|
||||
myCart.bank(bank + myCart.romBankCount(), segment);
|
||||
|
||||
myCart.lockBank();
|
||||
invalidate();
|
||||
|
@ -183,26 +177,15 @@ string Cartridge3EPlusWidget::bankState()
|
|||
{
|
||||
ostringstream& buf = buffer();
|
||||
|
||||
// In this scheme, consecutive 512b segments are either both ROM or both RAM;
|
||||
// we only need to look at the lower segment to determine what the 1K bank is
|
||||
for(int i = 0; i < 4; ++i)
|
||||
for(int seg = 0; seg < myCart.myBankSegs; ++seg)
|
||||
{
|
||||
uInt16 bank = myCart.bankInUse[i*2];
|
||||
int bank = myCart.getSegmentBank(seg);
|
||||
|
||||
if(bank == myCart.BANK_UNDEFINED) // never accessed
|
||||
{
|
||||
buf << " U!";
|
||||
}
|
||||
if(bank >= myCart.romBankCount()) // was RAM mapped here?
|
||||
buf << " RAM " << bank - myCart.romBankCount();
|
||||
else
|
||||
{
|
||||
int bankno = bank & myCart.BIT_BANK_MASK;
|
||||
|
||||
if(bank & myCart.BITMASK_ROMRAM) // was RAM mapped here?
|
||||
buf << " RAM " << bankno;
|
||||
else
|
||||
buf << " ROM " << bankno;
|
||||
}
|
||||
if(i < 3)
|
||||
buf << " ROM " << bank;
|
||||
if(seg < myCart.myBankSegs - 1)
|
||||
buf << " /";
|
||||
}
|
||||
|
||||
|
@ -212,68 +195,42 @@ string Cartridge3EPlusWidget::bankState()
|
|||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void Cartridge3EPlusWidget::updateUIState()
|
||||
{
|
||||
// Set description for each 512b bank state (@ each index)
|
||||
// Set description for each 1K bank state (@ each index)
|
||||
// Set contents for actual banks number and type (@ each even index)
|
||||
for(int i = 0; i < 8; ++i)
|
||||
for(int seg = 0; seg < myCart.myBankSegs; ++seg)
|
||||
{
|
||||
uInt16 bank = myCart.bankInUse[i];
|
||||
uInt16 bank = myCart.getSegmentBank(seg);
|
||||
ostringstream buf;
|
||||
|
||||
if(bank == myCart.BANK_UNDEFINED) // never accessed
|
||||
if(bank >= myCart.romBankCount()) // was RAM mapped here?
|
||||
{
|
||||
myBankState[i]->setText("Undefined");
|
||||
if(i % 2 == 0)
|
||||
{
|
||||
myBankNumber[i/2]->clearSelection();
|
||||
myBankType[i/2]->clearSelection();
|
||||
}
|
||||
uInt16 ramBank = bank - myCart.romBankCount();
|
||||
|
||||
buf << "RAM " << std::dec << ramBank << " @ $" << Common::Base::HEX4
|
||||
<< (ramBank << myCart.myBankShift) << "(R)";
|
||||
myBankState[seg * 2]->setText(buf.str());
|
||||
|
||||
buf.str("");
|
||||
buf << "RAM " << std::dec << ramBank << " @ $" << Common::Base::HEX4
|
||||
<< ((ramBank << myCart.myBankShift) + myCart.myBankSize) << "(W)";
|
||||
myBankState[seg * 2 + 1]->setText(buf.str());
|
||||
|
||||
myBankNumber[seg]->setSelected(ramBank);
|
||||
myBankType[seg]->setSelected("RAM");
|
||||
}
|
||||
else
|
||||
{
|
||||
ostringstream buf;
|
||||
int bankno = bank & myCart.BIT_BANK_MASK;
|
||||
buf << "ROM " << std::dec << bank << " @ $" << Common::Base::HEX4
|
||||
<< ((bank << myCart.myBankShift));
|
||||
myBankState[seg * 2]->setText(buf.str());
|
||||
|
||||
if(bank & myCart.BITMASK_ROMRAM) // was RAM mapped here?
|
||||
{
|
||||
if(bank & myCart.BITMASK_LOWERUPPER) // upper is write port
|
||||
{
|
||||
buf << "RAM " << bankno << " @ $" << Common::Base::HEX4
|
||||
<< (bankno << myCart.RAM_BANK_TO_POWER) << " (W)";
|
||||
myBankState[i]->setText(buf.str());
|
||||
}
|
||||
else
|
||||
{
|
||||
buf << "RAM " << bankno << " @ $" << Common::Base::HEX4
|
||||
<< (bankno << myCart.RAM_BANK_TO_POWER) << " (R)";
|
||||
myBankState[i]->setText(buf.str());
|
||||
}
|
||||
buf.str("");
|
||||
buf << "ROM " << std::dec << bank << " @ $" << Common::Base::HEX4
|
||||
<< ((bank << myCart.myBankShift) + myCart.myBankSize);
|
||||
myBankState[seg * 2 + 1]->setText(buf.str());
|
||||
|
||||
if(i % 2 == 0)
|
||||
{
|
||||
myBankNumber[i/2]->setSelected(bankno);
|
||||
myBankType[i/2]->setSelected("RAM");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(bank & myCart.BITMASK_LOWERUPPER) // upper is high 512b
|
||||
{
|
||||
buf << "ROM " << bankno << " @ $" << Common::Base::HEX4
|
||||
<< ((bankno << myCart.RAM_BANK_TO_POWER) + myCart.RAM_BANK_SIZE);
|
||||
myBankState[i]->setText(buf.str());
|
||||
}
|
||||
else
|
||||
{
|
||||
buf << "ROM " << bankno << " @ $" << Common::Base::HEX4
|
||||
<< (bankno << myCart.RAM_BANK_TO_POWER);
|
||||
myBankState[i]->setText(buf.str());
|
||||
}
|
||||
|
||||
if(i % 2 == 0)
|
||||
{
|
||||
myBankNumber[i/2]->setSelected(bankno);
|
||||
myBankType[i/2]->setSelected("ROM");
|
||||
}
|
||||
}
|
||||
myBankNumber[seg]->setSelected(bank);
|
||||
myBankType[seg]->setSelected("ROM");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -294,9 +251,10 @@ uInt32 Cartridge3EPlusWidget::internalRamRPort(int start)
|
|||
string Cartridge3EPlusWidget::internalRamDescription()
|
||||
{
|
||||
ostringstream desc;
|
||||
|
||||
desc << "Accessible 512b at a time via:\n"
|
||||
<< " $f000/$f400/$f800/$fc00 for Read Access\n"
|
||||
<< " $f200/$f600/$fa00/$fe00 for Write Access (+$200)";
|
||||
<< " $f200/$f600/$fa00/$fe00 for Write Access";
|
||||
|
||||
return desc.str();
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ int CartDebugWidget::addBaseInformation(size_t bytes, const string& manufacturer
|
|||
const string& desc, const uInt16 maxlines)
|
||||
{
|
||||
const int lwidth = _font.getStringWidth("Manufacturer "),
|
||||
fwidth = _w - lwidth - 20;
|
||||
fwidth = _w - lwidth - 12;
|
||||
EditTextWidget* w = nullptr;
|
||||
ostringstream buf;
|
||||
|
||||
|
|
|
@ -105,7 +105,7 @@ Bankswitch::BSList = {{
|
|||
{ "128IN1" , "128IN1 Multicart (256/512K)" },
|
||||
{ "2K" , "2K (32-2048 bytes Atari)" },
|
||||
{ "3E" , "3E (32K Tigervision)" },
|
||||
{ "3E+" , "3E+ (TJ modified DASH)" },
|
||||
{ "3E+" , "3E+ (TJ modified 3E)" },
|
||||
{ "3F" , "3F (512K Tigervision)" },
|
||||
{ "4A50" , "4A50 (64K 4A50 + RAM)" },
|
||||
{ "4K" , "4K (4K Atari)" },
|
||||
|
|
|
@ -35,7 +35,7 @@ void Cartridge3E::install(System& system)
|
|||
{
|
||||
CartridgeEnhanced::install(system);
|
||||
|
||||
System::PageAccess access(this, System::PageAccessType::READWRITE);
|
||||
System::PageAccess access(this, System::PageAccessType::WRITE);
|
||||
|
||||
// The hotspots ($3E and $3F) are in TIA address space, so we claim it here
|
||||
for(uInt16 addr = 0x00; addr < 0x40; addr += System::PAGE_SIZE)
|
||||
|
|
|
@ -22,339 +22,104 @@
|
|||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Cartridge3EPlus::Cartridge3EPlus(const ByteBuffer& image, size_t size,
|
||||
const string& md5, const Settings& settings)
|
||||
: Cartridge(settings, md5),
|
||||
mySize(size)
|
||||
{
|
||||
// Allocate array for the ROM image
|
||||
myImage = make_unique<uInt8[]>(mySize);
|
||||
: CartridgeEnhanced(image, size, md5, settings)
|
||||
|
||||
// Copy the ROM image into my buffer
|
||||
std::copy_n(image.get(), mySize, myImage.get());
|
||||
createRomAccessArrays(mySize + myRAM.size());
|
||||
{
|
||||
myBankShift = BANK_SHIFT;
|
||||
myRamSize = RAM_SIZE;
|
||||
myRamBankCount = RAM_BANKS;
|
||||
myRamWpHigh = RAM_HIGH_WP;
|
||||
|
||||
//// Allocate array for the ROM image
|
||||
//myImage = make_unique<uInt8[]>(mySize);
|
||||
|
||||
//// Copy the ROM image into my buffer
|
||||
//std::copy_n(image.get(), mySize, myImage.get());
|
||||
//createRomAccessArrays(mySize + myRAM.size());
|
||||
}
|
||||
|
||||
//// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
//void Cartridge3EPlus::reset()
|
||||
//{
|
||||
// initializeRAM(myRAM.data(), myRAM.size());
|
||||
//
|
||||
// // Remember startup bank (0 per spec, rather than last per 3E scheme).
|
||||
// // Set this to go to 3rd 1K Bank.
|
||||
// initializeStartBank(0);
|
||||
//
|
||||
// // Initialise bank values for all ROM/RAM access
|
||||
// // This is used to reverse-lookup from address to bank location
|
||||
// for(auto& b: bankInUse)
|
||||
// b = BANK_UNDEFINED; // bank is undefined and inaccessible!
|
||||
//
|
||||
// initializeBankState();
|
||||
//
|
||||
// // We'll map the startup banks 0 and 3 from the image into the third 1K bank upon reset
|
||||
// bankROM((0 << BANK_BITS) | 0);
|
||||
// bankROM((3 << BANK_BITS) | 0);
|
||||
//}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void Cartridge3EPlus::reset()
|
||||
{
|
||||
initializeRAM(myRAM.data(), myRAM.size());
|
||||
CartridgeEnhanced::reset();
|
||||
|
||||
// Remember startup bank (0 per spec, rather than last per 3E scheme).
|
||||
// Set this to go to 3rd 1K Bank.
|
||||
initializeStartBank(0);
|
||||
|
||||
// Initialise bank values for all ROM/RAM access
|
||||
// This is used to reverse-lookup from address to bank location
|
||||
for(auto& b: bankInUse)
|
||||
b = BANK_UNDEFINED; // bank is undefined and inaccessible!
|
||||
|
||||
initializeBankState();
|
||||
|
||||
// We'll map the startup banks 0 and 3 from the image into the third 1K bank upon reset
|
||||
bankROM((0 << BANK_BITS) | 0);
|
||||
bankROM((3 << BANK_BITS) | 0);
|
||||
bank(mySystem->randGenerator().next() % romBankCount(), 1);
|
||||
bank(mySystem->randGenerator().next() % romBankCount(), 2);
|
||||
bank(startBank(), 3); // Stella reads the PC vector always from here (TODO?)
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void Cartridge3EPlus::install(System& system)
|
||||
{
|
||||
mySystem = &system;
|
||||
CartridgeEnhanced::install(system);
|
||||
|
||||
System::PageAccess access(this, System::PageAccessType::READWRITE);
|
||||
System::PageAccess access(this, System::PageAccessType::WRITE);
|
||||
|
||||
// The hotspots are in TIA address space, so we claim it here
|
||||
for(uInt16 addr = 0x00; addr < 0x40; addr += System::PAGE_SIZE)
|
||||
mySystem->setPageAccess(addr, access);
|
||||
|
||||
// Initialise bank values for all ROM/RAM access
|
||||
// This is used to reverse-lookup from address to bank location
|
||||
for(auto& b: bankInUse)
|
||||
b = BANK_UNDEFINED; // bank is undefined and inaccessible!
|
||||
|
||||
initializeBankState();
|
||||
|
||||
// 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
|
||||
bankROM((0 << BANK_BITS) | 0);
|
||||
bankROM((3 << BANK_BITS) | 0);
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
uInt16 Cartridge3EPlus::getBank(uInt16 address) const
|
||||
bool Cartridge3EPlus::checkSwitchBank(uInt16 address, uInt8 value)
|
||||
{
|
||||
return bankInUse[(address & 0xFFF) >> 10]; // 1K slices
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
uInt16 Cartridge3EPlus::romBankCount() const
|
||||
{
|
||||
return uInt16(mySize >> 10); // 1K slices
|
||||
// Switch banks if necessary
|
||||
if(address == 0x003F) {
|
||||
// Switch ROM bank into segment 0
|
||||
bank(value & 0b111111, value >> 6);
|
||||
return true;
|
||||
}
|
||||
else if(address == 0x003E)
|
||||
{
|
||||
// Switch RAM bank into segment 0
|
||||
bank((value & 0b111111) + romBankCount(), value >> 6);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
uInt8 Cartridge3EPlus::peek(uInt16 address)
|
||||
{
|
||||
uInt16 peekAddress = address;
|
||||
address &= 0x0FFF; // restrict to 4K address range
|
||||
address &= 0x0FFF;
|
||||
|
||||
uInt8 value = 0;
|
||||
uInt32 bank = (address >> (ROM_BANK_TO_POWER - 1)) & 7; // convert to 512 byte bank index (0-7)
|
||||
uInt16 imageBank = bankInUse[bank]; // the ROM/RAM bank that's here
|
||||
if(address < 0x0040) // TIA peek
|
||||
return mySystem->tia().peek(address);
|
||||
|
||||
if(imageBank == BANK_UNDEFINED) // an uninitialised bank?
|
||||
{
|
||||
// accessing invalid bank, so return should be... random?
|
||||
value = mySystem->randGenerator().next();
|
||||
|
||||
}
|
||||
else if(imageBank & BITMASK_ROMRAM) // a RAM bank
|
||||
{
|
||||
Int32 ramBank = imageBank & BIT_BANK_MASK; // discard irrelevant bits
|
||||
Int32 offset = ramBank << RAM_BANK_TO_POWER; // base bank address in RAM
|
||||
offset += (address & BITMASK_RAM_BANK); // + byte offset in RAM bank
|
||||
|
||||
return peekRAM(myRAM[offset], peekAddress);
|
||||
}
|
||||
|
||||
return value;
|
||||
return CartridgeEnhanced::peek(peekAddress);
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
bool Cartridge3EPlus::poke(uInt16 address, uInt8 value)
|
||||
{
|
||||
bool changed = false;
|
||||
if(CartridgeEnhanced::poke(address, value))
|
||||
return true;
|
||||
|
||||
// Check for write to the bank switch address. RAM/ROM and bank # are encoded in 'value'
|
||||
// There are NO mirrored hotspots.
|
||||
|
||||
if(address == BANK_SWITCH_HOTSPOT_RAM)
|
||||
changed = bankRAM(value);
|
||||
else if(address == BANK_SWITCH_HOTSPOT_ROM)
|
||||
changed = bankROM(value);
|
||||
|
||||
if(!(address & 0x1000))
|
||||
{
|
||||
if(address < 0x0040) // TIA poke
|
||||
// Handle TIA space that we claimed above
|
||||
changed = changed || mySystem->tia().poke(address, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
uInt32 bankNumber = (address >> RAM_BANK_TO_POWER) & 7; // now 512 byte bank # (ie: 0-7)
|
||||
Int16 whichBankIsThere = bankInUse[bankNumber]; // ROM or RAM bank reference
|
||||
return mySystem->tia().poke(address, value);
|
||||
|
||||
if(whichBankIsThere & BITMASK_ROMRAM)
|
||||
{
|
||||
if(address & RAM_BANK_SIZE)
|
||||
{
|
||||
uInt32 byteOffset = address & BITMASK_RAM_BANK;
|
||||
uInt32 baseAddress = ((whichBankIsThere & BIT_BANK_MASK) << RAM_BANK_TO_POWER) + byteOffset;
|
||||
pokeRAM(myRAM[baseAddress], address, value);
|
||||
changed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Writing to the read port should be ignored, but trigger a break if option enabled
|
||||
uInt8 dummy;
|
||||
|
||||
pokeRAM(dummy, address, value);
|
||||
myRamWriteAccess = address;
|
||||
changed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
bool Cartridge3EPlus::bankRAM(uInt8 bank)
|
||||
{
|
||||
if(bankLocked()) // debugger can lock RAM
|
||||
return false;
|
||||
|
||||
//cerr << "bankRAM " << int(bank) << endl;
|
||||
|
||||
// Each RAM bank uses two slots, separated by 0x200 in memory -- one read, one write.
|
||||
bankRAMSlot(bank | BITMASK_ROMRAM | 0);
|
||||
bankRAMSlot(bank | BITMASK_ROMRAM | BITMASK_LOWERUPPER);
|
||||
|
||||
return myBankChanged = true;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void Cartridge3EPlus::bankRAMSlot(uInt16 bank)
|
||||
{
|
||||
uInt16 bankNumber = (bank >> BANK_BITS) & 3; // which bank # we are switching TO (BITS D6,D7) to 512 byte block
|
||||
uInt16 currentBank = bank & BIT_BANK_MASK; // Wrap around/restrict to valid range
|
||||
bool upper = bank & BITMASK_LOWERUPPER; // is this the read or write port
|
||||
|
||||
uInt32 startCurrentBank = currentBank << RAM_BANK_TO_POWER; // Effectively * 512 bytes
|
||||
//cerr << "raw bank=" << std::dec << currentBank << endl
|
||||
// << "startCurrentBank=$" << std::hex << startCurrentBank << endl;
|
||||
// Setup the page access methods for the current bank
|
||||
System::PageAccess access(this, System::PageAccessType::READ);
|
||||
|
||||
if(upper) // We're mapping the write port
|
||||
{
|
||||
bankInUse[bankNumber * 2 + 1] = Int16(bank);
|
||||
access.type = System::PageAccessType::WRITE;
|
||||
}
|
||||
else // We're mapping the read port
|
||||
{
|
||||
bankInUse[bankNumber * 2] = Int16(bank);
|
||||
access.type = System::PageAccessType::READ;
|
||||
}
|
||||
|
||||
uInt16 start = 0x1000 + (bankNumber << (RAM_BANK_TO_POWER+1)) + (upper ? RAM_WRITE_OFFSET : 0);
|
||||
uInt16 end = start + RAM_BANK_SIZE - 1;
|
||||
|
||||
//cerr << "bank RAM: " << bankNumber << " -> " << (bankNumber * 2 + (upper ? 1 : 0)) << (upper ? " (W)" : " (R)") << endl
|
||||
// << "start=" << std::hex << start << ", end=" << end << endl << endl;
|
||||
for(uInt16 addr = start; addr <= end; addr += System::PAGE_SIZE)
|
||||
{
|
||||
if(!upper)
|
||||
access.directPeekBase = &myRAM[startCurrentBank + (addr & (RAM_BANK_SIZE - 1))];
|
||||
|
||||
access.romAccessBase = &myRomAccessBase[mySize + startCurrentBank + (addr & (RAM_BANK_SIZE - 1))];
|
||||
mySystem->setPageAccess(addr, access);
|
||||
}
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
bool Cartridge3EPlus::bankROM(uInt8 bank)
|
||||
{
|
||||
if(bankLocked()) // debugger can lock ROM
|
||||
return false;
|
||||
|
||||
// Map ROM bank image into the system into the correct slot
|
||||
// Memory map is 1K slots at 0x1000, 0x1400, 0x1800, 0x1C00
|
||||
// Each ROM uses 2 consecutive 512 byte slots
|
||||
bankROMSlot(bank | 0);
|
||||
bankROMSlot(bank | BITMASK_LOWERUPPER);
|
||||
|
||||
return myBankChanged = true;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void Cartridge3EPlus::bankROMSlot(uInt16 bank)
|
||||
{
|
||||
uInt16 bankNumber = (bank >> BANK_BITS) & 3; // which bank # we are switching TO (BITS D6,D7)
|
||||
uInt16 currentBank = bank & BIT_BANK_MASK; // Wrap around/restrict to valid range
|
||||
bool upper = bank & BITMASK_LOWERUPPER; // is this the lower or upper 512b
|
||||
|
||||
bankInUse[bankNumber * 2 + (upper ? 1 : 0)] = Int16(bank); // Record which bank switched in (as ROM)
|
||||
|
||||
uInt32 startCurrentBank = currentBank << ROM_BANK_TO_POWER; // Effectively *1K
|
||||
|
||||
// Setup the page access methods for the current bank
|
||||
System::PageAccess access(this, System::PageAccessType::READ);
|
||||
|
||||
uInt16 start = 0x1000 + (bankNumber << ROM_BANK_TO_POWER) + (upper ? ROM_BANK_SIZE / 2 : 0);
|
||||
uInt16 end = start + ROM_BANK_SIZE / 2 - 1;
|
||||
|
||||
for(uInt16 addr = start; addr <= end; addr += System::PAGE_SIZE)
|
||||
{
|
||||
access.directPeekBase = &myImage[startCurrentBank + (addr & (ROM_BANK_SIZE - 1))];
|
||||
access.romAccessBase = &myRomAccessBase[startCurrentBank + (addr & (ROM_BANK_SIZE - 1))];
|
||||
mySystem->setPageAccess(addr, access);
|
||||
}
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void Cartridge3EPlus::initializeBankState()
|
||||
{
|
||||
// Switch in each 512b slot
|
||||
for(uInt32 b = 0; b < 8; ++b)
|
||||
{
|
||||
if(bankInUse[b] == BANK_UNDEFINED)
|
||||
{
|
||||
// All accesses point to peek/poke above
|
||||
System::PageAccess access(this, System::PageAccessType::READ);
|
||||
uInt16 start = 0x1000 + (b << RAM_BANK_TO_POWER);
|
||||
uInt16 end = start + RAM_BANK_SIZE - 1;
|
||||
for(uInt16 addr = start; addr <= end; addr += System::PAGE_SIZE)
|
||||
mySystem->setPageAccess(addr, access);
|
||||
}
|
||||
else if (bankInUse[b] & BITMASK_ROMRAM)
|
||||
bankRAMSlot(bankInUse[b]);
|
||||
else
|
||||
bankROMSlot(bankInUse[b]);
|
||||
}
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
bool Cartridge3EPlus::patch(uInt16 address, uInt8 value)
|
||||
{
|
||||
#if 0
|
||||
// Patch the cartridge ROM (for debugger)
|
||||
|
||||
myBankChanged = true;
|
||||
|
||||
uInt32 bankNumber = (address >> RAM_BANK_TO_POWER) & 7; // now 512 byte bank # (ie: 0-7)
|
||||
uInt16 whichBankIsThere = bankInUse[bankNumber]; // ROM or RAM bank reference
|
||||
|
||||
if (whichBankIsThere == BANK_UNDEFINED) {
|
||||
|
||||
// We're trying to access undefined memory (no bank here yet). Fail!
|
||||
myBankChanged = false;
|
||||
|
||||
} else if (whichBankIsThere & BITMASK_ROMRAM) { // patching RAM (512 byte banks)
|
||||
|
||||
uInt32 byteOffset = address & BITMASK_RAM_BANK;
|
||||
uInt32 baseAddress = ((whichBankIsThere & BIT_BANK_MASK) << RAM_BANK_TO_POWER) + byteOffset;
|
||||
myRAM[baseAddress] = value; // write to RAM
|
||||
|
||||
// TODO: Stephen -- should we set 'myBankChanged' true when there's a RAM write?
|
||||
|
||||
} else { // patching ROM (1K banks)
|
||||
|
||||
uInt32 byteOffset = address & BITMASK_ROM_BANK;
|
||||
uInt32 baseAddress = (whichBankIsThere << ROM_BANK_TO_POWER) + byteOffset;
|
||||
myImage[baseAddress] = value; // write to the image
|
||||
}
|
||||
|
||||
return myBankChanged;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
const uInt8* Cartridge3EPlus::getImage(size_t& size) const
|
||||
{
|
||||
size = mySize;
|
||||
return myImage.get();
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
bool Cartridge3EPlus::save(Serializer& out) const
|
||||
{
|
||||
try
|
||||
{
|
||||
out.putShortArray(bankInUse.data(), bankInUse.size());
|
||||
out.putByteArray(myRAM.data(), myRAM.size());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
cerr << "ERROR: Cartridge3EPlus::save" << endl;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
bool Cartridge3EPlus::load(Serializer& in)
|
||||
{
|
||||
try
|
||||
{
|
||||
in.getShortArray(bankInUse.data(), bankInUse.size());
|
||||
in.getByteArray(myRAM.data(), myRAM.size());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
cerr << "ERROR: Cartridge3EPlus::load" << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
initializeBankState();
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
class System;
|
||||
|
||||
#include "bspf.hxx"
|
||||
#include "Cart.hxx"
|
||||
#include "CartEnhanced.hxx"
|
||||
|
||||
#ifdef DEBUGGER_SUPPORT
|
||||
class Cartridge3EPlusWidget;
|
||||
|
@ -29,19 +29,67 @@ class Cartridge3EPlusWidget;
|
|||
#endif
|
||||
|
||||
/**
|
||||
Cartridge class from Thomas Jentzsch, mostly based on the 'DASH' scheme
|
||||
with the following changes:
|
||||
Cartridge class for new tiling engine "Boulder Dash" format games with RAM.
|
||||
Kind of a combination of 3F and 3E, with better switchability.
|
||||
B.Watson's Cart3E was used as a template for building this implementation.
|
||||
|
||||
RAM areas:
|
||||
- read $x000, write $x200
|
||||
- read $x400, write $x600
|
||||
- read $x800, write $xa00
|
||||
- read $xc00, write $xe00
|
||||
The destination bank (0-3) is held in the top bits of the value written to
|
||||
$3E (for RAM switching) or $3F (for ROM switching). The low 6 bits give
|
||||
the actual bank number (0-63) corresponding to 512 byte blocks for RAM and
|
||||
1024 byte blocks for ROM. The maximum size is therefore 32K RAM and 64K ROM.
|
||||
|
||||
@author Thomas Jentzsch and Stephen Anthony
|
||||
D7D6 indicate the bank number (0-3)
|
||||
D5D4D3D2D1D0 indicate the actual # (0-63) from the image/ram
|
||||
|
||||
ROM:
|
||||
|
||||
Note: in descriptions $F000 is equivalent to $1000 -- that is, we only deal
|
||||
with the low 13 bits of addressing. Stella code uses $1000, I'm used to $F000
|
||||
So, mask with top bits clear :) when reading this document.
|
||||
|
||||
In this scheme, the 4K address space is broken into four 1K ROM/512b RAM segments
|
||||
living at 0x1000, 0x1400, 0x1800, 0x1C00 (or, same thing, 0xF000... etc.),
|
||||
|
||||
The last 1K ROM ($FC00-$FFFF) segment in the 6502 address space (ie: $1C00-$1FFF)
|
||||
is initialised to point to the FIRST 1K of the ROM image, so the reset vectors
|
||||
must be placed at the end of the first 1K in the ROM image. 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.
|
||||
|
||||
ROM switching (write of block+bank number to $3F) D7D6 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).
|
||||
|
||||
D7 D6 D5D4D3D2D1D0
|
||||
0 0 x x x x x x switch a 1K ROM bank xxxxxx to $F000
|
||||
0 1 switch a 1K ROM bank xxxxxx to $F400
|
||||
1 0 switch a 1K ROM bank xxxxxx to $F800
|
||||
1 1 switch a 1K ROM bank xxxxxx to $FC00
|
||||
|
||||
RAM switching (write of segment+bank number to $3E) with D7D6 upper 2 bits of
|
||||
bank # indicates the destination RAM segment (0-3, corresponding to $F000,
|
||||
$F400, $F800, $FC00).
|
||||
|
||||
Can handle 64 x 512 byte RAM banks (32K total)
|
||||
|
||||
D7 D6 D5D4D3D2D1D0
|
||||
0 0 x x x x x x switch a 512 byte RAM bank xxxxxx to $F000 with write @ $F200
|
||||
0 1 switch a 512 byte RAM bank xxxxxx to $F400 with write @ $F600
|
||||
1 0 switch a 512 byte RAM bank xxxxxx to $F800 with write @ $FA00
|
||||
1 1 switch a 512 byte RAM bank xxxxxx to $FC00 with write @ $FE00
|
||||
|
||||
@author Thomas Jentzsch and Stephen Anthony
|
||||
*/
|
||||
|
||||
class Cartridge3EPlus: public Cartridge
|
||||
class Cartridge3EPlus: public CartridgeEnhanced
|
||||
{
|
||||
friend class Cartridge3EPlusWidget;
|
||||
|
||||
|
@ -70,51 +118,6 @@ class Cartridge3EPlus: public Cartridge
|
|||
*/
|
||||
void install(System& system) override;
|
||||
|
||||
/**
|
||||
Get the current bank.
|
||||
|
||||
@param address The address to use when querying the bank
|
||||
*/
|
||||
uInt16 getBank(uInt16 address = 0) const override;
|
||||
|
||||
/**
|
||||
Query the number of banks supported by the cartridge.
|
||||
*/
|
||||
uInt16 romBankCount() const override;
|
||||
|
||||
/**
|
||||
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) override;
|
||||
|
||||
/**
|
||||
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(size_t& size) const override;
|
||||
|
||||
/**
|
||||
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 override;
|
||||
|
||||
/**
|
||||
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) override;
|
||||
|
||||
/**
|
||||
Get a descriptor for the device name (used in error checking).
|
||||
|
||||
|
@ -152,47 +155,20 @@ class Cartridge3EPlus: public Cartridge
|
|||
bool poke(uInt16 address, uInt8 value) override;
|
||||
|
||||
private:
|
||||
bool bankRAM(uInt8 bank); // switch a RAM bank
|
||||
bool bankROM(uInt8 bank); // switch a ROM bank
|
||||
bool checkSwitchBank(uInt16 address, uInt8 value) override;
|
||||
|
||||
void bankRAMSlot(uInt16 bank); // switch in a 512b RAM slot (lower or upper 1/2 bank)
|
||||
void bankROMSlot(uInt16 bank); // switch in a 512b RAM slot (read or write port)
|
||||
private:
|
||||
// log(ROM bank segment size) / log(2)
|
||||
static constexpr uInt16 BANK_SHIFT = 10; // = 1K = 0x0400
|
||||
|
||||
void initializeBankState(); // set all banks according to current bankInUse state
|
||||
// The size of extra RAM in ROM address space
|
||||
static constexpr uInt16 RAM_BANKS = 64;
|
||||
|
||||
// We have an array that indicates for each of the 8 512 byte areas of the address space, which ROM/RAM
|
||||
// bank is used in that area. ROM switches 1K so occupies 2 successive entries for each switch. RAM occupies
|
||||
// two as well, one 512 byte for read and one for write. The RAM locations are +0x800 apart, and the ROM
|
||||
// are consecutive. This allows us to determine on a read/write exactly where the data is.
|
||||
// RAM size
|
||||
static constexpr uInt16 RAM_SIZE = RAM_BANKS << (BANK_SHIFT - 1); // = 32K = 0x4000;
|
||||
|
||||
static constexpr uInt16 BANK_UNDEFINED = 0x8000; // bank is undefined and inaccessible
|
||||
std::array<uInt16, 8> bankInUse; // bank being used for ROM/RAM (eight 512 byte areas)
|
||||
|
||||
static constexpr uInt16 BANK_SWITCH_HOTSPOT_RAM = 0x3E; // writes to this address cause bankswitching
|
||||
static constexpr uInt16 BANK_SWITCH_HOTSPOT_ROM = 0x3F; // writes to this address cause bankswitching
|
||||
|
||||
static constexpr uInt8 BANK_BITS = 6; // # bits for bank
|
||||
static constexpr uInt8 BIT_BANK_MASK = (1 << BANK_BITS) - 1; // mask for those bits
|
||||
static constexpr uInt16 BITMASK_LOWERUPPER = 0x100; // flags lower or upper section of bank (1==upper)
|
||||
static constexpr uInt16 BITMASK_ROMRAM = 0x200; // flags ROM or RAM bank switching (1==RAM)
|
||||
|
||||
static constexpr uInt16 MAXIMUM_BANK_COUNT = (1 << BANK_BITS);
|
||||
static constexpr uInt16 RAM_BANK_TO_POWER = 9; // 2^n = 512
|
||||
static constexpr uInt16 RAM_BANK_SIZE = (1 << RAM_BANK_TO_POWER);
|
||||
static constexpr uInt16 BITMASK_RAM_BANK = (RAM_BANK_SIZE - 1);
|
||||
static constexpr uInt32 RAM_TOTAL_SIZE = MAXIMUM_BANK_COUNT * RAM_BANK_SIZE;
|
||||
|
||||
static constexpr uInt16 ROM_BANK_TO_POWER = 10; // 2^n = 1024
|
||||
static constexpr uInt16 ROM_BANK_SIZE = (1 << ROM_BANK_TO_POWER);
|
||||
static constexpr uInt16 BITMASK_ROM_BANK = (ROM_BANK_SIZE - 1);
|
||||
|
||||
static constexpr uInt16 ROM_BANK_COUNT = 64;
|
||||
|
||||
static constexpr uInt16 RAM_WRITE_OFFSET = 0x200;
|
||||
|
||||
ByteBuffer myImage; // Pointer to a dynamically allocated ROM image of the cartridge
|
||||
size_t mySize{0}; // Size of the ROM image
|
||||
std::array<uInt8, RAM_TOTAL_SIZE> myRAM;
|
||||
// Write port for extra RAM is at high address
|
||||
static constexpr bool RAM_HIGH_WP = true;
|
||||
|
||||
private:
|
||||
// Following constructors and assignment operators not supported
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
CartridgeEnhanced::CartridgeEnhanced(const ByteBuffer& image, size_t size,
|
||||
const string& md5, const Settings& settings)
|
||||
const string& md5, const Settings& settings)
|
||||
: Cartridge(settings, md5),
|
||||
mySize(size)
|
||||
{
|
||||
|
@ -270,9 +270,7 @@ bool CartridgeEnhanced::bank(uInt16 bank, uInt16 segment)
|
|||
access.romPeekCounter = &myRomAccessCounter[offset];
|
||||
access.romPokeCounter = &myRomAccessCounter[offset + myAccessSize];
|
||||
mySystem->setPageAccess(addr, access);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
return myBankChanged = true;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue