Documente Academic
Documente Profesional
Documente Cultură
uk
H o mB e a s Zi u c k sM o e g a D r M i a v e s /t G e re n S e y s Cs i ht s ie Bpm l 8 o Gamebo y g
codeslinger.co.uk
Gamebo y - Ro m and Ram Banking.
Gameboy Emulation:
Getting Started The Hardware Memory Control and Map ROM and RAM Banking The Timers Interupts LCD DMA Transfer Graphic Emulation Joypad Emulation Opcode Examples PDFmyURL.com
m_MBC1 = false ; m_MBC2 = false ; switch (m_CartridgeMemory[0x147]) { case 1 : m_MBC1 = true ; break case 2 : m_MBC1 = true ; break case 3 : m_MBC1 = true ; break case 5 : m_MBC2 = true ; break case 6 : m_MBC2 = true ; break
Finished Product
We also need a variable declaratio n to specify which ROM bank is currently lo aded into internal memo ry address 0 x40 0 0 -0 x7FFF. As ROM Bank 0 is fixed into memo ry address 0 x0 -0 x3FFF this variable sho uld never be 0 , it sho uld be at least 1. We need to initialize this variable o n emulato r lo ad to 1. m_CurrentROMBank = 1 ; // this is type BYTE
// read memory should never modify member variables hence const BYTE Emulator::ReadMemory(WORD address) const { // are we reading from the rom memory bank?
PDFmyURL.com
if ((address>=0x4000) && (address <= 0x7FFF)) { WORD newAddress = address - 0x4000 ; return m_CartridgeMemory[newAddress + (m_CurrentROMBank*0x4000)] ; } // are we reading from ram memory bank? else if ((address >= 0xA000) && (address <= 0xBFFF)) { WORD newAddress = address - 0xA000 ; return m_RAMBanks[newAddress + (m_CurrentRAMBank*0x2000)] ; } // else return memory return m_Rom[address] ; }
That was actually pretty easy wasn't it? Ho pefully no w yo u can see why yo u must ALWAYS yo u ReadMemo ry and WriteMemo ry when accessing internal gamebo y memo ry
} else if ((address >= 0xA000) && (address < 0xC000)) { if (m_EnableRAM) { WORD newAddress = address - 0xA000 ; m_RAMBanks[newAddress + (m_CurrentRAMBank*0x2000)] = data ; } } // the rest of this function carries on as before } ///////////////////////////////////////////////////////////////// void Emulator::HandleBanking(WORD address, BYTE data) { // do RAM enabling if (address < 0x2000) { if (m_MBC1 || m_MBC2) { DoRamBankEnable(address,data) ; } } // do ROM bank change else if ((address >= 0x200) && (address < 0x4000)) { if (m_MBC1 || m_MBC2) { DoChangeLoROMBank(data) ; } } // do ROM or RAM bank change else if ((address >= 0x4000) && (address < 0x6000)) {
PDFmyURL.com
// there is no rambank in mbc2 so always use rambank 0 if (m_MBC1) { if(m_ROMBanking) { DoChangeHiRomBank(data) ; } else { DoRAMBankChange(data) ; } } } // this will change whether we are doing ROM banking // or RAM banking with the above if statement else if ((address >= 0x6000) && (address < 0x8000)) { if (m_MBC1) DoChangeROMRAMMode(data) ; } }
Enabling RAM:
In o rder to write to RAM banks the game must specifically request that ram bank writing is enabled. It do es this by attempting to write to internal ROM address between 0 and 0 x20 0 0 . Fo r MBC1 if the lo wer nibble o f the data the game is writing to memo ry is 0 xA then ram bank writing is enabled else if the lo wer nibble is 0 then ram bank writing is disabled. MBC2 is exactly the same except there is an additio nal clause that bit 4 o f the address byte must be 0 . This gives the fo llo wing functio n:
if (TestBit(address,4) == 1) return ; } BYTE testData = data & 0xF ; if (testData == 0xA) m_EnableRAM = true ; else if (testData == 0x0) m_EnableRAM = false ; }
void Emulator::DoChangeLoROMBank(BYTE data) { if (m_MBC2) { m_CurrentROMBank = data & 0xF ; if (m_CurrentROMBank == 0) m_CurrentROMBank++ ; return ; } BYTE lower5 = data & 31 ; m_CurrentROMBank &= 224; // turn off the lower 5 m_CurrentROMBank |= lower5 ; if (m_CurrentROMBank == 0) m_CurrentROMBank++ ; }
So if we're using MBC2 then the current ro m bank beco mes the lo wer nibble o f data. Ho wever if we are
PDFmyURL.com
using MBC1 then we must set the lo wer 5 bits o f current ro m bank to the lo wer 5 bits o f data whilst keeping the upper 3 bits the same. Yo u may be wo ndering why I increment m_CurrentROMBank if it is set to 0 . The reaso n is that ro m bank 0 is static and can always be fo und in memo ry address 0 x0 0 0 -0 x40 0 0 so ro m bank 0 sho uld never be lo aded into memo ry 0 x40 0 0 -0 x8 0 0 0 . If m_CurrentROMBank is ever set to 0 then it is treated as ro mbank 1 so ro mbank 1 o r greater will reside in the bank regio n 0 x40 0 0 0 x8 0 0 0 .
void Emulator::DoChangeHiRomBank(BYTE data) { // turn off the upper 3 bits of the current rom m_CurrentROMBank &= 31 ; // turn off the lower 5 bits of the data data &= 224 ; m_CurrentROMBank |= data ; if (m_CurrentROMBank == 0) m_CurrentROMBank++ ; }
is respo nsible fo r ho w to act when the game writes to memo ry address 0 x40 0 0 -0 x6 0 0 0 . This variable defaults to true but is changes during MBC1 mo de when the game writes to memo ry address 0 x6 0 0 0 0 x8 0 0 0 . If the least significant bit o f the data being written to this address range is 0 then m_Ro mBanking is set to true, o therwise it is set to false meaning there is abo ut to be a ram bank change. It is impo rtant to set m_CurrentRAMBank to 0 whenever yo u set m_Ro mBanking to true because the gamebo y can o nly use rambank 0 in this mo de.
void Emulator::DoChangeROMRAMMode(BYTE data) { BYTE newData = data & 0x1 ; m_RomBanking = (newData == 0)?true:false ; if (m_RomBanking) m_CurrentRAMBank = 0 ; }
That is the end o f the banking sectio n. Next is The Timers sectio n which is easier than this!
PDFmyURL.com