Chapter XXXVI. MBC5
While several other MBC chips were used for production games, we're only going to implement one further one for this tutorial — MBC5. This mapper is used for the Pokemon titles and a large number of Game Boy Color games, so it's likely one that will be encountered often.
ROM Address Space
The MBC5 shares a lot of similarities with the MBC1, and really only has one small trick up its sleeve, as we will see. We'll begin in the same way as the other chips, by expanding the write_cart
function.
// In cart/mod.rs
// Unchanged code omitted
pub fn write_cart(&mut self, addr: u16, val: u8) {
match self.mbc {
MBC::NONE => {},
MBC::MBC1 => { self.mbc1_write_rom(addr, val); },
MBC::MBC2 => { self.mbc2_write_rom(addr, val); },
MBC::MBC3 => { self.mbc3_write_rom(addr, val); },
MBC::MBC5 => { self.mbc5_write_rom(addr, val); },
_ => unimplemented!()
}
}
All of the other write_cart
sub-functions have broken the ROM address space into the same segments. Here, the MBC5 breaks the trend and uses a slightly different address space.
0x0000-0x1FFF | RAM is enabled if value == 0x0A |
0x2000-0x2FFF | Sets lower byte of ROM bank |
0x3000-0x3FFF | Sets 9th bit of ROM bank |
0x4000-0x5FFF | Sets lower four bits of RAM bank, if any |
0x6000-0x7FFF | Undefined |
The first block, from 0x0000 to 0x1FFF, is exactly the same as the MBC1 functionality. If the value 0x0A is written to that section, external RAM is enabled. Following this is where things begin to deviate slightly. The next two sections still deal with setting the ROM bank number, but their allotted section is much smaller than in other MBCs, and the bits they affect are different as well. In the MBC5, writing from 0x2000 to 0x2FFF sets the lower eight bits of the ROM bank, and for 0x3000 to 0x3FFF, only a single bit is kept and used as the 9th bit of the bank. You'll note that this means the MBC5 can support up to 0x1FF — 511 — possible banks. The MBC5 also does not have the same restriction on not allowing the zeroth bank from being loaded in. It will happily load in the 0x00, 0x20, etc. banks.
Lastly, 0x4000 through 0x5FFF is used for specifying the lower four bits of the RAM bank address, if any external RAM exists. The region from 0x6000 through 0x7FFF, while technically usable by the MBC, has no purpose, and attempting to write there is unsupported.
Implementing this behavior is similar to our existing functions. We'll need to define new constants for the 0x2000-0x2FFF and 0x3000-0x3FFF ranges, but setting the various bank values should be familiar.
// In cart/mod.rs
// Unchanged code omitted
const ROM_BANK_LOW_START: u16 = 0x2000;
const ROM_BANK_LOW_STOP: u16 = 0x2FFF;
const ROM_BANK_HIGH_START: u16 = 0x3000;
const ROM_BANK_HIGH_STOP: u16 = 0x3FFF;
fn mbc5_write_rom(&mut self, addr: u16, val: u8) {
match addr {
RAM_ENABLE_START..=RAM_ENABLE_STOP => {
self.ram_enabled = val == 0x0A;
},
ROM_BANK_LOW_START..=ROM_BANK_LOW_STOP => {
self.rom_bank &= 0xFF00;
self.rom_bank |= val as u16;
},
ROM_BANK_HIGH_START..=ROM_BANK_HIGH_STOP => {
self.rom_bank.set_bit(9, val != 0);
},
RAM_BANK_NUM_START..=RAM_BANK_NUM_STOP => {
self.ram_bank = val & 0x0F;
},
_ => unreachable!()
}
}
RAM Space
The other behaviors of the MBC, such as reading or writing from the external RAM space, is exactly the same as the MBC1. For each of these, we can utilize our helper functions, which simply requires us to note the usage of the MBC5 in the match statements.
// In cart/mod.rs
// Unchanged code omitted
pub fn read_ram(&self, addr: u16) -> u8 {
match self.mbc {
MBC::NONE | MBC::MBC1 | MBC::MBC2 | MBC::MBC5 => {
self.read_ram_helper(addr)
},
MBC::MBC3 => {
self.mbc3_read_ram(addr)
}
_ => unimplemented!()
}
}
pub fn write_ram(&mut self, addr: u16, val: u8) {
match self.mbc {
MBC::NONE => {
let rel_addr = addr - EXT_RAM_START;
self.ram[rel_addr as usize] = val;
},
MBC::MBC1 | MBC::MBC5 => {
self.write_ram_helper(addr, val)
},
MBC::MBC3 => self.mbc3_write_ram(addr, val),
_ => unimplemented!()
}
}
With that, our MBC implementation is (finally) complete. The vast majority of Game Boy titles should now load and be more or less playable. All that remains is a few minor feature before we have original gameplay.
Next Chapter