From 05901a2ad8049ec238b59531a957ffd9b9d3a489 Mon Sep 17 00:00:00 2001 From: thrust26 Date: Sat, 4 Apr 2020 10:53:14 +0200 Subject: [PATCH] add support for multi segment banking into CartEnhanced class refactor more cart classes --- src/common/bspf.hxx | 2 +- src/debugger/gui/CartE0Widget.cxx | 12 +- src/debugger/gui/DebuggerDialog.cxx | 2 +- src/emucore/CartBF.cxx | 133 +----------- src/emucore/CartBF.hxx | 96 +-------- src/emucore/CartBFSC.cxx | 194 +---------------- src/emucore/CartBFSC.hxx | 100 +-------- src/emucore/CartDF.cxx | 135 +----------- src/emucore/CartDF.hxx | 96 +-------- src/emucore/CartDFSC.cxx | 202 +----------------- src/emucore/CartDFSC.hxx | 100 +-------- src/emucore/CartE0.cxx | 128 ++--------- src/emucore/CartE0.hxx | 85 +------- src/emucore/CartEF.cxx | 135 +----------- src/emucore/CartEF.hxx | 96 +-------- src/emucore/CartEFSC.cxx | 202 +----------------- src/emucore/CartEFSC.hxx | 99 +-------- src/emucore/CartEnhanced.cxx | 80 +++---- src/emucore/CartEnhanced.hxx | 81 +++---- src/emucore/CartF0.hxx | 3 +- src/emucore/CartF4.hxx | 2 +- src/emucore/CartF4SC.cxx | 2 +- src/emucore/CartF6.hxx | 2 +- src/emucore/CartF6SC.cxx | 2 +- src/emucore/CartF8.hxx | 3 +- src/emucore/CartF8SC.cxx | 1 - src/emucore/CartF8SC.hxx | 3 - src/emucore/CartFC.hxx | 3 +- src/emucore/CartFE.cxx | 4 +- src/emucore/CartFE.hxx | 4 +- src/emucore/CartUA.cxx | 2 +- src/emucore/CartUA.hxx | 2 +- .../{BFSC => DFSC}/penult-demo-9-NTSC.bin | Bin .../E7/Bump 'n' Jump (1983) (M Network).bin | Bin 0 -> 8192 bytes .../EF/Zippy_V2_FINAL_NTSC.bas.bin | Bin 0 -> 65536 bytes 35 files changed, 183 insertions(+), 1828 deletions(-) rename test/roms/bankswitching/{BFSC => DFSC}/penult-demo-9-NTSC.bin (100%) create mode 100644 test/roms/bankswitching/E7/Bump 'n' Jump (1983) (M Network).bin create mode 100644 test/roms/bankswitching/EF/Zippy_V2_FINAL_NTSC.bas.bin diff --git a/src/common/bspf.hxx b/src/common/bspf.hxx index 06333b13c..7312783b3 100644 --- a/src/common/bspf.hxx +++ b/src/common/bspf.hxx @@ -84,7 +84,7 @@ using ByteArray = std::vector; using ShortArray = std::vector; using StringList = std::vector; using ByteBuffer = std::unique_ptr; // NOLINT -using WordBuffer = std::unique_ptr; // NOLINT +using DWordBuffer = std::unique_ptr; // NOLINT // We use KB a lot; let's make a literal for it constexpr uInt32 operator "" _KB(unsigned long long size) diff --git a/src/debugger/gui/CartE0Widget.cxx b/src/debugger/gui/CartE0Widget.cxx index de845b197..bdce56fdb 100644 --- a/src/debugger/gui/CartE0Widget.cxx +++ b/src/debugger/gui/CartE0Widget.cxx @@ -98,9 +98,9 @@ CartridgeE0Widget::CartridgeE0Widget( // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void CartridgeE0Widget::loadConfig() { - mySlice0->setSelectedIndex(myCart.myCurrentBank[0]); - mySlice1->setSelectedIndex(myCart.myCurrentBank[1]); - mySlice2->setSelectedIndex(myCart.myCurrentBank[2]); + mySlice0->setSelectedIndex(myCart.myCurrentSegOffset[0] >> myCart.myBankShift); + mySlice1->setSelectedIndex(myCart.myCurrentSegOffset[1] >> myCart.myBankShift); + mySlice2->setSelectedIndex(myCart.myCurrentSegOffset[2] >> myCart.myBankShift); CartDebugWidget::loadConfig(); } @@ -136,9 +136,9 @@ string CartridgeE0Widget::bankState() ostringstream& buf = buffer(); buf << "Slices: " << std::dec - << seg0[myCart.myCurrentBank[0]] << " / " - << seg1[myCart.myCurrentBank[1]] << " / " - << seg2[myCart.myCurrentBank[2]]; + << seg0[myCart.myCurrentSegOffset[0] >> myCart.myBankShift] << " / " + << seg1[myCart.myCurrentSegOffset[1] >> myCart.myBankShift] << " / " + << seg2[myCart.myCurrentSegOffset[2] >> myCart.myBankShift]; return buf.str(); } diff --git a/src/debugger/gui/DebuggerDialog.cxx b/src/debugger/gui/DebuggerDialog.cxx index 47e05da04..3143dbc10 100644 --- a/src/debugger/gui/DebuggerDialog.cxx +++ b/src/debugger/gui/DebuggerDialog.cxx @@ -617,7 +617,7 @@ void DebuggerDialog::addRomArea() // The 'cart-specific' information tab (optional) - tabID = myRomTab->addTab(" " + instance().console().cartridge().name() + " ", TabWidget::AUTO_WIDTH); + tabID = myRomTab->addTab(" " + instance().console().cartridge().name() + " ", TabWidget::AUTO_WIDTH); myCartInfo = instance().console().cartridge().infoWidget( myRomTab, *myLFont, *myNFont, 2, 2, tabWidth - 1, tabHeight - myRomTab->getTabHeight() - 2); diff --git a/src/emucore/CartBF.cxx b/src/emucore/CartBF.cxx index 91d13cbb7..0d1921c17 100644 --- a/src/emucore/CartBF.cxx +++ b/src/emucore/CartBF.cxx @@ -21,33 +21,12 @@ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CartridgeBF::CartridgeBF(const ByteBuffer& image, size_t size, const string& md5, const Settings& settings) - : Cartridge(settings, md5) + : CartridgeEnhanced(image, size, md5, settings) { - // Copy the ROM image into my buffer - std::copy_n(image.get(), std::min(myImage.size(), size), myImage.begin()); - createRomAccessArrays(myImage.size()); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void CartridgeBF::reset() -{ - initializeStartBank(1); - - // Upon reset we switch to the startup bank - bank(startBank()); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void CartridgeBF::install(System& system) -{ - mySystem = &system; - - // Install pages for the startup bank - bank(startBank()); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt8 CartridgeBF::peek(uInt16 address) +bool CartridgeBF::checkSwitchBank(uInt16 address, uInt8) { // Due to the way addressing is set up, we will only get here if the // address is in the hotspot range ($1F80 - $1FFF) @@ -55,111 +34,9 @@ uInt8 CartridgeBF::peek(uInt16 address) // Switch banks if necessary if((address >= 0x0F80) && (address <= 0x0FBF)) + { bank(address - 0x0F80); - - return myImage[myBankOffset + address]; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeBF::poke(uInt16 address, uInt8) -{ - // Due to the way addressing is set up, we will only get here if the - // address is in the hotspot range ($1F80 - $1FFF) - address &= 0x0FFF; - - // Switch banks if necessary - if((address >= 0x0F80) && (address <= 0x0FBF)) - bank(address - 0x0F80); - + return true; + } return false; } - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeBF::bank(uInt16 bank) -{ - if(bankLocked()) return false; - - // Remember what bank we're in - myBankOffset = bank << 12; - - System::PageAccess access(this, System::PageAccessType::READ); - - // Set the page accessing methods for the hot spots - for(uInt16 addr = (0x1F80 & ~System::PAGE_MASK); addr < 0x2000; - addr += System::PAGE_SIZE) - { - access.romAccessBase = &myRomAccessBase[myBankOffset + (addr & 0x0FFF)]; - mySystem->setPageAccess(addr, access); - } - - // Setup the page access methods for the current bank - for(uInt16 addr = 0x1000; addr < static_cast(0x1F80U & ~System::PAGE_MASK); - addr += System::PAGE_SIZE) - { - access.directPeekBase = &myImage[myBankOffset + (addr & 0x0FFF)]; - access.romAccessBase = &myRomAccessBase[myBankOffset + (addr & 0x0FFF)]; - mySystem->setPageAccess(addr, access); - } - return myBankChanged = true; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt16 CartridgeBF::getBank(uInt16) const -{ - return myBankOffset >> 12; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt16 CartridgeBF::bankCount() const -{ - return 64; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeBF::patch(uInt16 address, uInt8 value) -{ - myImage[myBankOffset + (address & 0x0FFF)] = value; - return myBankChanged = true; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -const uInt8* CartridgeBF::getImage(size_t& size) const -{ - size = myImage.size(); - return myImage.data(); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeBF::save(Serializer& out) const -{ - try - { - out.putInt(myBankOffset); - } - catch(...) - { - cerr << "ERROR: CartridgeBF::save" << endl; - return false; - } - - return true; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeBF::load(Serializer& in) -{ - try - { - myBankOffset = in.getInt(); - } - catch(...) - { - cerr << "ERROR: CartridgeBF::load" << endl; - return false; - } - - // Remember what bank we were in - bank(myBankOffset >> 12); - - return true; -} diff --git a/src/emucore/CartBF.hxx b/src/emucore/CartBF.hxx index 158f43a45..08b54d731 100644 --- a/src/emucore/CartBF.hxx +++ b/src/emucore/CartBF.hxx @@ -21,7 +21,7 @@ class System; #include "bspf.hxx" -#include "Cart.hxx" +#include "CartEnhanced.hxx" #ifdef DEBUGGER_SUPPORT #include "CartBFWidget.hxx" #endif @@ -31,9 +31,9 @@ class System; There are 64 4K banks (total of 256K ROM). Accessing $1F80 - $1FBF switches to each bank. - @author Mike Saarna + @author Mike Saarna, Thomas Jentzsch */ -class CartridgeBF : public Cartridge +class CartridgeBF : public CartridgeEnhanced { friend class CartridgeBFWidget; @@ -51,71 +51,6 @@ class CartridgeBF : public Cartridge virtual ~CartridgeBF() = default; public: - /** - Reset device to its power-on state - */ - void reset() override; - - /** - 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) override; - - /** - Install pages for the specified bank in the system. - - @param bank The bank that should be installed in the system - */ - bool bank(uInt16 bank) 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 bankCount() 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). @@ -135,29 +70,12 @@ class CartridgeBF : public Cartridge } #endif - public: - /** - Get the byte at the specified address. - - @return The byte at the specified address - */ - uInt8 peek(uInt16 address) override; - - /** - 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) override; - private: - // The 256K ROM image of the cartridge - std::array myImage; + bool checkSwitchBank(uInt16 address, uInt8 value = 0) override; - // Indicates the offset into the ROM image (aligns to current bank) - uInt32 myBankOffset{0}; + uInt16 romHotspot() const override { return 0x1F80; } + + uInt16 getStartBank() const override { return 1; } private: // Following constructors and assignment operators not supported diff --git a/src/emucore/CartBFSC.cxx b/src/emucore/CartBFSC.cxx index d0d33c79f..bd9793893 100644 --- a/src/emucore/CartBFSC.cxx +++ b/src/emucore/CartBFSC.cxx @@ -21,197 +21,7 @@ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CartridgeBFSC::CartridgeBFSC(const ByteBuffer& image, size_t size, const string& md5, const Settings& settings) - : Cartridge(settings, md5) + : CartridgeBF(image, size, md5, settings) { - // Copy the ROM image into my buffer - std::copy_n(image.get(), std::min(myImage.size(), size), myImage.begin()); - createRomAccessArrays(myImage.size()); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void CartridgeBFSC::reset() -{ - initializeRAM(myRAM.data(), myRAM.size()); - initializeStartBank(15); - - // Upon reset we switch to the startup bank - bank(startBank()); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void CartridgeBFSC::install(System& system) -{ - mySystem = &system; - - System::PageAccess access(this, System::PageAccessType::READ); - - // Set the page accessing method for the RAM writing pages - // Map access to this class, since we need to inspect all accesses to - // check if RWP happens - access.type = System::PageAccessType::WRITE; - for(uInt16 addr = 0x1000; addr < 0x1080; addr += System::PAGE_SIZE) - { - access.romAccessBase = &myRomAccessBase[addr & 0x007F]; - mySystem->setPageAccess(addr, access); - } - - // Set the page accessing method for the RAM reading pages - access.type = System::PageAccessType::READ; - for(uInt16 addr = 0x1080; addr < 0x1100; addr += System::PAGE_SIZE) - { - access.directPeekBase = &myRAM[addr & 0x007F]; - access.romAccessBase = &myRomAccessBase[0x80 + (addr & 0x007F)]; - mySystem->setPageAccess(addr, access); - } - - // Install pages for the startup bank - bank(startBank()); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt8 CartridgeBFSC::peek(uInt16 address) -{ - uInt16 peekAddress = address; - address &= 0x0FFF; - - // Switch banks if necessary - if((address >= 0x0F80) && (address <= 0x0FBF)) - bank(address - 0x0F80); - - if(address < 0x0080) // Write port is at 0xF000 - 0xF07F (128 bytes) - return peekRAM(myRAM[address], peekAddress); - else - return myImage[myBankOffset + address]; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeBFSC::poke(uInt16 address, uInt8 value) -{ - uInt16 pokeAddress = address; - address &= 0x0FFF; - - // Switch banks if necessary - if((address >= 0x0F80) && (address <= 0x0FBF)) - { - bank(address - 0x0F80); - return false; - } - - if (!(address & 0x080)) - { - pokeRAM(myRAM[address & 0x007F], pokeAddress, value); - return true; - } - else - { - // Writing to the read port should be ignored, but trigger a break if option enabled - uInt8 dummy; - - pokeRAM(dummy, pokeAddress, value); - myRamWriteAccess = pokeAddress; - return false; - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeBFSC::bank(uInt16 bank) -{ - if(bankLocked()) return false; - - // Remember what bank we're in - myBankOffset = bank << 12; - - System::PageAccess access(this, System::PageAccessType::READ); - - // Set the page accessing methods for the hot spots - for(uInt16 addr = (0x1F80 & ~System::PAGE_MASK); addr < 0x2000; - addr += System::PAGE_SIZE) - { - access.romAccessBase = &myRomAccessBase[myBankOffset + (addr & 0x0FFF)]; - mySystem->setPageAccess(addr, access); - } - - // Setup the page access methods for the current bank - for(uInt16 addr = 0x1100; addr < static_cast(0x1F80U & ~System::PAGE_MASK); - addr += System::PAGE_SIZE) - { - access.directPeekBase = &myImage[myBankOffset + (addr & 0x0FFF)]; - access.romAccessBase = &myRomAccessBase[myBankOffset + (addr & 0x0FFF)]; - mySystem->setPageAccess(addr, access); - } - return myBankChanged = true; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt16 CartridgeBFSC::getBank(uInt16) const -{ - return myBankOffset >> 12; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt16 CartridgeBFSC::bankCount() const -{ - return 64; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeBFSC::patch(uInt16 address, uInt8 value) -{ - address &= 0x0FFF; - - if(address < 0x0100) - { - // Normally, a write to the read port won't do anything - // However, the patch command is special in that ignores such - // cart restrictions - myRAM[address & 0x007F] = value; - } - else - myImage[myBankOffset + address] = value; - - return myBankChanged = true; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -const uInt8* CartridgeBFSC::getImage(size_t& size) const -{ - size = myImage.size(); - return myImage.data(); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeBFSC::save(Serializer& out) const -{ - try - { - out.putInt(myBankOffset); - out.putByteArray(myRAM.data(), myRAM.size()); - } - catch(...) - { - cerr << "ERROR: CartridgeBFSC::save" << endl; - return false; - } - - return true; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeBFSC::load(Serializer& in) -{ - try - { - myBankOffset = in.getInt(); - in.getByteArray(myRAM.data(), myRAM.size()); - } - catch(...) - { - cerr << "ERROR: CartridgeBFSC::load" << endl; - return false; - } - - // Remember what bank we were in - bank(myBankOffset >> 12); - - return true; + myRamSize = RAM_SIZE; } diff --git a/src/emucore/CartBFSC.hxx b/src/emucore/CartBFSC.hxx index b4cb72aad..35c4b84ca 100644 --- a/src/emucore/CartBFSC.hxx +++ b/src/emucore/CartBFSC.hxx @@ -21,7 +21,7 @@ class System; #include "bspf.hxx" -#include "Cart.hxx" +#include "CartBF.hxx" #ifdef DEBUGGER_SUPPORT #include "CartBFSCWidget.hxx" #endif @@ -31,9 +31,9 @@ class System; Accessing $1F80 - $1FBF switches to each bank. RAM read port is $1080 - $10FF, write port is $1000 - $107F. - @author Stephen Anthony + @author Stephen Anthony, Thomas Jentzsch */ -class CartridgeBFSC : public Cartridge +class CartridgeBFSC : public CartridgeBF { friend class CartridgeBFSCWidget; @@ -50,72 +50,6 @@ class CartridgeBFSC : public Cartridge const Settings& settings); virtual ~CartridgeBFSC() = default; - public: - /** - Reset device to its power-on state - */ - void reset() override; - - /** - 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) override; - - /** - Install pages for the specified bank in the system. - - @param bank The bank that should be installed in the system - */ - bool bank(uInt16 bank) 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 bankCount() 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). @@ -135,32 +69,12 @@ class CartridgeBFSC : public Cartridge } #endif - public: - /** - Get the byte at the specified address. - - @return The byte at the specified address - */ - uInt8 peek(uInt16 address) override; - - /** - 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) override; + private: + uInt16 getStartBank() const override { return 15; } private: - // The 256K ROM image of the cartridge - std::array myImage; - - // The 128 bytes of RAM - std::array myRAM; - - // Indicates the offset into the ROM image (aligns to current bank) - uInt32 myBankOffset{0}; + // RAM size + static constexpr uInt16 RAM_SIZE = 0x80; private: // Following constructors and assignment operators not supported diff --git a/src/emucore/CartDF.cxx b/src/emucore/CartDF.cxx index b43bc0837..db9ab62d2 100644 --- a/src/emucore/CartDF.cxx +++ b/src/emucore/CartDF.cxx @@ -21,145 +21,20 @@ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CartridgeDF::CartridgeDF(const ByteBuffer& image, size_t size, const string& md5, const Settings& settings) - : Cartridge(settings, md5) + : CartridgeEnhanced(image, size, md5, settings) { - // Copy the ROM image into my buffer - std::copy_n(image.get(), std::min(myImage.size(), size), myImage.begin()); - createRomAccessArrays(myImage.size()); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void CartridgeDF::reset() -{ - initializeStartBank(1); - - // Upon reset we switch to the startup bank - bank(startBank()); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void CartridgeDF::install(System& system) -{ - mySystem = &system; - - // Install pages for the startup bank - bank(startBank()); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt8 CartridgeDF::peek(uInt16 address) +bool CartridgeDF::checkSwitchBank(uInt16 address, uInt8) { address &= 0x0FFF; // Switch banks if necessary if((address >= 0x0FC0) && (address <= 0x0FDF)) + { bank(address - 0x0FC0); - - return myImage[myBankOffset + address]; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeDF::poke(uInt16 address, uInt8) -{ - address &= 0x0FFF; - - // Switch banks if necessary - if((address >= 0x0FC0) && (address <= 0x0FDF)) - bank(address - 0x0FC0); - + return true; + } return false; } - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeDF::bank(uInt16 bank) -{ - if(bankLocked()) return false; - - // Remember what bank we're in - myBankOffset = bank << 12; - - System::PageAccess access(this, System::PageAccessType::READ); - - // Set the page accessing methods for the hot spots - for(uInt16 addr = (0x1FC0 & ~System::PAGE_MASK); addr < 0x2000; - addr += System::PAGE_SIZE) - { - access.romAccessBase = &myRomAccessBase[myBankOffset + (addr & 0x0FFF)]; - access.romPeekCounter = &myRomAccessCounter[myBankOffset + (addr & 0x0FFF)]; - access.romPokeCounter = &myRomAccessCounter[myBankOffset + (addr & 0x0FFF) + myAccessSize]; - mySystem->setPageAccess(addr, access); - } - - // Setup the page access methods for the current bank - for(uInt16 addr = 0x1000; addr < static_cast(0x1FC0U & ~System::PAGE_MASK); - addr += System::PAGE_SIZE) - { - access.directPeekBase = &myImage[myBankOffset + (addr & 0x0FFF)]; - access.romAccessBase = &myRomAccessBase[myBankOffset + (addr & 0x0FFF)]; - access.romPeekCounter = &myRomAccessCounter[myBankOffset + (addr & 0x0FFF)]; - access.romPokeCounter = &myRomAccessCounter[myBankOffset + (addr & 0x0FFF) + myAccessSize]; - mySystem->setPageAccess(addr, access); - } - return myBankChanged = true; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt16 CartridgeDF::getBank(uInt16) const -{ - return myBankOffset >> 12; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt16 CartridgeDF::bankCount() const -{ - return 32; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeDF::patch(uInt16 address, uInt8 value) -{ - myImage[myBankOffset + (address & 0x0FFF)] = value; - return myBankChanged = true; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -const uInt8* CartridgeDF::getImage(size_t& size) const -{ - size = myImage.size(); - return myImage.data(); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeDF::save(Serializer& out) const -{ - try - { - out.putInt(myBankOffset); - } - catch(...) - { - cerr << "ERROR: CartridgeDF::save" << endl; - return false; - } - - return true; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeDF::load(Serializer& in) -{ - try - { - myBankOffset = in.getInt(); - } - catch(...) - { - cerr << "ERROR: CartridgeDF::load" << endl; - return false; - } - - // Remember what bank we were in - bank(myBankOffset >> 12); - - return true; -} diff --git a/src/emucore/CartDF.hxx b/src/emucore/CartDF.hxx index 04804b957..5b2025285 100644 --- a/src/emucore/CartDF.hxx +++ b/src/emucore/CartDF.hxx @@ -21,7 +21,7 @@ class System; #include "bspf.hxx" -#include "Cart.hxx" +#include "CartEnhanced.hxx" #ifdef DEBUGGER_SUPPORT #include "CartDFWidget.hxx" #endif @@ -31,9 +31,9 @@ class System; There are 32 4K banks (total of 128K ROM). Accessing $1FC0 - $1FDF switches to each bank. - @author Mike Saarna + @author Mike Saarna, Thomas Jentzsch */ -class CartridgeDF : public Cartridge +class CartridgeDF : public CartridgeEnhanced { friend class CartridgeDFWidget; @@ -51,71 +51,6 @@ class CartridgeDF : public Cartridge virtual ~CartridgeDF() = default; public: - /** - Reset device to its power-on state - */ - void reset() override; - - /** - 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) override; - - /** - Install pages for the specified bank in the system. - - @param bank The bank that should be installed in the system - */ - bool bank(uInt16 bank) 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 bankCount() 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). @@ -135,29 +70,12 @@ class CartridgeDF : public Cartridge } #endif - public: - /** - Get the byte at the specified address. - - @return The byte at the specified address - */ - uInt8 peek(uInt16 address) override; - - /** - 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) override; - private: - // The 128K ROM image of the cartridge - std::array myImage; + bool checkSwitchBank(uInt16 address, uInt8 value = 0) override; - // Indicates the offset into the ROM image (aligns to current bank) - uInt32 myBankOffset{0}; + uInt16 romHotspot() const override { return 0x1FC0; } + + uInt16 getStartBank() const override { return 15; } private: // Following constructors and assignment operators not supported diff --git a/src/emucore/CartDFSC.cxx b/src/emucore/CartDFSC.cxx index 3661459fa..f759136a7 100644 --- a/src/emucore/CartDFSC.cxx +++ b/src/emucore/CartDFSC.cxx @@ -21,205 +21,7 @@ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CartridgeDFSC::CartridgeDFSC(const ByteBuffer& image, size_t size, const string& md5, const Settings& settings) - : Cartridge(settings, md5) + : CartridgeDF(image, size, md5, settings) { - // Copy the ROM image into my buffer - std::copy_n(image.get(), std::min(myImage.size(), size), myImage.begin()); - createRomAccessArrays(myImage.size()); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void CartridgeDFSC::reset() -{ - initializeRAM(myRAM.data(), myRAM.size()); - initializeStartBank(15); - - // Upon reset we switch to the startup bank - bank(startBank()); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void CartridgeDFSC::install(System& system) -{ - mySystem = &system; - - System::PageAccess access(this, System::PageAccessType::READ); - - // Set the page accessing method for the RAM writing pages - // Map access to this class, since we need to inspect all accesses to - // check if RWP happens - access.type = System::PageAccessType::WRITE; - for(uInt16 addr = 0x1000; addr < 0x1080; addr += System::PAGE_SIZE) - { - access.romAccessBase = &myRomAccessBase[addr & 0x007F]; - access.romPeekCounter = &myRomAccessCounter[addr & 0x007F]; - access.romPokeCounter = &myRomAccessCounter[(addr & 0x007F) + myAccessSize]; - mySystem->setPageAccess(addr, access); - } - - // Set the page accessing method for the RAM reading pages - access.type = System::PageAccessType::READ; - for(uInt16 addr = 0x1080; addr < 0x1100; addr += System::PAGE_SIZE) - { - access.directPeekBase = &myRAM[addr & 0x007F]; - access.romAccessBase = &myRomAccessBase[0x80 + (addr & 0x007F)]; - access.romPeekCounter = &myRomAccessCounter[0x80 + (addr & 0x007F)]; - access.romPokeCounter = &myRomAccessCounter[0x80 + (addr & 0x007F) + myAccessSize]; - mySystem->setPageAccess(addr, access); - } - - // Install pages for the startup bank - bank(startBank()); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt8 CartridgeDFSC::peek(uInt16 address) -{ - uInt16 peekAddress = address; - address &= 0x0FFF; - - // Switch banks if necessary - if((address >= 0x0FC0) && (address <= 0x0FDF)) - bank(address - 0x0FC0); - - if(address < 0x0080) // Write port is at 0xF000 - 0xF07F (128 bytes) - return peekRAM(myRAM[address], peekAddress); - else - return myImage[myBankOffset + address]; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeDFSC::poke(uInt16 address, uInt8 value) -{ - uInt16 pokeAddress = address; - address &= 0x0FFF; - - // Switch banks if necessary - if((address >= 0x0FC0) && (address <= 0x0FDF)) - { - bank(address - 0x0FC0); - return false; - } - - if(!(address & 0x080)) - { - pokeRAM(myRAM[address & 0x007F], pokeAddress, value); - return true; - } - else - { - // Writing to the read port should be ignored, but trigger a break if option enabled - uInt8 dummy; - - pokeRAM(dummy, pokeAddress, value); - myRamWriteAccess = pokeAddress; - return false; - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeDFSC::bank(uInt16 bank) -{ - if(bankLocked()) return false; - - // Remember what bank we're in - myBankOffset = bank << 12; - - System::PageAccess access(this, System::PageAccessType::READ); - - // Set the page accessing methods for the hot spots - for(uInt16 addr = (0x1FC0 & ~System::PAGE_MASK); addr < 0x2000; - addr += System::PAGE_SIZE) - { - access.romAccessBase = &myRomAccessBase[myBankOffset + (addr & 0x0FFF)]; - access.romPeekCounter = &myRomAccessCounter[myBankOffset + (addr & 0x0FFF)]; - access.romPokeCounter = &myRomAccessCounter[myBankOffset + (addr & 0x0FFF) + myAccessSize]; - mySystem->setPageAccess(addr, access); - } - - // Setup the page access methods for the current bank - for(uInt16 addr = 0x1100; addr < static_cast(0x1FC0U & ~System::PAGE_MASK); - addr += System::PAGE_SIZE) - { - access.directPeekBase = &myImage[myBankOffset + (addr & 0x0FFF)]; - access.romAccessBase = &myRomAccessBase[myBankOffset + (addr & 0x0FFF)]; - access.romPeekCounter = &myRomAccessCounter[myBankOffset + (addr & 0x0FFF)]; - access.romPokeCounter = &myRomAccessCounter[myBankOffset + (addr & 0x0FFF) + myAccessSize]; - mySystem->setPageAccess(addr, access); - } - return myBankChanged = true; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt16 CartridgeDFSC::getBank(uInt16) const -{ - return myBankOffset >> 12; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt16 CartridgeDFSC::bankCount() const -{ - return 32; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeDFSC::patch(uInt16 address, uInt8 value) -{ - address &= 0x0FFF; - - if(address < 0x0100) - { - // Normally, a write to the read port won't do anything - // However, the patch command is special in that ignores such - // cart restrictions - myRAM[address & 0x007F] = value; - } - else - myImage[myBankOffset + address] = value; - - return myBankChanged = true; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -const uInt8* CartridgeDFSC::getImage(size_t& size) const -{ - size = myImage.size(); - return myImage.data(); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeDFSC::save(Serializer& out) const -{ - try - { - out.putInt(myBankOffset); - out.putByteArray(myRAM.data(), myRAM.size()); - } - catch(...) - { - cerr << "ERROR: CartridgeDFSC::save" << endl; - return false; - } - - return true; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeDFSC::load(Serializer& in) -{ - try - { - myBankOffset = in.getInt(); - in.getByteArray(myRAM.data(), myRAM.size()); - } - catch(...) - { - cerr << "ERROR: CartridgeDFSC::load" << endl; - return false; - } - - // Remember what bank we were in - bank(myBankOffset >> 12); - - return true; + myRamSize = RAM_SIZE; } diff --git a/src/emucore/CartDFSC.hxx b/src/emucore/CartDFSC.hxx index af35f476b..cf3661039 100644 --- a/src/emucore/CartDFSC.hxx +++ b/src/emucore/CartDFSC.hxx @@ -21,7 +21,7 @@ class System; #include "bspf.hxx" -#include "Cart.hxx" +#include "CartDF.hxx" #ifdef DEBUGGER_SUPPORT #include "CartDFSCWidget.hxx" #endif @@ -31,9 +31,9 @@ class System; Accessing $1FC0 - $1FDF switches to each bank. RAM read port is $1080 - $10FF, write port is $1000 - $107F. - @author Stephen Anthony + @author Stephen Anthony, Thomas Jentzsch */ -class CartridgeDFSC : public Cartridge +class CartridgeDFSC : public CartridgeDF { friend class CartridgeDFSCWidget; @@ -51,71 +51,6 @@ class CartridgeDFSC : public Cartridge virtual ~CartridgeDFSC() = default; public: - /** - Reset device to its power-on state - */ - void reset() override; - - /** - 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) override; - - /** - Install pages for the specified bank in the system. - - @param bank The bank that should be installed in the system - */ - bool bank(uInt16 bank) 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 bankCount() 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). @@ -135,34 +70,11 @@ class CartridgeDFSC : public Cartridge } #endif - public: - /** - Get the byte at the specified address. - - @return The byte at the specified address - */ - uInt8 peek(uInt16 address) override; - - /** - 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) override; - private: - // The 128K ROM image of the cartridge - std::array myImage; + // RAM size + static constexpr uInt16 RAM_SIZE = 0x80; - // The 128 bytes of RAM - std::array myRAM; - - // Indicates the offset into the ROM image (aligns to current bank) - uInt32 myBankOffset{0}; - - private: +private: // Following constructors and assignment operators not supported CartridgeDFSC() = delete; CartridgeDFSC(const CartridgeDFSC&) = delete; diff --git a/src/emucore/CartE0.cxx b/src/emucore/CartE0.cxx index 3788a95bc..e35917993 100644 --- a/src/emucore/CartE0.cxx +++ b/src/emucore/CartE0.cxx @@ -21,11 +21,9 @@ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CartridgeE0::CartridgeE0(const ByteBuffer& image, size_t size, const string& md5, const Settings& settings) - : Cartridge(settings, md5) + : CartridgeEnhanced(image, size, md5, settings) { - // Copy the ROM image into my buffer - std::copy_n(image.get(), std::min(myImage.size(), size), myImage.begin()); - createRomAccessArrays(myImage.size()); + myBankShift = BANK_SHIFT; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -44,7 +42,7 @@ void CartridgeE0::reset() bank(5, 1); bank(6, 2); } - myCurrentBank[3] = bankCount() - 1; // fixed + myCurrentSegOffset[3] = (bankCount() - 1) << myBankShift; // fixed myBankChanged = true; } @@ -52,7 +50,13 @@ void CartridgeE0::reset() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void CartridgeE0::install(System& system) { - mySystem = &system; + //// Allocate array for the current bank segments slices + //myCurrentSegOffset = make_unique(myBankSegs); + + //// Setup page access + //mySystem = &system; + + CartridgeEnhanced::install(system); System::PageAccess access(this, System::PageAccessType::READ); @@ -79,127 +83,23 @@ void CartridgeE0::install(System& system) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt16 CartridgeE0::getBank(uInt16 address) const +bool CartridgeE0::checkSwitchBank(uInt16 address, uInt8) { - return myCurrentBank[(address & 0xFFF) >> 10]; // 1K slices -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt16 CartridgeE0::bankCount() const -{ - return 8; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt8 CartridgeE0::peek(uInt16 address) -{ - address &= 0x0FFF; - - // Switch banks if necessary - if((address >= 0x0FE0) && (address <= 0x0FE7)) - { - bank(address & 0x0007, 0); - } - else if((address >= 0x0FE8) && (address <= 0x0FEF)) - { - bank(address & 0x0007, 1); - } - else if((address >= 0x0FF0) && (address <= 0x0FF7)) - { - bank(address & 0x0007, 2); - } - - return myImage[(myCurrentBank[address >> 10] << 10) + (address & 0x03FF)]; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeE0::poke(uInt16 address, uInt8) -{ - address &= 0x0FFF; - // Switch banks if necessary if((address >= 0x0FE0) && (address <= 0x0FE7)) { bank(address & 0x0007, 0); + return true; } else if((address >= 0x0FE8) && (address <= 0x0FEF)) { bank(address & 0x0007, 1); + return true; } else if((address >= 0x0FF0) && (address <= 0x0FF7)) { bank(address & 0x0007, 2); + return true; } return false; } - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void CartridgeE0::bank(uInt16 bank, uInt16 slice) -{ - if(bankLocked()) return; - - // Remember the new slice - myCurrentBank[slice] = bank; - uInt16 sliceOffset = slice * (1 << 10); - uInt16 bankOffset = bank << 10; - - // Setup the page access methods for the current bank - System::PageAccess access(this, System::PageAccessType::READ); - - for(uInt16 addr = 0x1000 + sliceOffset; addr < 0x1000 + sliceOffset + 0x400; addr += System::PAGE_SIZE) - { - access.directPeekBase = &myImage[bankOffset + (addr & 0x03FF)]; - access.romAccessBase = &myRomAccessBase[bankOffset + (addr & 0x03FF)]; - access.romPeekCounter = &myRomAccessCounter[bankOffset + (addr & 0x03FF)]; - access.romPokeCounter = &myRomAccessCounter[bankOffset + (addr & 0x03FF) + myAccessSize]; - mySystem->setPageAccess(addr, access); - } - myBankChanged = true; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeE0::patch(uInt16 address, uInt8 value) -{ - address &= 0x0FFF; - myImage[(myCurrentBank[address >> 10] << 10) + (address & 0x03FF)] = value; - return true; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -const uInt8* CartridgeE0::getImage(size_t& size) const -{ - size = myImage.size(); - return myImage.data(); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeE0::save(Serializer& out) const -{ - try - { - out.putShortArray(myCurrentBank.data(), myCurrentBank.size()); - } - catch(...) - { - cerr << "ERROR: CartridgeE0::save" << endl; - return false; - } - - return true; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeE0::load(Serializer& in) -{ - try - { - in.getShortArray(myCurrentBank.data(), myCurrentBank.size()); - } - catch(...) - { - cerr << "ERROR: CartridgeE0::load" << endl; - return false; - } - - return true; -} diff --git a/src/emucore/CartE0.hxx b/src/emucore/CartE0.hxx index 35513be85..66dbbba6a 100644 --- a/src/emucore/CartE0.hxx +++ b/src/emucore/CartE0.hxx @@ -21,7 +21,7 @@ class System; #include "bspf.hxx" -#include "Cart.hxx" +#include "CartEnhanced.hxx" #ifdef DEBUGGER_SUPPORT #include "CartE0Widget.hxx" #endif @@ -39,9 +39,9 @@ class System; only one actual bank, in which pieces of it can be swapped out in many different ways. - @author Bradford W. Mott + @author Bradford W. Mott, Thomas Jentzsch */ -class CartridgeE0 : public Cartridge +class CartridgeE0 : public CartridgeEnhanced { friend class CartridgeE0Widget; @@ -72,52 +72,6 @@ class CartridgeE0 : 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 bankCount() 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). @@ -137,37 +91,14 @@ class CartridgeE0 : public Cartridge } #endif - public: - /** - Get the byte at the specified address. + private: + bool checkSwitchBank(uInt16 address, uInt8 = 0) override; - @return The byte at the specified address - */ - uInt8 peek(uInt16 address) override; - - /** - 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) override; + uInt16 romHotspot() const override { return 0x1FE0; } private: - /** - Install the specified slice for segment (bank) 0..2 - - @param slice The slice to map into the segment - */ - void bank(uInt16 bank, uInt16 slice); - - private: - // The 8K ROM image of the cartridge - std::array myImage; - - // Indicates the slice mapped into each of the four segments - std::array myCurrentBank; + // log(ROM bank segment size) / log(2) + static constexpr uInt16 BANK_SHIFT = 10; // = 1K = 0x0400 private: // Following constructors and assignment operators not supported diff --git a/src/emucore/CartEF.cxx b/src/emucore/CartEF.cxx index 16635200b..70cd8236b 100644 --- a/src/emucore/CartEF.cxx +++ b/src/emucore/CartEF.cxx @@ -21,145 +21,20 @@ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CartridgeEF::CartridgeEF(const ByteBuffer& image, size_t size, const string& md5, const Settings& settings) - : Cartridge(settings, md5) + : CartridgeEnhanced(image, size, md5, settings) { - // Copy the ROM image into my buffer - std::copy_n(image.get(), std::min(myImage.size(), size), myImage.begin()); - createRomAccessArrays(myImage.size()); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void CartridgeEF::reset() -{ - initializeStartBank(1); - - // Upon reset we switch to the startup bank - bank(startBank()); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void CartridgeEF::install(System& system) -{ - mySystem = &system; - - // Install pages for the startup bank - bank(startBank()); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt8 CartridgeEF::peek(uInt16 address) +bool CartridgeEF::checkSwitchBank(uInt16 address, uInt8) { address &= 0x0FFF; // Switch banks if necessary if((address >= 0x0FE0) && (address <= 0x0FEF)) + { bank(address - 0x0FE0); - - return myImage[myBankOffset + address]; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeEF::poke(uInt16 address, uInt8) -{ - address &= 0x0FFF; - - // Switch banks if necessary - if((address >= 0x0FE0) && (address <= 0x0FEF)) - bank(address - 0x0FE0); - + return true; + } return false; } - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeEF::bank(uInt16 bank) -{ - if(bankLocked()) return false; - - // Remember what bank we're in - myBankOffset = bank << 12; - - System::PageAccess access(this, System::PageAccessType::READ); - - // Set the page accessing methods for the hot spots - for(uInt16 addr = (0x1FE0 & ~System::PAGE_MASK); addr < 0x2000; - addr += System::PAGE_SIZE) - { - access.romAccessBase = &myRomAccessBase[myBankOffset + (addr & 0x0FFF)]; - access.romPeekCounter = &myRomAccessCounter[myBankOffset + (addr & 0x0FFF)]; - access.romPokeCounter = &myRomAccessCounter[myBankOffset + (addr & 0x0FFF) + myAccessSize]; - mySystem->setPageAccess(addr, access); - } - - // Setup the page access methods for the current bank - for(uInt16 addr = 0x1000; addr < static_cast(0x1FE0U & ~System::PAGE_MASK); - addr += System::PAGE_SIZE) - { - access.directPeekBase = &myImage[myBankOffset + (addr & 0x0FFF)]; - access.romAccessBase = &myRomAccessBase[myBankOffset + (addr & 0x0FFF)]; - access.romPeekCounter = &myRomAccessCounter[myBankOffset + (addr & 0x0FFF)]; - access.romPokeCounter = &myRomAccessCounter[myBankOffset + (addr & 0x0FFF) + myAccessSize]; - mySystem->setPageAccess(addr, access); - } - return myBankChanged = true; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt16 CartridgeEF::getBank(uInt16) const -{ - return myBankOffset >> 12; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt16 CartridgeEF::bankCount() const -{ - return 16; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeEF::patch(uInt16 address, uInt8 value) -{ - myImage[myBankOffset + (address & 0x0FFF)] = value; - return myBankChanged = true; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -const uInt8* CartridgeEF::getImage(size_t& size) const -{ - size = myImage.size(); - return myImage.data(); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeEF::save(Serializer& out) const -{ - try - { - out.putShort(myBankOffset); - } - catch(...) - { - cerr << "ERROR: CartridgeEF::save" << endl; - return false; - } - - return true; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeEF::load(Serializer& in) -{ - try - { - myBankOffset = in.getShort(); - } - catch(...) - { - cerr << "ERROR: CartridgeEF::load" << endl; - return false; - } - - // Remember what bank we were in - bank(myBankOffset >> 12); - - return true; -} diff --git a/src/emucore/CartEF.hxx b/src/emucore/CartEF.hxx index 62a10975d..1461e954d 100644 --- a/src/emucore/CartEF.hxx +++ b/src/emucore/CartEF.hxx @@ -21,7 +21,7 @@ class System; #include "bspf.hxx" -#include "Cart.hxx" +#include "CartEnhanced.hxx" #ifdef DEBUGGER_SUPPORT #include "CartEFWidget.hxx" #endif @@ -31,9 +31,9 @@ class System; There are 16 4K banks (total of 64K ROM). Accessing $1FE0 - $1FEF switches to each bank. - @author Stephen Anthony + @author Stephen Anthony, Thomas Jentzsch */ -class CartridgeEF : public Cartridge +class CartridgeEF : public CartridgeEnhanced { friend class CartridgeEFWidget; @@ -51,71 +51,6 @@ class CartridgeEF : public Cartridge virtual ~CartridgeEF() = default; public: - /** - Reset device to its power-on state - */ - void reset() override; - - /** - 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) override; - - /** - Install pages for the specified bank in the system. - - @param bank The bank that should be installed in the system - */ - bool bank(uInt16 bank) 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 bankCount() 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). @@ -135,29 +70,12 @@ class CartridgeEF : public Cartridge } #endif - public: - /** - Get the byte at the specified address. - - @return The byte at the specified address - */ - uInt8 peek(uInt16 address) override; - - /** - 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) override; - private: - // The 64K ROM image of the cartridge - std::array myImage; + bool checkSwitchBank(uInt16 address, uInt8 value = 0) override; - // Indicates the offset into the ROM image (aligns to current bank) - uInt16 myBankOffset{0}; + uInt16 romHotspot() const override { return 0x1FE0; } + + uInt16 getStartBank() const override { return 1; } private: // Following constructors and assignment operators not supported diff --git a/src/emucore/CartEFSC.cxx b/src/emucore/CartEFSC.cxx index 3502be7a0..c70cff194 100644 --- a/src/emucore/CartEFSC.cxx +++ b/src/emucore/CartEFSC.cxx @@ -21,205 +21,7 @@ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CartridgeEFSC::CartridgeEFSC(const ByteBuffer& image, size_t size, const string& md5, const Settings& settings) - : Cartridge(settings, md5) + : CartridgeEF(image, size, md5, settings) { - // Copy the ROM image into my buffer - std::copy_n(image.get(), std::min(myImage.size(), size), myImage.begin()); - createRomAccessArrays(myImage.size()); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void CartridgeEFSC::reset() -{ - initializeRAM(myRAM.data(), myRAM.size()); - initializeStartBank(15); - - // Upon reset we switch to the startup bank - bank(startBank()); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void CartridgeEFSC::install(System& system) -{ - mySystem = &system; - - System::PageAccess access(this, System::PageAccessType::READ); - - // Set the page accessing method for the RAM writing pages - // Map access to this class, since we need to inspect all accesses to - // check if RWP happens - access.type = System::PageAccessType::WRITE; - for(uInt16 addr = 0x1000; addr < 0x1080; addr += System::PAGE_SIZE) - { - access.romAccessBase = &myRomAccessBase[addr & 0x007F]; - access.romPeekCounter = &myRomAccessCounter[addr & 0x007F]; - access.romPokeCounter = &myRomAccessCounter[(addr & 0x007F) + myAccessSize]; - mySystem->setPageAccess(addr, access); - } - - // Set the page accessing method for the RAM reading pages - access.type = System::PageAccessType::READ; - for(uInt16 addr = 0x1080; addr < 0x1100; addr += System::PAGE_SIZE) - { - access.directPeekBase = &myRAM[addr & 0x007F]; - access.romAccessBase = &myRomAccessBase[0x80 + (addr & 0x007F)]; - access.romPeekCounter = &myRomAccessCounter[0x80 + (addr & 0x007F)]; - access.romPokeCounter = &myRomAccessCounter[0x80 + (addr & 0x007F) + myAccessSize]; - mySystem->setPageAccess(addr, access); - } - - // Install pages for the startup bank - bank(startBank()); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt8 CartridgeEFSC::peek(uInt16 address) -{ - uInt16 peekAddress = address; - address &= 0x0FFF; - - // Switch banks if necessary - if((address >= 0x0FE0) && (address <= 0x0FEF)) - bank(address - 0x0FE0); - - if(address < 0x0080) // Write port is at 0xF000 - 0xF07F (128 bytes) - return peekRAM(myRAM[address], peekAddress); - else - return myImage[myBankOffset + address]; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeEFSC::poke(uInt16 address, uInt8 value) -{ - uInt16 pokeAddress = address; - address &= 0x0FFF; - - // Switch banks if necessary - if((address >= 0x0FE0) && (address <= 0x0FEF)) - { - bank(address - 0x0FE0); - return false; - } - - if (!(address & 0x080)) - { - pokeRAM(myRAM[address & 0x007F], pokeAddress, value); - return true; - } - else - { - // Writing to the read port should be ignored, but trigger a break if option enabled - uInt8 dummy; - - pokeRAM(dummy, pokeAddress, value); - myRamWriteAccess = pokeAddress; - return false; - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeEFSC::bank(uInt16 bank) -{ - if(bankLocked()) return false; - - // Remember what bank we're in - myBankOffset = bank << 12; - - System::PageAccess access(this, System::PageAccessType::READ); - - // Set the page accessing methods for the hot spots - for(uInt16 addr = (0x1FE0 & ~System::PAGE_MASK); addr < 0x2000; - addr += System::PAGE_SIZE) - { - access.romAccessBase = &myRomAccessBase[myBankOffset + (addr & 0x0FFF)]; - access.romPeekCounter = &myRomAccessCounter[myBankOffset + (addr & 0x0FFF)]; - access.romPokeCounter = &myRomAccessCounter[myBankOffset + (addr & 0x0FFF) + myAccessSize]; - mySystem->setPageAccess(addr, access); - } - - // Setup the page access methods for the current bank - for(uInt16 addr = 0x1100; addr < static_cast(0x1FE0U & ~System::PAGE_MASK); - addr += System::PAGE_SIZE) - { - access.directPeekBase = &myImage[myBankOffset + (addr & 0x0FFF)]; - access.romAccessBase = &myRomAccessBase[myBankOffset + (addr & 0x0FFF)]; - access.romPeekCounter = &myRomAccessCounter[myBankOffset + (addr & 0x0FFF)]; - access.romPokeCounter = &myRomAccessCounter[myBankOffset + (addr & 0x0FFF) + myAccessSize]; - mySystem->setPageAccess(addr, access); - } - return myBankChanged = true; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt16 CartridgeEFSC::getBank(uInt16) const -{ - return myBankOffset >> 12; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt16 CartridgeEFSC::bankCount() const -{ - return 16; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeEFSC::patch(uInt16 address, uInt8 value) -{ - address &= 0x0FFF; - - if(address < 0x0100) - { - // Normally, a write to the read port won't do anything - // However, the patch command is special in that ignores such - // cart restrictions - myRAM[address & 0x007F] = value; - } - else - myImage[myBankOffset + address] = value; - - return myBankChanged = true; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -const uInt8* CartridgeEFSC::getImage(size_t& size) const -{ - size = myImage.size(); - return myImage.data(); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeEFSC::save(Serializer& out) const -{ - try - { - out.putShort(myBankOffset); - out.putByteArray(myRAM.data(), myRAM.size()); - } - catch(...) - { - cerr << "ERROR: CartridgeEFSC::save" << endl; - return false; - } - - return true; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeEFSC::load(Serializer& in) -{ - try - { - myBankOffset = in.getShort(); - in.getByteArray(myRAM.data(), myRAM.size()); - } - catch(...) - { - cerr << "ERROR: CartridgeEFSC::load" << endl; - return false; - } - - // Remember what bank we were in - bank(myBankOffset >> 12); - - return true; + myRamSize = RAM_SIZE; } diff --git a/src/emucore/CartEFSC.hxx b/src/emucore/CartEFSC.hxx index 810b27b22..d204d1ef5 100644 --- a/src/emucore/CartEFSC.hxx +++ b/src/emucore/CartEFSC.hxx @@ -21,7 +21,7 @@ class System; #include "bspf.hxx" -#include "Cart.hxx" +#include "CartEF.hxx" #ifdef DEBUGGER_SUPPORT #include "CartEFSCWidget.hxx" #endif @@ -32,9 +32,9 @@ class System; Accessing $1FE0 - $1FEF switches to each bank. RAM read port is $1080 - $10FF, write port is $1000 - $107F. - @author Stephen Anthony + @author Stephen Anthony, Thomas Jentzsch */ -class CartridgeEFSC : public Cartridge +class CartridgeEFSC : public CartridgeEF { friend class CartridgeEFSCWidget; @@ -52,71 +52,6 @@ class CartridgeEFSC : public Cartridge virtual ~CartridgeEFSC() = default; public: - /** - Reset device to its power-on state - */ - void reset() override; - - /** - 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) override; - - /** - Install pages for the specified bank in the system. - - @param bank The bank that should be installed in the system - */ - bool bank(uInt16 bank) 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 bankCount() 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). @@ -136,32 +71,12 @@ class CartridgeEFSC : public Cartridge } #endif - public: - /** - Get the byte at the specified address. - - @return The byte at the specified address - */ - uInt8 peek(uInt16 address) override; - - /** - 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) override; + private: + uInt16 getStartBank() const override { return 15; } private: - // The 64K ROM image of the cartridge - std::array myImage; - - // The 128 bytes of RAM - std::array myRAM; - - // Indicates the offset into the ROM image (aligns to current bank) - uInt16 myBankOffset{0}; + // RAM size + static constexpr uInt16 RAM_SIZE = 0x80; private: // Following constructors and assignment operators not supported diff --git a/src/emucore/CartEnhanced.cxx b/src/emucore/CartEnhanced.cxx index ff4113ad4..0f0472392 100644 --- a/src/emucore/CartEnhanced.cxx +++ b/src/emucore/CartEnhanced.cxx @@ -37,9 +37,14 @@ CartridgeEnhanced::CartridgeEnhanced(const ByteBuffer& image, size_t size, // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void CartridgeEnhanced::install(System& system) { + // calculate bank switching and RAM sizes and masks + myBankSize = 1 << myBankShift; // e.g. = 2 ^ 12 = 4K = 0x1000 + myBankMask = myBankSize - 1; // e.g. = 0x0FFF + myBankSegs = 1 << (12 - myBankShift); // e.g. = 1 + myRamMask = myRamSize - 1; // e.g. = 0xFFFF (doesn't matter for RAM size 0) + // Allocate array for the current bank segments slices - myCurrentBankOffset = make_unique(BANK_SEGS); - std::fill_n(myCurrentBankOffset.get(), BANK_SEGS, 0); + myCurrentSegOffset = make_unique(myBankSegs); // Allocate array for the RAM area myRAM = make_unique(myRamSize); @@ -93,24 +98,22 @@ void CartridgeEnhanced::reset() uInt8 CartridgeEnhanced::peek(uInt16 address) { uInt16 peekAddress = address; - address &= myBankMask; - checkSwitchBank(address); + checkSwitchBank(address & 0x0FFF); + address &= myBankMask; if(address < myRamSize) // Write port is at 0xF000 - 0xF07F (128 bytes) return peekRAM(myRAM[address], peekAddress); else - return myImage[myBankOffset + address]; - return myImage[myCurrentBankOffset[address >> BANK_SHIFT] + (address & BANK_MASK)]; + //return myImage[myBankOffset + address]; + return myImage[myCurrentSegOffset[(peekAddress & 0xFFF) >> myBankShift] + address]; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool CartridgeEnhanced::poke(uInt16 address, uInt8 value) { - address &= myBankMask; - // Switch banks if necessary - if (checkSwitchBank(address & myBankMask)) + if (checkSwitchBank(address & 0x0FFF)) return false; if(myRamSize) @@ -133,45 +136,34 @@ bool CartridgeEnhanced::poke(uInt16 address, uInt8 value) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool CartridgeEnhanced::bank(uInt16 bank, uInt16 slice) +bool CartridgeEnhanced::bank(uInt16 bank, uInt16 segment) { if(bankLocked()) return false; - // Remember what bank we're in - myBankOffset = bank << myBankShift; - + // Remember what bank is in which segment + uInt32 bankOffset = myCurrentSegOffset[segment] = bank << myBankShift; + uInt16 segmentOffset = segment << myBankShift; uInt16 romHotspot = this->romHotspot(); - uInt16 fromAddr = 0x1000 + myRamSize * 2; - uInt16 toAddr; + uInt16 hotSpotAddr; + uInt16 fromAddr = (segmentOffset + 0x1000 + myRamSize * 2) & ~System::PAGE_MASK; + uInt16 toAddr = (segmentOffset + 0x1000 + myBankSize) & ~System::PAGE_MASK; if(romHotspot) - { - System::PageAccess access(this, System::PageAccessType::READ); - - // Set the page accessing methods for the hot spots - for(uInt16 addr = (romHotspot & ~System::PAGE_MASK); addr < 0x1000 + myBankSize; - addr += System::PAGE_SIZE) - { - uInt16 offset = myBankOffset + (addr & myBankMask); - access.romAccessBase = &myRomAccessBase[offset]; - access.romPeekCounter = &myRomAccessCounter[offset]; - access.romPokeCounter = &myRomAccessCounter[offset + myAccessSize]; - mySystem->setPageAccess(addr, access); - } - toAddr = romHotspot; - } + hotSpotAddr = segmentOffset + (romHotspot & ~System::PAGE_MASK); else - toAddr = 0x1000 + myBankSize; + hotSpotAddr = 0xFFFF; // none System::PageAccess access(this, System::PageAccessType::READ); // Setup the page access methods for the current bank - for(uInt16 addr = (fromAddr & ~System::PAGE_MASK); addr < (toAddr & ~System::PAGE_MASK); - addr += System::PAGE_SIZE) + for(uInt16 addr = fromAddr; addr < toAddr; addr += System::PAGE_SIZE) { - uInt16 offset = myBankOffset + (addr & myBankMask); - if(myDirectPeek) + uInt32 offset = bankOffset + (addr & myBankMask); + + if(myDirectPeek && addr != hotSpotAddr) access.directPeekBase = &myImage[offset]; + else + access.directPeekBase = nullptr; access.romAccessBase = &myRomAccessBase[offset]; access.romPeekCounter = &myRomAccessCounter[offset]; access.romPokeCounter = &myRomAccessCounter[offset + myAccessSize]; @@ -182,9 +174,9 @@ bool CartridgeEnhanced::bank(uInt16 bank, uInt16 slice) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt16 CartridgeEnhanced::getBank(uInt16) const +uInt16 CartridgeEnhanced::getBank(uInt16 address) const { - return myBankOffset >> myBankShift; + return myCurrentSegOffset[(address & 0xFFF) >> myBankShift] >> myBankShift; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -196,9 +188,7 @@ uInt16 CartridgeEnhanced::bankCount() const // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool CartridgeEnhanced::patch(uInt16 address, uInt8 value) { - address &= myBankMask; - - if(address < myRamSize * 2) + if((address & myBankMask) < myRamSize * 2) { // Normally, a write to the read port won't do anything // However, the patch command is special in that ignores such @@ -206,7 +196,7 @@ bool CartridgeEnhanced::patch(uInt16 address, uInt8 value) myRAM[address & myRamMask] = value; } else - myImage[myBankOffset + address] = value; + myImage[myCurrentSegOffset[(address & 0xFFF) >> myBankShift] + (address & myBankMask)] = value; return myBankChanged = true; } @@ -223,7 +213,7 @@ bool CartridgeEnhanced::save(Serializer& out) const { try { - out.putShort(myBankOffset); + out.putIntArray(myCurrentSegOffset.get(), myBankSegs); if(myRamSize) out.putByteArray(myRAM.get(), myRamSize); @@ -242,7 +232,7 @@ bool CartridgeEnhanced::load(Serializer& in) { try { - myBankOffset = in.getShort(); + in.getIntArray(myCurrentSegOffset.get(), myBankSegs); if(myRamSize) in.getByteArray(myRAM.get(), myRamSize); } @@ -251,9 +241,5 @@ bool CartridgeEnhanced::load(Serializer& in) cerr << "ERROR: " << name() << "::load" << endl; return false; } - - // Remember what bank we were in - bank(myBankOffset >> myBankShift); - return true; } diff --git a/src/emucore/CartEnhanced.hxx b/src/emucore/CartEnhanced.hxx index 22d253b6a..b8511ac29 100644 --- a/src/emucore/CartEnhanced.hxx +++ b/src/emucore/CartEnhanced.hxx @@ -63,7 +63,7 @@ class CartridgeEnhanced : public Cartridge @param bank The bank that should be installed in the system */ - bool bank(uInt16 bank, uInt16 slice); + bool bank(uInt16 bank, uInt16 segment); /** Install pages for the specified bank in the system. @@ -135,62 +135,71 @@ class CartridgeEnhanced : public Cartridge bool poke(uInt16 address, uInt8 value) override; protected: + // The '2 ^ N = bank segment size' exponent + uInt16 myBankShift{BANK_SHIFT}; // default 12 (-> one 4K segment) + + // The size of a bank's segment + uInt16 myBankSize{0}; + + // The mask for a bank segment + uInt16 myBankMask{0}; + + // The number of segments a bank is split into + uInt16 myBankSegs{0}; + + // The extra RAM size + uInt16 myRamSize{RAM_SIZE}; // default 0 + + // The mask for the extra RAM + uInt16 myRamMask{0}; // RAM_SIZE - 1, but doesn't matter when RAM_SIZE is 0 + // Pointer to a dynamically allocated ROM image of the cartridge ByteBuffer myImage{nullptr}; + // Contains the offset into the ROM image for each of the bank segments + DWordBuffer myCurrentSegOffset{nullptr}; + + // Indicates whether to use direct ROM peeks or not + bool myDirectPeek{true}; + // Pointer to a dynamically allocated RAM area of the cartridge ByteBuffer myRAM{nullptr}; - uInt16 myBankShift{BANK_SHIFT}; - - uInt16 myBankSize{BANK_SIZE}; - - uInt16 myBankMask{BANK_MASK}; - - uInt16 myRamSize{RAM_SIZE}; - - uInt16 myRamMask{RAM_MASK}; - - bool myDirectPeek{true}; - - // Indicates the offset into the ROM image (aligns to current bank) - uInt16 myBankOffset{0}; - - // Indicates the slice mapped into each of the bank segments - WordBuffer myCurrentBankOffset{nullptr}; - private: - // log(ROM bank size) / log(2) - static constexpr uInt16 BANK_SHIFT = 12; + // Calculated as: log(ROM bank segment size) / log(2) + static constexpr uInt16 BANK_SHIFT = 12; // default = 4K - // bank size - static constexpr uInt16 BANK_SIZE = 1 << BANK_SHIFT; // 2 ^ 12 = 4K + // The size of extra RAM in ROM address space + static constexpr uInt16 RAM_SIZE = 0; // default = none - // bank mask - static constexpr uInt16 BANK_MASK = BANK_SIZE - 1; - - // bank segments - static constexpr uInt16 BANK_SEGS = 1; - - // RAM size - static constexpr uInt16 RAM_SIZE = 0; - - // RAM mask - static constexpr uInt16 RAM_MASK = 0; - - // Size of the ROM image + // The size of the ROM image size_t mySize{0}; protected: /** Check hotspots and switch bank if triggered. + + @param address The address to check + @param value The optional value used to determine the bank switched to + @return True if a bank switch happened. */ virtual bool checkSwitchBank(uInt16 address, uInt8 value = 0) = 0; private: + /** + Get the ROM's startup bank. + + @return The bank the ROM will start in + */ virtual uInt16 getStartBank() const { return 0; } + /** + Get the hotspot in ROM address space. + + @return The first hotspot address in ROM space or 0 + */ virtual uInt16 romHotspot() const { return 0; } + // TODO: handle cases where there the hotspots cover multiple pages private: // Following constructors and assignment operators not supported diff --git a/src/emucore/CartF0.hxx b/src/emucore/CartF0.hxx index 07123bf34..fc8d62939 100644 --- a/src/emucore/CartF0.hxx +++ b/src/emucore/CartF0.hxx @@ -67,12 +67,11 @@ class CartridgeF0 : public CartridgeEnhanced } #endif - protected: + private: bool checkSwitchBank(uInt16 address, uInt8 value = 0); uInt16 romHotspot() const override { return 0x1FF0; } - private: uInt16 getStartBank() const override { return 15; } private: diff --git a/src/emucore/CartF4.hxx b/src/emucore/CartF4.hxx index 3b4f0f141..afe81d0e0 100644 --- a/src/emucore/CartF4.hxx +++ b/src/emucore/CartF4.hxx @@ -66,7 +66,7 @@ class CartridgeF4 : public CartridgeEnhanced } #endif - protected: + private: bool checkSwitchBank(uInt16 address, uInt8 value = 0); uInt16 romHotspot() const override { return 0x1FF4; } diff --git a/src/emucore/CartF4SC.cxx b/src/emucore/CartF4SC.cxx index 5364f2eb4..4fe6e13eb 100644 --- a/src/emucore/CartF4SC.cxx +++ b/src/emucore/CartF4SC.cxx @@ -23,5 +23,5 @@ CartridgeF4SC::CartridgeF4SC(const ByteBuffer& image, size_t size, : CartridgeF4(image, size, md5, settings) { myRamSize = RAM_SIZE; - myRamMask = RAM_MASK; + myRamMask = RAM_SIZE - 1; } diff --git a/src/emucore/CartF6.hxx b/src/emucore/CartF6.hxx index 275727786..2f183ad7b 100644 --- a/src/emucore/CartF6.hxx +++ b/src/emucore/CartF6.hxx @@ -66,7 +66,7 @@ class CartridgeF6 : public CartridgeEnhanced } #endif - protected: + private: bool checkSwitchBank(uInt16 address, uInt8 value = 0) override; uInt16 romHotspot() const override { return 0x1FF6; } diff --git a/src/emucore/CartF6SC.cxx b/src/emucore/CartF6SC.cxx index 034f02bce..15613a357 100644 --- a/src/emucore/CartF6SC.cxx +++ b/src/emucore/CartF6SC.cxx @@ -23,5 +23,5 @@ CartridgeF6SC::CartridgeF6SC(const ByteBuffer& image, size_t size, : CartridgeF6(image, size, md5, settings) { myRamSize = RAM_SIZE; - myRamMask = RAM_MASK; + myRamMask = RAM_SIZE - 1; } diff --git a/src/emucore/CartF8.hxx b/src/emucore/CartF8.hxx index 71e4d6e72..23863fa57 100644 --- a/src/emucore/CartF8.hxx +++ b/src/emucore/CartF8.hxx @@ -66,12 +66,11 @@ class CartridgeF8 : public CartridgeEnhanced } #endif - protected: + private: bool checkSwitchBank(uInt16 address, uInt8 value = 0) override; uInt16 romHotspot() const override { return 0x1FF8; } - private: uInt16 getStartBank() const override { return 1; } private: diff --git a/src/emucore/CartF8SC.cxx b/src/emucore/CartF8SC.cxx index e9dd5bd5c..98ce4357f 100644 --- a/src/emucore/CartF8SC.cxx +++ b/src/emucore/CartF8SC.cxx @@ -23,5 +23,4 @@ CartridgeF8SC::CartridgeF8SC(const ByteBuffer& image, size_t size, : CartridgeF8(image, size, md5, settings) { myRamSize = RAM_SIZE; - myRamMask = RAM_MASK; } diff --git a/src/emucore/CartF8SC.hxx b/src/emucore/CartF8SC.hxx index 9ec8209c4..25fe0dfe3 100644 --- a/src/emucore/CartF8SC.hxx +++ b/src/emucore/CartF8SC.hxx @@ -71,9 +71,6 @@ class CartridgeF8SC : public CartridgeF8 // RAM size static constexpr uInt16 RAM_SIZE = 0x80; - // RAM mask - static constexpr uInt16 RAM_MASK = RAM_SIZE - 1; - private: // Following constructors and assignment operators not supported CartridgeF8SC() = delete; diff --git a/src/emucore/CartFC.hxx b/src/emucore/CartFC.hxx index c1987a189..b08f2085c 100644 --- a/src/emucore/CartFC.hxx +++ b/src/emucore/CartFC.hxx @@ -87,12 +87,11 @@ class CartridgeFC : public CartridgeEnhanced */ bool poke(uInt16 address, uInt8 value) override; - protected: + private: bool checkSwitchBank(uInt16 address, uInt8 value = 0) override; uInt16 romHotspot() const override { return 0x1FF8; } - private: // Target bank defined by writing to $1FF8/9 uInt16 myTargetBank{0}; diff --git a/src/emucore/CartFE.cxx b/src/emucore/CartFE.cxx index 1837c7b88..3a07730e0 100644 --- a/src/emucore/CartFE.cxx +++ b/src/emucore/CartFE.cxx @@ -37,7 +37,7 @@ void CartridgeFE::reset() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void CartridgeFE::install(System& system) { - mySystem = &system; + CartridgeEnhanced::install(system); // The hotspot $01FE is in a mirror of zero-page RAM // We need to claim access to it here, and deal with it in peek/poke below @@ -63,7 +63,7 @@ bool CartridgeFE::checkSwitchBank(uInt16 address, uInt8 value) uInt8 CartridgeFE::peek(uInt16 address) { uInt8 value = (address < 0x200) ? mySystem->m6532().peek(address) : - myImage[myBankOffset + (address & myBankMask)]; + myImage[myCurrentSegOffset[(address & 0xFFF) >> myBankShift] + (address & myBankMask)]; // Check if we hit hotspot checkSwitchBank(address, value); diff --git a/src/emucore/CartFE.hxx b/src/emucore/CartFE.hxx index 18361faf0..3132704cd 100644 --- a/src/emucore/CartFE.hxx +++ b/src/emucore/CartFE.hxx @@ -72,8 +72,8 @@ class System; particular *why* the address $01FE will be placed on the address bus after both the JSR and RTS opcodes. - @author Stephen Anthony; with ideas/research from Christian Speckner and - alex_79 and TomSon (of AtariAge) + @author Stephen Anthony, Thomas Jentzsch; with ideas/research from Christian + Speckner and alex_79 and TomSon (of AtariAge) */ class CartridgeFE : public CartridgeEnhanced { diff --git a/src/emucore/CartUA.cxx b/src/emucore/CartUA.cxx index 4fd5a5410..74ccc3773 100644 --- a/src/emucore/CartUA.cxx +++ b/src/emucore/CartUA.cxx @@ -30,7 +30,7 @@ CartridgeUA::CartridgeUA(const ByteBuffer& image, size_t size, // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void CartridgeUA::install(System& system) { - mySystem = &system; + CartridgeEnhanced::install(system); // Get the page accessing methods for the hot spots since they overlap // areas within the TIA we'll need to forward requests to the TIA diff --git a/src/emucore/CartUA.hxx b/src/emucore/CartUA.hxx index 66fe82ec8..94dcb4a98 100644 --- a/src/emucore/CartUA.hxx +++ b/src/emucore/CartUA.hxx @@ -97,7 +97,7 @@ class CartridgeUA : public CartridgeEnhanced bool poke(uInt16 address, uInt8 value) override; - protected: + private: bool checkSwitchBank(uInt16 address, uInt8 value = 0) override; private: diff --git a/test/roms/bankswitching/BFSC/penult-demo-9-NTSC.bin b/test/roms/bankswitching/DFSC/penult-demo-9-NTSC.bin similarity index 100% rename from test/roms/bankswitching/BFSC/penult-demo-9-NTSC.bin rename to test/roms/bankswitching/DFSC/penult-demo-9-NTSC.bin diff --git a/test/roms/bankswitching/E7/Bump 'n' Jump (1983) (M Network).bin b/test/roms/bankswitching/E7/Bump 'n' Jump (1983) (M Network).bin new file mode 100644 index 0000000000000000000000000000000000000000..71b4048f86b035c7030f0ae55c94cca49b90c0a3 GIT binary patch literal 8192 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 literal 0 HcmV?d00001 diff --git a/test/roms/bankswitching/EF/Zippy_V2_FINAL_NTSC.bas.bin b/test/roms/bankswitching/EF/Zippy_V2_FINAL_NTSC.bas.bin new file mode 100644 index 0000000000000000000000000000000000000000..3ca1a12aceffec9fb2bcb6a9cb6ab1491e3f4147 GIT binary patch literal 65536 zcmeEv31D1Rx&PeRXEI49lSwk!nrTU~l%<8XP_d=04|S~fJqwgYWC=ca0&Yd&iMX^; zZUsaPrA5GWA@m{_6(gGv#ckZ2;WYPknzW;V7 zx%Zs!eCIpg`ObIt&2kx=jUi)$tM>XGcE@Q~jj6o}CbeS7NOWcJ$O01HURhYW_#dZDB)Vc%FR734J_3&HAXVVv!tZ-Kx zGjHIm^{*7y;{nlrW5B9t+uB-ab{^$U2M~{D{pb=nG@BcbN)CJUS*6D;aZe4_Z(QVs zV?Qj5l*MiI10@KQ^GeK(^P4=nFJ?vIo{j&!ns=S$T#bqWZ|xsL&h^(RbRD zJWvYQL>10Bvl~Gtz;hg-%ti2pkldNZ945E$6?1cF6r6X_I}+)3ymA4p2ya-C$&sKw^NnkgzT$Y`efE3CLR%NxShVu&!IB zP_^ByMS>O-wB0rV%6^l0aTLZ2QeMkTu~4^eq!g%ZpNdlrtyJ5RRsp4pY*jJ)k}p-k zB>-*qBwaN~7#CA76hzRUit9h6E@+G^^-i&5+WG*@0618w9~C#dXYSODm6kto$;!kf z&Bk_08$s@oY(i~=HYBf(H}q{Ek#W4pI2Xq)KQc~p+xAwk39qI>MKchzHLJ~NEwpB| zt!oXK+h^`{8`~S&w@VIzjL0GwdIxV~W-ojo%mL96rDEM3o^;sU)S(SKcDLBwwhDGb zPwDQ;*tHSvOdOB?beXxN_!@jOGJ_S-{t~ zL;shu_8sPq4qw_2iR|d>f*DyHskEcL3x2Lf&gp~_cZ!Em8dkiwr>gDJzz;hO*oQ0XL0~^7P5poVNQ~r{n!_>v7=TJ3PtU+7WD> zfM9xzMwGu-qe}Rqu?JMnNm;+N=@!sG@ye~mZFfVoMn?tMr?|*qw4fg{0V*{O#-O*FRR#-Vbmv9fYR=s5_Kn zev_|#yMAFspHgV%V5qVcVm46W&2I|Znyvq!!p;@oFOafY^3p@Wf0GBd)*!Jx`tRYt z<3<=JSG3<^-qO+4%Cx>CWi{AwXGLz_VyD>E#`RVK^3v9hL@RUnU3xHKwHkN9*xx?T zq9N9+)=~x=te{jfucfRVHlZj8DflN+)}E9#BL{B#9B_PIma@k5*_h`O!h;-}zDo`i zb#pA7alqLI_gN|H2X?qjLbIbor z`>9$@3jvP+F7%@?2>k>MLO%(E(4Pc@(4Pu}(4Pr|(4Py#DEiGEbQPK>!yq(g!5}or zs?elIUxPleiZQ>tdEM@+)VepSQVrGF$5lM;*jb-C_p+U968f*3XJ{onyURtWP=CZ-}+cv0f+E4#)aqu|DTm@1~WE z^`n92K?5ydHqYFN%GIh)S$|0(et)P=t^0Fzs)3DLwr2IVgJ?_RO^mnM03HCwUSQO! zW^di5vx>i4|LKM+fFp%9sBcAO;$(#TYO>a}M}NIWN??zEU9F^W9rIjkCGuhJlTv`wO4q!6gHs;UsFiAR`J_Th> zv9W4R82M0mcs{SJv2K!z3H-g9S8S4qKYgl%vVk|{0@v8U`*VR$ z+rZ9T;0_z;0`Luo&lrGjfY16|;E6Ucl?yyq0og=J7q)yc*XcX9MIyO$1m11Ac=zVw z-ERl-TN_wR3H`pbb4r|taC#w)vu!vXfY-F;hVN#koh-L?g}nNBv@ zYf{#EDeH}#g2Gw?oLctUZIqRHC}=|OM(=qCI)@QttRCyTMkDHaj77Bdfc#Q4ux-Da zQ`T_G`khUl%~;?pL%(O?4C{urYtcM4^Wssv$g9zsll0K!KEVSf%n zeIV-5FF3K@y@10+1Nt>5qYXQNoY2IY^c|y9|Bq>AXGlZ$54j^?E>B#h8OyB$ZOhRO zz)%*3bv*jVrkh((+=tP-W!C~jYbaS<=`hq+_4i+-=b%{KeJ?^aUW9Ki(2#LF`mya|n3xgu*QbkS+X&xFS&LFufsGJR z2%_=hrP*Pl-J7zmNLgPSg~psfv_z|o_FpM$S;}g&(F$#99Kd720DfLeSvRN9mr z{HMKr*S22|6MFRR*Y?rmAxumdGi--zLnip14sG}xNX(rsgjxlM1J=hdj(63EEjQ#9 zfUd;YR`ZjTQC1(LyQEnTz}N z|2om^>Y?Q|_M zeBe*8s+a`BabhKjg>s=6eY}zG7}9j8+BqCG+TQV z=hT9`5g?Q|CNT999y=b57&B#@IG8L6yHcmIA+T4Ovd)k)#L-ohU^I zfObO4`h<r(g)j&rty~3{6b()<~6^Nnb>+8W~MnU zJ2oO6t4_qE6N;=tfe^ovtwcGv27Mny`s`tRm~6-(QOvH~j_g!qCjnuL02?83)^a@^ zk#wX8Q4W_ z{Ih(atU$$>06u0^fKyJrP{le6Ye0@qSx@AMS0`wMpkmSy^bOWFhF$xMMY6>zK$|5 z14p`K;hbJk*^m`YFLD^&hUC4~=tV0tdCF`l=X{X+(wV&j#!^JBI%WMSFKP$RBrZGH0Ggps&Jg;67Izfro0FaV#Q7e+=;;d*IaNcEm*)bgTW z3fobjJkNpp*d?tVnFT5LU>0#3^!WhWOssP8UN3Kqe- zWrRQlttC`eJ35|Cb6mYSWzDsntgm<`}9qb46k$Q(IoK82$qsJGD8?>$AII6GN4 z2ax*SsUh^9zi+*1T-Si^voZks9i&w-U5|G(Jqzz-dLbK3REMC^+fupoMtJ!ll;}qo zALHbdoxq_eT*r^$DhZESx!#teM!8Z=pb#y`=fV#g`iE`I=$N6;AHy{p`e#SEI-!3t z2j22+C5RTo1~Qs0KeT}>a)5|?)4#W@v|WB-yA&(Fnm*aG&USfjl*^Qsmu;7UQ7&bY z9Q%yxFn)2PwGT2TwU#8%37nb(#01HXraDN^J?u$-Z4T@ABb`K2`P7{bw#>DCE*R~z zn7Ce)qijwQv%$t(l!Mu_#0ECy0@v8U2W()BEVe7)yY$ZAeIVci_y6ZSKF91b`ZLXL zjDC&hTu!E^*c)|1nNMb)a*f7TPV0_$r=60;xo<7;cz%*?m+K5{F@zj zB(6epPdXC*>}SV<5Brl}6gWE%#7`g>D*SJc1qc330%tG6 zpIj|wPKZD@b5j=TlZ)))gq6M7%RzXUJ{-~#|Ypf5kod;q`)0E`0oYXE<(Tc?@72JqJaCINg5 zz{m6_PBR|^@Ui;l1tD`iKK)=&_PBVW8(C^HZWbqlmp3(Q6 zWWG7NB@D!S1iisXz#lz-kLMUR=j+;Qar%NRJ?q+Qar#jiU+fX_+v;8@gGz1 z)*`Le>VG=JT#K|?3niiA-2>n~`t~!-djPx#K&I-?0sOhX?+o+j0R9|6Dqbsqt$N`? zvlYNr0I7Hn0{EalVWIgTfDZyl#d`$6NA!~xnvVeZh$!CU06nhHUuZrK(BqsN-A4fsBMmJNVG0KY!l2EZVI3Fp`V7z9xE85;nD02&+s3@aP- z2hMUikJqJs0hjvuUGhu1)Gz8)Kjk_rusHCQz$XHpfM)+11HTKr5;z!`W&7XeQ@^N7 z{gwpmpKA$zF4u5C{oV|y-!p+{0$X!_0k6la<@~O5@fUOjz4ni0{MOsQrhxiI1NP4! zm7f#6eF61z?9Nwz@NCCu3&5))j<+2ZhxI1|>X%{5uc==~1dRdyp0a;k0rh(|pne*n zB$0yHD7fE3WWU4P;|<{F@qRDxef+)`sPn4d9IyI0qMzzjzeQg4OM2DMiP}MkpFdJ7 z#!qvp-#nN4IZ2_p)lbD+uRA9r0Ne0RK>gkhsGk#nlV;lj>gNc=5s)LWi~t>-vgzuv z&)6o{dzG~lloPbWiWAo-z3R8g`v`uUyo>GMCEm;MyF}zbSrq&2U!Yii5x@E+{Pr)9 zke|y|FYxK+Z?0GUB3|`Nc-1fBQon>t{pPyVZ?Q}LoOtTz=SNuxyWC6cU(&69%oqHv zv42TB=VeG;dah&qTczhn&C!fQX|hNRQX8#_OEmRsaH-!^m-;n$)z8sTM@m!u>gPzu zk>*8S^;7w!G4P~I{pKJS1d!?L@Lvq`9G3?=rMc?xUktPJSQ8DdV&cFumQ$hhHPEBo zg1T*T%36`vmE;i47Q<-YVs1gRu!IYrOc}^QQNJCfzw-B7o-C!CC?9qo1|PZR-SBU;V5#-nJeC>)P|IqiyTQU_IeH>r~r%CahmP&su0(8(@9* zJnKT+`UO}M=UZQ}t>1?AW9M5xw5@l*dd>M5QBiEHg!Px_Tg|rhPFVkZzGd3hU&16MM0ltmlMKQw zWYCs5G|T@6mi;!%tIrdb1Gy~w;I?Px&IyzV;YieDu+k2rS4O2R4o2B12bC&x1rrRn zAU|qkX7!vKq;Fao?7gY}S#1b%sL;K6Lf5Nb(Q4wNJlPccc( zAr?D?k)9&&&6AUQZcy6O(VRnkB-?RuU!CV3L9{|f#pp!L@TYjwpSi$QS6f|KQC3<~ zTzIe`;0ySDUXNQl=yH4g!Ej-5NoiTUG*%KTjhB@j^p<@Zk6Rd|t1|<92J>LBNWm zCD9TF7{udn6Lt((?V!d0TpUr@!huA6`vov~FjW)9O4Pv%pnc8e%P3AT%zPm%17>p= zkb$llc5FCq8B;mWy-Zi%W2WMo8gd{oklT zLG;#0aq`Wr2;D9Y+}kr)yL? zq7IEt+fNP@l>C6QFBb-G1@%5H%FCr)Dpw$O^tBgo;0?|FpZZ0@5WN`HJ zHV3i*_{J;rAIWm8B^@PDP!#V_P=)Rb296{&TvU6E?|Dxa@??aC{!x~izC==dK^Tlb|M7wn_oFBr%l7~8$v?k!Z4&z1ZVCKYydpL*3N8!Ll zZTZog1aD7anKYwUBecsAJa&N0$8vWGBpa2#2Pf+w4z3y-o1ceT>m)La@j8sMF)|Wk zWS>lo&DJA?X-e)nCsD!lKZM_>M5JR$jyygksq&nYDqsRFv;Ty{-$PW;ItXAcwQfFI z8v&y)n-pzei_7jtC+Neil*$vW)TiId-jzIkdQS>-_^`_*Y@4rOE?*tsiz`L*`TC$r z{BM}0)0p4>cGk*;HJj~FF53@F_IaX}g}`rB_L24y%ZWK{?|w9T^k1dvGyf|^r5t$j4AOjCc{B=i32T0#n+E&G{?Um z{sRC`$PFogWef(^ju%43R}-j+bR zSe15Pe$_`L2nt+=*rf#3z6Sm6|5W%}^6^>ivU$Lzb34DqjQ?2_%G@_~(l@0{rVYWW z>3FMXEqB;`IAtA`H>;lxqqK1Fr0fLdsOl5Z51~Bfl5);ak9OtkH}`$)|Y6X@;zcd zB-oSQBle?$J@q|euM_N^W%}+3JsdXsWZkHK**DET%v|+% zc+eZ0)AkE!;@iyJUa|hlvHHaNkYn|W^-;$f5bHX}iv4LI^^{``iM7qKhQ-?9SUt$s z)EKB97kC&>@Pz(i6qIEE+ifFp{Q`Yx6f_{vM@K<}0$rC2Rn_bmQe>+tfW4>#`c2%F+P~DnMmH)n$dnf{06pAtEJ^-^BKX zrZP05#}Wl98s>eq6%(thKzuv!W~Z!&^7dGa;h#Gr{yzJl?)wh-{J(D>l(ZlsmIs^g z>@Bsb=tM2dwPLca4Rg~Qu}p()eWqAbDbg{Bgm~WitMMwTB*f(@Q1p@k~_4r2y z^nv^TiyvQ{iAk9;+;X%&yGc8EP{XjTCvfm!VDQul14CcBci<~uy7x;{zWn*)zWkM; zFAsiYXmIe_V>|!E!-^fS2V7;FygORd~qWhS${<%>uBSqmPeM}2=fnN z{t*@gJTqKlZ8y~J{e#9W#&WDJb%GP@P~*x<8V?CD7>v za{-4M6(Ds_@{`Ljvdk^vL)OR9O2QV@nZ29PyuiY#O~%spP1qK!q3^C?do@hy5SdD| z%Prh_lGomh8E>!IYgE;DM)|D-b6))Wkn6Dh0rTH`M(%wa>HhC|?tL#n>s~;ZF5?BP zH7k^W*#CM3q3SO+wd4GRwq7*GTe|Obz2Qo`-_S5bxf(-b#L^*zl?H`tfl*(0jGmA_d|dAG#6E9}M7Qd)JD@yWgMjP7lQy@7@xxH^YmX zqCl`Q5({`^!Z=W1qfv`R@bPG|Fg|Ye(bNZkcks{8L9PYgK<&6VZx9VGA?P+{F<`n& zTH@TSy-?PcFNFZ3!vS;%y7c9@AU}3NfBQ{YE{j4a*jk;uZhogW9Pn?%r$4E}`Vgc#DHs)yWw)!m+Y+-fhzn1ZLS4 z=2q-JWTK*jY>vhFyiASQ0Xm!BYjW1wvo49Lr`nnz|fKl1uuT zJNa4K-Vf)2AcV~EMnnwQWg1Eb(Ix?qjm`WYmbEq>C3Dc-83&91rAPeD*b+ z@xcV_Q(>n_z1B810F7YZ==cdCKN^EaHf@z4*;2sHBS8F3&y8q1^mm-t^q7#NYhTkZ zB{)SMgN!WZM3DR}?dv!1>S!HKAi8(8y4&x9jeANQ?9uW>yX)$vb+#g6eon8w75fF3 za9cH7G6<>Dz_}A#D2^vfVP?4`KgRvp<(fkIS@ z(2vXd?@>i4Gfx%%HyVSo`<^?{oi&|gf^%Yi%*0@&^q;uVYW=OLboee1wZBhw>gLwdIau+ef@m{$cU9l zk$#n|h|ecd)+t=HZrzg;E9mKTG_&u!m1Kp>2xg$D2SL%pjo`InC0BIFJRXjOxlKV< z$~qA^t#S;j9m5(RuT=AAn2ur*oWR7sC*8Ew*VW?g~2a6NysOI?Z_!@gK5c4%rjJ{Ok@L0 zTK&=6`cPtEd6~P9E1^>xdwF9p(xw1?K4oEJ^MH_9s(3_M$dr-EXHOk0TOE zjx#s4y^ICsn^6F;`~J&p(NP2(Kt?SkHa9X?nVsBg)2T1K-Rz`3VU!4SNo-+kgzP#+ zfi{|*MkRMye-!xJ;EU1Q3j0;cr1--6xAiIO7@IFHz66$VjP}o{8aEmP7QtJ!%+-|Qg+#<`o)cV5E9rpUdEf^JX))$VuL#;0~x5&~l zwZd@P9cqOkfE-g%YYbC&*lP^8WY-v;cZXVIi0L0Rmz8e)kE)bC*ya={sE4U!P%YKI z{8WnDOh%PR=78wWHggbqlOA1XCofoyi1njb95i?$7SF3OP!xFkjJ~C!t)0+ePqO_d zs8RZ`4Fr0?J$xHc7;ltzr`OzwJiZw%P@1X`99G^pJ&VkHu<1c1G^~srl5r$t-tXB; z!Ob#IiQ=qk+8fn6$pgxkYgV%SBAK+tC1|j#vIYiK*1(|38W>R4fUA|58CZ#iA!qDa z?4fO}kH>LPC>791sEvS{mBNbZA z2Op)rNHt%74-;6_N)O7C`?rki&??b}OYsg27q(1WdLt6%hZu3jg7=T-T0Vn5BCxSl zKL=y60>ZNwo|ol1nYorn(9wJg9nB7OFyBH4Gl~x83pT;sHX=4Vh+gbhdciFLP1b&R zIv$?(+nzoOPy4~e8Su0pd~%0G%f&Y0RXK=DZNwkvBHm*oKAMC0dmHh^T*Q7Gk%t?+ zji7O~LQ4#YZzJe>Aij;DOAz$Pj{SSIec=B8>c>~#y>(qdYieC}YpS7j)H8YdDXG)X z`;SG}Tzk`PEkA$wNfai3YQ<1p+d-I~`jul^e!pY-{sWHbng<=zmmYFV4G%k}Gk)!u zW<27UCOztyqQ7xWp2r;1JsHza9(Q0DtaD7KJs~FjiC<}%$K#$b!R>Z?w1R?yFO0|2 zrFq=leXs1;v2zz)JX%*5-3qjc@^r!KaTgXH$ET3~cEio*^L?y1il-zNi%pw8eGi`a z5fhG`aKsV7-KU~4oe7`u5vJ@4et>k<;O$FgcnS;4%APM{dgCcStF~4{nlsVHA2DIV z5fhFWKmLer3GS%LG*6hIL?UXn?U-X2b40zIK9Pd15eYqgdOVKE#pBQ`TUGXnW))Ck zE{3Qc{p$~A|!J@k_5(UTMjw>@`qI1@t!A&a)1vp zv^$|+BAt2oW*%)G^9?;+#FtiETTG#1HA@kM>iM0=Q8)U4`#k(-JvTDNm3J6yDUDYF z`F>JWSy@#L)q3NNH@e^qtzaP@|Mi4_rX8AEJU&#rN*ri=?rmSYA+o9Foz9SSh8X&9hnif~2leCZ^)i ze|d03dLzlfn2!`W;Kz?`G;1 z7iS_T(t{dM5#|Yl*;mCz5NGE1`4A|%9r-YJ@=z!!`zVtUQnVwgpYX^?kB701Q*OSG z`Ft2=yqFrHNf_hlkS!E_rm*Fys`7|J*pg(ymQllt6fN}zEs7d1(v(Nh9WUCHQy`!| zf-^M-a-vu>T1CAgSK>R%i_4`?eAI70kW{5SDh&HgNz79MtgM8Yl_s9Od*67Yyu7R| z9*f1l?)D@SMAx)H(bN-8n0oAywpM9e1sa}ZKVhyNEgY^w?OPT7QXuH{?ca|jnS6mT z6++VsCm2$}f=%)15BMii zUL$$XU61+wg;9z-Mu`^M=GSN@c1_LOZ`ah2gf_3Ta#0)MGS&9ALZCX+6Y1Z)StpUa7JAKiS6)N-ER z!yF26k#Qh^^zH4jMJyhMt^!J$}H((l+ovr5G9d_m@m>{3s9C7qa1LSeFK zHbpua4=q6H*}i?dvk-cOZs@l?p5d8GrO$@qvuK|&y|xxYP9*jn0Hg-V5{qxpy841D z8!%OT6MepMDoYd<_4O4MRaX}k`Teo@a->a+Nmh2H=JCLSJ|`3G!B|Z5NVy@Jz@(2N zW;JIQXy`d>Wk``3)2Gj#K7&bD+9gZ3mFR0~N=pk-ffW@ZCEZ6HafHVsd{APNQ%g&c zfWw8=)sSp;b&Zo?vubLptMk(3v+F3noQGmUCfc#%Lr`c*7qw(u5`a?9WJi~aDYygq zje2j(7otYugaH-W2PJcNMi%#aF+FBq>VGeBXz;LV(b7KRg0|694fMg#1*!7Or zF~87rWRlCBd@>der5}QnnaM(z0=)nxC?GuHfhsXOy+Sf$`5qHCAGsO$P#A9}x;V%O|flws6+={`KE!Y;=96@jI@Ai;51q7B~LL zb+Bp3wYX`qYstYSu7lVq@eX9?*c%V#AYXLzO)hEblG}q#+r;O0Zfn;p}#7aY@fUUW=XyyTe9dD$_2 zYKxe7OrQ*{26$K?2cd&lXE{@hde47l)b%2irQzOPz2N``3nzws;UHTse1he^PBgjz z;t!QUyeJYWJVt)~1Nik1qV6DQlhu+Qi2?tYDxZ>;nI`LM7DH}iy=q&{9Yea9n01atj^gTupv zgWzkZxGWGrD>nP+kN?H-JH{9MVcXykHfcYfw{-ACJn+jiKDZHwXI* zp%sx}xmoT*ZCI`$upQjGG|{JtmJIa2IrwI@WT+(S?nb8r7)Z)8Z6=ETBmExFM6~DM=cM*evj)U%w$6|FKNkMH=>B`j{Y&{Bk5VmJb`|1Oi`f9|+8K z`95n10+B8j$Z?q@z6GHS_4-dftYzIf5WjcTHqc2AFz~ zibnAxp&~x!O)bJ)I1ow1$^vD81_B~Rpa&Egz*Hv>i5*mVB@A)<99g?u*Hl%xwDMac zNEJ`8tg9=L=;{K<6}%Lo;z!MiKO!B=1lp&sl@;|PIfwBe;rj<2P0<1$a`vjlzFJiD zT5P+g$J^`c^S=U4RASN(@@9n5bs~wc{x6-G=#Mmr&_xJ%&7}cdL~Xo06wM(YCc7k+ z539e6A`NGT!)OB3d+x-ZV%cLseaa zBFg3J?#{{|C7*^4KRlj;%<$H}t}gy3s+d~wMB-|scLJh+{q@)Jj+I5DY{?+;Bx!<^ zPCgm=8AY;`ja5}zLQBl6s*(~_@=|!mj%sQ3ZrirM8s%!)l(HCp$p4A>3GqZ6s3@qr z_U&`K6AAv`mOzml2)O)JJF0Mu0Fo|ob-XBsMpbafb_@)4y|n`cyYuLO*qDv~p-kFS zDyfhVkb|TP{8NM!pbz=WFZpb2|3ICGe?0KP)I^?EsR{qE_*B~7nf0wdu}OKr8x;qK ziY9XKh7bC*lP6D^vTK*u+tn5C>hgNg>q6H&I51EUDHs?S>Vl7w(lgLRN0RwVO0FnD z_i{YO!iz{t5%Eo)469c9I)@fK?(tKK3*otN%9P2I$HPBH5qey%k`iAcu?O>D9v7>y z{najfnVEr(ob7VGT7t*}=a$OK5ItwDGEb?~Zs z>{mMZTcWBrZ_dX5J(+?*57Pxb92PPR4&k|6(I{W|2kau1Yy^+}(4#zX9$^@XE-e96 z;71;Lq~GgyLl{bKw&#nG0P#5UL@f3ioDeV`+MP%L(F;do zd|^C*>Wq)C@0C~lJ_)a|m_Dy6>pJlE0jB;*_QTXI8D`iBdfT?0vu4klG9J&ADFbK` zL5qfvySu4bwm|sNzxkpQ`%&Mz zarO_%hwa4Pa7`_`;PUl)qhKl;<@x>)$rU9dnoFbT1Z!r4ZTZ#IG9!9>G|?|)e&l=f zffu4*48~uJp%9=DuPQ4pt_g+U<*1`-(058CDq=r9To4jsu;$w}wfu!bwco1IT-C@3 zSD?_zvZH_bC6&5NM~t_KGKCSZ`D%C#vW5o;rKpy{1{SI=UfG^N@erSZP^b_ixn(O> zfMzr*$y>!Vt_r`6gbPP}Gv#68B~ZL!<|&|IlrLO!;O)9lEo3lRG5||yq|>VV84E*^h9j$PmQ=NC_LaaeP#_4tFWd2V zQgfW0gHSZ|_P+AU2`5Yk-56E3Y%i5iY&44zFUTYC`6ARNAMioxisV-DrAAYa$18bM zR4^+g5~V1{3XeYe*eO#A!@K)240QbQ)22-;MNE+fWp0lTGYMYzQ^BB$OqL-%wMDWm zctvS~q!ZPehawKqc=gr&`=e2gj!>eK3FG6(m!a~}G~}Sc0X*m{48Whtc52}Jq>2g* z+W9y>$pJG^-V;P_LYir;x9~2**4}u$w6tq)JfPLM-P^abJeC~zYKdfVrmv=Tch}Tz z-yYU_dbBXUVP*o36~k>F8=$BIN~t&~Ndns2m_)*0TH)YOVO3QjxEmZo5|oueR%Ku~ z^XPS3v+;K|vb{lZLXWyNmuEX>414hPpfdX$!huN8N+2`HU1f|7`%>DSBFG#%AKW;u z_9YFa75GF5y$BImlEG_bC8&iM5v?rdij@ZZ@iU>?WGOd~W;@&e8=5|F|9|V_Te0k= z01G?a!@Q&^iN$C^Wydl#*oT$96RW_u*(IXS7>0+4iIcU4zl>9edzvn_+*nXs2uLY* zy>M}!HH`BWoBSqETbxFt95p;#h$HpC02H1gzqtr7~BTn9!d)x{Z)=cRBIhFWzdvdxtg zBLz0RShps}{VEyY<`GFj2ZR&0d8FlLoWp|?wy`Ip7u$L%FG&abdM1K2+xfhAxG1F@#n>v$gu1&tx( z6Kolgb-mmg%S$Gv>=hMqcFG?8hOE~0=x4o&AbPMUuh-m+BY_HF&Lkl7#3%OHe2Ti1 z0wRVu44r21a3_ zk)@4C|24_@o4zaza(Q;B)uSK4)uiCp4{Kr5xqKtQuMsl}16&Jj*J#r$D{7nLO;fE> z+g#aH3*;)qs1TfuQ|ZaXCCS>RkAvAB{f<}ZO6o}SOb#l6C8I1QU^kOsF<6dNq>jTb zPzbeLyp!YT8&)OA=Xfp5c*XUrqGgP*6MM@GY(|#{ZEbi12W=@U?h?~adDAMft#M_o zKqTD8GPUjET5KPnC`!^r9Ft{<7s90nnOyjvDeI^Cxv&nqNmQ0DXHJjIpN}S1u5UY+ z>py=3TF_(XXwbsC>Crh_6&(Q-9lkfQXx}0HAt^#^!YFFL&VpdHXQsR=Pw2T+1Xu*Q z&4G;0c+;AqM76ntS>T0zh^D*gY^;S9bO1FmdYRKH}ft=QOLtOSOenBY`yDE$+4XwFDxu|L>@eS z=4v_rW|Tbi*gonsS4k6_)Jl-;xWd<=c+x%=v018iQXDO!uq6icy())lCdPi%_qiET<5{X{y;MGH;}1?>Tr8%tcB|8G$5{p zej96EkCvUBfw$Or?znR2ty*q@>7?uIPz1I<-h)ulRN5ne%WbYuY2*YYArN( z`tS;5Z1lp0=H>XVkOTX;38zT*t_69*1@2kqc^J6=@{@0waz=*barVmK0QrHAS#sRR zK>KoYd53HGB1G((l=Ypw{WA)VT|J{(bqbF2A4b8keUi;N9M$pjoGMzM_Lj&FL3HKI z4$_6<@-(Gyrg(YsD3W9rEAO|#eblP@f9YJ;YQ49}-qQ;QEf$R+#Lk<6PES&we%|h~Xa=V0zbMkWs zY&oG?Y({HDnrlj8C7&uS0qD$E4CuvoU(GaL?XWGjx)GkAVJ$9iGP342xDz<&q$7ahd*f(pO4IvB?C zj!2y{d8;BA1*dm!U8I%{Rh~#KP`uR+GE8;;NC99GjNv-SS`kcszXuy|h2B4mGDeiK zW0+ykguM;Ifno!^$45JmQ#T@0;bs)vh+y39t>6oLB;Y{T>PXD(P4HC_DWL;iF}@OX z9miLkuNYq?d=>L0u33jDVTiuli$xM{i26$q^+AaGmj^+&0y{!D+Ty%mB@BBxA{2TA zOjIctQv;)gaLt&a2S;oa_`v=DgO95nG^{mdsQv%ZsgbFXtH)g}YZLiR#ZnlV#M~&% zmzG~zPBXvfRGm|`t|Y2zjZN4~#a;Z^-Hd#KO77CU{Hk9WP0T#!>d29E&q2F|b^nWO zPT+ej>c8ug8^@Wiqs}>qI>&|1+zE$+fLNM zf4~`aZM$$%e`A&YtC&@Zt~Xi*pwf+2vK*zF)W`L8V>eYdiXIIcS-Y{%=_ctuvdI%I z>1#~hP>Vmkm&e^zidWPa@DmU}B;|oAgeov(+@;Uvk#{h_i(Pwc0H6n06avomL<>iD zQuXzqr|C!It@(A*e-_cNU`y>!+T(xD>e^o{jlL|bKJz*h>G#u zk^?-Q*;LT(q{EfGz)Sd2A|?xanZCPl-j^|8?wsEgz*e;WWN*2-Q+g#uj)fyt<&Gr? z%Q&&9_#Mm>2AcmMhe|z%UJ@2XuYTtOhg$v4Xb!FKr7Y|k93?B94KrZw%3*C+E^E6` zPdLQPUD#|nlH48ndT|0dn?#{OP(|iR|w9MD+G}N zq2N?&YF#%PwB5)<`ZzriPhK@nweOOT)?$!^^)oK5pOr_|ss*J0&+5?sHJ)5nZfVDS zSBhpZF489i8S5SOy5*KXA);G{)DqW)Rv&r@oh0C;ZG%j(~ z3r0anCJE5l6;L>HH+8*zb8>eTrHBoQo6z&jTu8E+hdg2M3FpJ2oT75}uVrpBQr4I9 z&m+pv$q?>4BwCyFpicmE?7xive}M_!}rpZHE|5` z4z&#Vz6^Qc^L>zqD!rDntei419dxLHyeib1&CM+7c$or6ise= z!t3mO#(A8@Zv9gg$U(cUXRwddqu*JLNWfmj;`MNmao7Ydk2~m6$pHnTei?((1!Hx{ zE?6<@6dc=((y9^za$W6RIQXK%>~9-La}2OwPbOM4tIk-pmXsu(yM);tIZ_7vI*I?N zVoSlR@^UvicMn&fSmk0xSzeaiCY!4cC{2}TQT3$wXx zaQN&IVZhJKvBCg+K^`p?2I4;|o`_GFagq2yYgk5r0D2Rn5S<#gotFaw>VQV;jufIQ0XAqA>LtJB;0Ev5k2-8{p`YBEo}c7bf0k`ooOsE;nm+A0w`^)t&EFgGG z{R?O^qL6^GeMiEF|4@B{_4TyQb;0YZsV`|}Lo;9;d|JNGio!*yGE)TTH4a75h2aMJF z#Wm&tuW`U>`-w|d8yp33Bh0e*^F+3uf0*9RomAn4y($WDxe5aXv6j&B#bq>yGe4axiM}a{VdmCC7Vq^Q; z*i(O=y|F!jyX&!kUPf9uK!_$32MBexB$1ywTduT6U*(d%YjZ8jZ42%WR9CGxV>SAk zajA9YxKzV9KGyw@J^fruv!dS6veq&D+%Y`p7#_C`RyWG1aX3-yt~&Hry%DTx!#f`I z*5VK{FW=~8<6SCdz;OT>y5VJj`}qbOZ@_tI8G7r24;;Yp9z`c1@Pr2|2k0vyl&bxEip>HjQ`{%~QIBJgeg@hcLeA#|6fytuaQdVd@vjoyGz1sMa=O@~~fzoB$Ay z^?a_b4 z9h&fgDJCx_+0+^VPKWKqky*YznTYPibOD{*Xk<-5GGm@Jib*0Lu$G_} ziXt++Dj0)38m`3Kgvf+&<1Fn_jH!8zWz0@ke-kCsUUY?eQ6l&1+H@dtLLZ2L0x3C^ zE10Rc#THWOYt)m|+c}7|JjIjFQ6r6NN{oyKX3C%$xe*Yib0T44ipy*SYaU@O`IV0- z@$c2IpWY{T%gZS|dlh?Dq-}4j7nKd{^U+$s@tu1ySoi339u)|abtS4@1!e;e)5slH zBaOZRmj~feB-8rhTVl;v+Tk_of1Pe8_u3g|BT}>9yF2%4M1v_Ac zrLnSMC6=x0$8Bqa)L&Cji7NNbu zSKnSM3Em^{M^%-Zu>oeNICxbQZ}P~{t(?%OBE6OB8Y1Q7?X%k12-_+HOEOZni!!J$c{k`rfHC+mZFBQs0j|X=KVM zQZ|7d=$(K{vGmEAww`9x5~X%>m`%zbr>y7m%E3b@Eqd9Q|2(83j!;_Qs`_ti1xEmC>lU*;UYQ?Xl6GTqU;y*N>|PDn69t@}Ae5I>wR{+cCDLsG@kiyzLTA!jjw~E7 zMoqsQdTe=b_7UMLu7&h=nHZVls?T6>|FNfsxt{f_(B^5!5e`rnlMUKywU)xNrw}EoC z{WFJW>hr95#uOjbm+BK4Lwt80mZ`20KBHK9gVoi^d8g=Yvs(rgJE+ADnWNr1Bug$3 zeg`bEM?P9k(@!~^oT52C<@CRIjO+BbX17cQJ1`z9a~Z4w$CM70o2(R!!OHr>vXaX2 zi9rb`e6CkUC8GD9!pY`CF;qV1Pz+t16T{K6Sup2tvbi$HXUnyUAq-up_*Ec&xjpy~ zX8yqa|2dD(L5aU6WmV)=cSs60UwIo2+|m9Y-D>b*(7a{=P8#i#g~&lPn_pUBaW)m> zdYrhzfF4$tw`eBL9bF5nqJ%kcP|EEWI*2YoRP!_l67%1q5pjD-5G&<`qmDd|`v$BK zn(6`TZn+aCd8TiZ@)?1iK=fq%l@<(S_UYTI`icE0TA&wlsq}y1cH=e=11x&nz zY6d6<)0KMX84_#=Zw$%kYhevxpr!%i^?nS<^8!j<3U$eLs7vP=?c2@mXer`mI0_hk zczF~~frqPfhCRt+&Y}U*Qe86ZYIKafY_#I|^GdK-X)Ga&i*cJ)z*;PpdiT^y+)swv zQx{qdxIP0H6rYv+HvjY`r-S94CG0O_5Q0j#RxYL)r3=W1=rT8b(eC^L=PJ?X+fAl%ijLdU zTvYN3{#Tf{PWhh4~8Z&*7cN0`LQXK52p7!0*+MJS(?rIH1oxQ*{lQ zqr&aEl1^KQ<4wI#YFjKYFUBkawNvd8WANNo zgpFSM!)HPEO@wg43^`R0R^#5Zmm-Dk-JP;9{x?eZ8rX|N0m-70{W2DlgK_~adwD3p z5D~jj#MKg@^3#-6I#y{bT|9`h6PXK(G|f(Au}*Ym_G1WDCOYAJ3-UA0b!A9j%()jj_e9m;Gp8ekYV1{9oZGCzp|A$>ZvV)wPu!nDs-m|{SoTgwD`nM78Wh--6w@h# zA|oz9`9$8GjtCL(3-g4G^37OG5DW@poYlD3^`J9^j36 z?3wpsqr?OV2)A${m)R8=CV3;)q5C20^eqP-RuT zF`<#kycmyw8EX>EA+_|f#~hluGbvLZm~)ztKhUxS#Ta%?wl6U|rM^`+tzbb%>a+Is zHE0I&22VC5I_+UR@CIr~#Zg6+f@9grC^#x-14X8cLbWYzS_XlrkttR&xDyK33d}_H zEq4}h+mxb{7H;ph<1R*t{*lt{FYELo$JcSJ{dEvWIRp1H&7LQFg${k%D zl98Yz&J-Bww>)e2WpL9v+8mrt(a(c{D-*GISU>wbaL(-^&X(az2{`gAGuw+KDiUG_&V0HQV zSY3YGh1k0u!!f{G%o7Y_(i*GAD{(GBqz*5CgfBmCw8Cn6pcL~J6Brlj`-xl!Sfmyv ze>`C0*(QO}M=5;#bHWHj5EE~t0FmL!eVDidfiQ2a9V5UbGGj-}A;8@MHi0^r5J4|? z2L!li-$Rqvi-~ub5QewbAJ4|pA@D!L!J#MpbGm3Bxc`6Q;}<4-zDiw+T!XjlbUJd8 zUb!gw+#=)$EUq#K?LAHT^}JealDn&E{8jpMi*oAuH96)xrOuCxSa_1%$Fw>JbA&J8 zXBM91;K;tGw6pK&k7QQud-~s*&97Zyvng`E^$NZ5isbCCnuE9@MTCv}NV%;+?IC+F z5&7;l`s}afNM(fN{_gS)WaK^TtM5zR|5I#kci6OsP)2?gWn}WIpqDFbJXoirJ|(!_ zk2duw#$q4XO4X-W0ZY}V$g7pW4IcP`EvRh#M7&xQA9&y=f(1filTV#jEB5m)L;Hi` zHi*jzX;U9;qN0tr9>UGWN%{5sf&G+R`dAF$?E`B~G zPp^tQ_OUS_Z1mzsQ*IK6VilEQKOD%sS1XiHM3MvBw~m4GTm|JRxCS>!da)bMqdr{p zB|?I_)I!EZ&>DP7_zB4EkqSsR_$vVrpE?1Gki;fHqCm~4CqmOt6~tD&vBHxO=m8g6 zGCTxZiz={430`dP8eY}=2+xCPj*4IH`;Ffr!z;nPosg$|5FI7Y3Ia3HETb)e94ay^ z@rWB(suE#AX2cY?j06QJnom7}o2@5f%5#kP{r~hErJw)&^vmRIJN-m8xJpwCM$|R2 zNK~%bjQQ{ZyRt5`DuR|AwH5n23DlI0`CKv{kGqDxh#Qx7iY`^4i~~&x!HMW z-KSOznu3X4p^B#1Y?|Ox(XI!1h6E7m5A9H|Su7P*42S!nl#b!H2V4ML2SncWD>fDc0OFJH1`mhxM=JEk zB9$~*z_L^!(5*faPzBsrk_UYJ)CN52<5oaTeQ@d^R0l_w=!iz?}fG!KYJj+jMTzbCALU`vW>uu6o|h_5&a4Caj=9qpmV08A~wbdRcKV) z@Uh*3k%)?|cz7@Vlvr75ij>g4DHajnBXO?+X^5^0K#`zZeKZAxKk%ZJ_BVW)(iADt@Q| z$gT}UyS;#zeze`VZXW|18Go=Iyp z_H~(CJL=P12&s>6(hoG<&|Jqfq(oGJhN@LlxunXcri6J5!BVaETyU01nW`0{_> zeSqD4wk!EG!nhY<;eX%nC&2Ag`QL}{qNk_9-I|)b_h)k#;eX%nf0lIJ`-^Di|LL6# zu4Mmh9Q>HJ+!_3sc(Y!9b8^eg(jWJ8@WZHt+gQ^a`NJPqEZ4W({7>tS`!e0}KagFH zXRql9VmA0r9KPp&Umf&+CgWVVWWjkkR8^pj+8hNeDGZjd%PWV!ij%rUJo zXVdc4>>t5q(en4fCMK<3vX-NnxM+DW5<-#YW2+-lfe)KZ_^>C&Lxu6c#d*ymQD3-r3{_3TirkmU&3LjzHy| zwH%3M9XuaU%??^zyhXILP=mzm% z_)e$*4hBQ`fccO@aom)UF&DxVQvm^ml4tvgnyhTfjUj;M-!F8@Y3y4$a$&FlN5Qi?=%Fc`@z$&*oiRn!gJ-4CYnsB=+UeKhja;qK{ zaHWBb560pESM9Vac2JLlx7*?E;?*29khUqqMvId+^y3hW7d=JQ!i^Ne4Z6kAL^OiQ z0!R`QSkgq5!M+j(;MS0-+0ezf7CxX7^hQxQ;e#e4TffZofIV6QP49 z#$wnXL)AB!>MTMXbc-v-cUM!uD@jk?Qx%hDBYLQ`}} zvW0{YlhADs?3_dA(VUUuqHYY9(EI>Rpp3y1vISX^iGSA8!V-q3$P$L9f*8e-%tagA z!l*UzVO>ZDm6oIV!AwA|B@X|0Nr{szQrcj2i-JUBw@MNRAEYg73EX1f#X1Y-{zKOw ze_QwdU44Dv{{QO7SEKJQr{hA2iRO#)rXZjSe%OWxD{9~ zRIHW_8KLASpTHj`p8K(>x?i8w3UqZ8#wVWuo*d$4ZZUR%s#=czpfQZa$3s~5;$r{heEDYcDl-ba>M|GVp1m&1g5C;nof;fbGZs0D{B1 zrFMRId5k!rh&F?n${(zgDvT}#1O;mva?x2 zW;>COLAHZ8E@m*g>}H0|7uo2SHI8G@$(mL&eS3r$hZrGVRXI9`{k4w{`ip15MiTSwUq39)Q9D zH)A&(In{0*OxwT=A0#2U*$fTD>s+5eN1!TJOy3N%cskTz_L7-A7{^06A?{UQIox9bg4u)c10qWhaZo)V2@@KlKHzu_1PL>DDEbJ;Aw~WYLzWCIA=U#+ zM8}oMflM+=o+VuxC`SN2w6d5fLy)cl+4jG*nI(}p5NSCahhitS6f*nmDk$NSpkR0` z+UgyFX2H@wsXmb4{UuMAr%#kx&{sf1?M|iShgkAo02>37425wEe2~A!^8i&OLsJ63 z)V^4$i&}<|8XBZ14b=z$Cv?do$@hah1d12m2`-_n#6r_t2Ofvc!34vkU@^lH0|gDR zCX8tti)TGdco{H=3~lj0Ta*XEyYwuZhGpyX10W^dE>^_f2qjFNq@XDa5H#rgq%EGa z1LzNS%*5^%OeZK%v8kI$ZHzOAQ-ZbNm!^tYrvQz6NlqU^_#hPyM+z!4#P(!?*oQqxq1So4KX zG2&eL3DraYB|`ZLsohvwyOkU~Y4z&{UxbY{sJVGysdL{BamO~q5?@03lhKmE0!;TY z74cZ}X{zfCpq&A$-1{-=>%fpFKtKT)tG@~ z5d-53Ow`^N+1Rn51EiINNK*A;v?>06J3QR-4=F#zNoU(U=$e?O2hjE&`GRr!ZRrB< zaFC-1jXFb?cnCIH#DG?#r|v_qfd6gwg-E?EWM2qt!Vqvz@s%CG6zrLd6ag7#ISBQ4 zQ{8Bvs?AQa-YO3s=y~Y*p>sVB54H;21}lPXj<$*tPml6Lw+q zz~Z6;Z6!R9mpBh|4P@}gf+|=k#muG^H%LC{jkBD0#?XUy7RRx(pp}oPiIg&k(Nm9; z`8_kF$=5XG-~{L4}520Z@FsCelV@QSlkDptWU-+q$DJ69gN9L*E|4T_PlJDCSLA%VJq(2q* zKi(sij{&J_RLw@ zotazL@8Sx}W2C+klpeM_lztDN*DJ*OeE5gb=lrGPdFg-r_R@Z!tn^>*W_2@Kr7P3= z#8-DK9uBJRTS4vddiQwy`@Ia*Ob&{bridu_C!ha!@7#^^_E5LAHOXx)ak~#49^Rc& zi0QZ#*8wnfOK+T~LrizW2rJA(VF%AmuZrW`GVETHMOHHCpq1^!o$TU^ZrPrF4nD@v zwcwT0PT*#ze|O6j+2`N`2PUJcOfwdo4eHWn#jV7%OOUuRvlA@xb2rF(8y$f;$?A!q z{g%>DAMHwTauh#vBN8TmO7P(4Nm@mi=@+q^(r~au+T&`RhRC{A0e%hcGW?povEt=} z=GmvB>w)=v2awPOVM2>HIu~CWuzYB62H|#xSOY-lJ+vxBmk9PMmJ#=ZObr|hTK2i~ zk{yLAf&dfTqdDe46OAN*g8-}i+>W)#p8eG?(AjqEK@l)crvTO`l~j=yZ(yrabW!M$ znX!n#S`wCKI)lSd&ft|GP3_LaFC9YNhu|^5^9^~Z{~kL$X#b&460lJs^eoVHGDoH6nmSZmbEw7rE+BlsV7=i$~G{PqMR;I^US z>~mCc4_GUWBH4suV-t-8unaO zfg*78PNJS-Tk1h64Eu^<-eUZ|?JdIZQ{IXAeaUOZHsqD2SaH&k=;ojSDmDQyu;Q7+ zXgkwGAEF+Nf(gDGvxR1k_{0&ah=OENkb+>|VTpWlmP>Zofmy5ue|Q9M;9!$$P$Y;^ zfEv}{2(KO?B886;X{K|e?_2n3`*0+`z-u3&=s4e!!1jhjc4JUg^!`WbhRQNt zUGsFj|BX-)=J9)@;<=d&QbE2@wHvt7F1JU4_AZ_@Ls8kys6yB!^LGyytn`8+;@tyE zA^xvesmJ}Ru>ZCise>m@7%UMS{Dgm#Aa^vL;5i4e{}C&mIZpQ+7^e@zE*I4wHH63GG#=4g;SIbI)j*I@EK{m;9&o+ z!3*?mbS6#!Ow&8DiSSqUf#P6YiWfu&R#}xie_zpy2p&J1z1{kUuy|aH6QZV}jG8u| zAS(I#t2buaCyaN}|DFkR*V&(cz!~_z1Zk9qT^o5D zMURBVpT5q}XOfHUg-!H&gi%}=l-|MY|H)GO{;EC5eMCVz_WxLmBPL0gbXw|`_y+)b z9sU(e8*1fV{1VgZ#(x@Fl*ZLLqe9pBF_m@tmO>W{K8H^fnQVF@ALxI%Re3GEjl8E=%G*f|yc=aT&>kn4tX{$2Z4?{~Qm1QT9hh{~eRnc3O--KRdv13#4PPX9D z4EeI6EO#K4AI*?j6s7l#RDLu=D#jml(eFoLz|jn;NW#%;*gB#V_)DH4Easqgd@=@qf%p`*#mPK8!>ahpHe|z?2_JK4ug`5QjoqqK3)dCjvA=dQH4;B!8fWB!nR6 zLJx=<7z`R+BFbs-;7C9QEZIQ9U%ChJAZ;N4qlY0L@{-V~i-$-OMY4y1DZ>%+Pz-t) zIq89%1e#Suk&vH4Bmhz*L-7A2@(}KU#uEua1eyXK8E6=jK4RigByGV%Xe|WMDGSL= z;ep=OYZCZq#`91d577`KEtZFH{~wKe41fF?B>1C_^xrGuPd@*j-g#P5DZY1WRAA4K zNsq2udf|d&wd}wx>Q(Svmn)E=Sae)A-I(&nGw`GP%ZU&Yw5(!=s^_BpCL8y(Gy zB~$aSrG$^(7Nq!=LP_5;NlI$@37#)VN%E3?QdLu<)LbczY_5{t?(OYut9O40exa9= zDu3Ib2}5KXn!WGV3-Z!^73Wrqv%SdD+v|+!?G;B}rgieWxL!pNe{i{Xqg$}+Hy#x1 zNgEH7?+|5V{AvPYsrxwc?{C0?;1D(UzTz7Um%<@_( zUP_QucuR&+kylhVU|;%yv|L&tt&~?Cyn93TXc%?_0Yf#TvQFA4- zPbfhx^!7Hd!dTQxBR{FF`=pK{dYc;@rX3DoXCJx9(6R1%U1Pod1J?b7@uSihg5J2w zzPz!)zC!tLNhQUAOU~XzUhP?xd4+?q*L&z% z8Qd#_=>2e?t;%FZ+*f)N3!0Jv5;X!y349!QNf_vog*j&MTUAVqO06-Na~4L1-48f3 zTh!sE5 zI%$7_90b&k3K&t9|o{P0Co_-+yIu*vbK7CixYs>0ANFPBO%fUQ0oC|9YDzd)d*0s z6L>whS#rV;;>^6Gs4(W>4U(#Wd14e9!g=-zU#bc31eL)s-(V2(^lHI8dY0LY^?LMU zv(0Hjety0+d}On-MvlzLo>$Yl`s5d?T{Cahp>c4l=HK=C7QFShsq^pN`R1(!<5K7U zZntj%JrV!2*PluSif}2W)KtAb)f9N@0v-#)HP>R9i3;;)TF^G;EM+glm01GmLckNq zCrb*Zt$m@)T#K>t4+c3rRBNhrGP_KvMQ|5*u0Iu znqZd9a$~uUvas}1YX6@<-FDu#oc%BNsWtJiHl^3;G0(Ibo%Xf6yH79p<=nfr)4u<> zvFp_Q$n;WAjv*@{SIAuPsW7W7OP!ZT<5?h1)K3(x`FVN9wA|uWzbnp7Gonjy!*qOx z{@S(c85tQS_}NgPA1t{(xwzN{B#p@_Ba9e0j3q`g#grmMPHP(K@8KZU%bg)p<#&c> z13pzPxte9mZr<+qyW6*^1Gv*^?T$r@Dr=X))zo~SyyHwm6X1k|hsTXuu)yc*@3;Nf zQZ^gTX0ttOF_um~w7&O%c0_z!+>o2I8uq{md%QPqT26iawawOVpLdD~A1K|Nm-#d1*DdX~GPbYe{A0ro2!tmX~t< z-T3k2%f^q-5-KVyD?fC(ey-CvHtD~4(_*uQCAx>-Y6<(PP348eNOz{>9LfiiRdj0KNz2U^WoQL<`qKW|A*+0Me{f{Rs~x2mZ^qp9`=J`3u5siSNG~cf3~)t7=>|gz`ii^I zJcVIEDhy{};KF_w`Q>S^<)x*KzIpvRU9=Z+#wTZvxa7N2XoH7nk*62~WSt)HWE!H6 z=9!>_w6x(mV2FCv>RDor*{B|E%CwSsm(eCf&&x|2u3}*`@}*;ogtse{U#_B8|qtb|J8J4PgI&Isbpu9coA6mBK6k3m^P(Y5)KL literal 0 HcmV?d00001