Newer
Older
let mut rom_bank = value as u16 & 0x1f;
rom_bank &= rom.rom_bank_count * 2 - 1;
if rom_bank == 0 {
rom_bank = 1;
}
rom.set_rom_bank(rom_bank);
}
// RAM bank selection and ROM bank selection upper bits
let ram_bank = value & 0x03;
if ram_bank as u16 >= rom.ram_bank_count {
return;
}
rom.set_ram_bank(ram_bank);
if value == 0x1 && rom.rom_bank_count > 32 {
unimplemented!("Advanced ROM banking mode for MBC1 is not implemented");
}
_ => warnln!("Writing to unknown Cartridge ROM location 0x{:04x}", addr),
}
},
read_ram: |rom: &Cartridge, addr: u16| -> u8 {
if !rom.ram_enabled {
return 0xff;
}
rom.ram_data[rom.ram_offset + (addr - 0xa000) as usize]
},
write_ram: |rom: &mut Cartridge, addr: u16, value: u8| {
if !rom.ram_enabled {
warnln!("Attempt to write to ERAM while write protect is active");
return;
}
rom.ram_data[rom.ram_offset + (addr - 0xa000) as usize] = value;
},
};
pub static MBC3: Mbc = Mbc {
name: "MBC3",
read_rom: |rom: &Cartridge, addr: u16| -> u8 {
match addr & 0xf000 {
0x0000 | 0x1000 | 0x2000 | 0x3000 => rom.rom_data[addr as usize],
0x4000 | 0x5000 | 0x6000 | 0x7000 => *rom
.rom_data
.get(rom.rom_offset + (addr - 0x4000) as usize)
.unwrap_or(&0x0),
_ => {
warnln!("Reading from unknown Cartridge ROM location 0x{:04x}", addr);
0xff
}
}
},
write_rom: |rom: &mut Cartridge, addr: u16, value: u8| {
match addr & 0xf000 {
// RAM enabled flag
0x0000 | 0x1000 => {
rom.ram_enabled = (value & 0x0f) == 0x0a;
}
// ROM bank selection
0x2000 | 0x3000 => {
let mut rom_bank = value as u16 & 0x7f;
rom_bank &= rom.rom_bank_count * 2 - 1;
if rom_bank == 0 {
rom_bank = 1;
}
rom.set_rom_bank(rom_bank);
}
// RAM bank selection
0x4000 | 0x5000 => {
let ram_bank = value & 0x03;
if ram_bank as u16 >= rom.ram_bank_count {
return;
}
_ => warnln!("Writing to unknown Cartridge ROM location 0x{:04x}", addr),
}
},
read_ram: |rom: &Cartridge, addr: u16| -> u8 {
if !rom.ram_enabled {
return 0xff;
}
rom.ram_data[rom.ram_offset + (addr - 0xa000) as usize]
},
write_ram: |rom: &mut Cartridge, addr: u16, value: u8| {
if !rom.ram_enabled {
warnln!("Attempt to write to ERAM while write protect is active");
return;
}
rom.ram_data[rom.ram_offset + (addr - 0xa000) as usize] = value;
},
};
pub static MBC5: Mbc = Mbc {
name: "MBC5",
read_rom: |rom: &Cartridge, addr: u16| -> u8 {
match addr & 0xf000 {
0x0000 | 0x1000 | 0x2000 | 0x3000 => rom.rom_data[addr as usize],
0x4000 | 0x5000 | 0x6000 | 0x7000 => *rom
.rom_data
.get(rom.rom_offset + (addr - 0x4000) as usize)
.unwrap_or(&0x0),
_ => {
warnln!("Reading from unknown Cartridge ROM location 0x{:04x}", addr);
0xff
}
}
},
write_rom: |rom: &mut Cartridge, addr: u16, value: u8| {
match addr & 0xf000 {
// RAM enabled flag
0x0000 | 0x1000 => {
rom.ram_enabled = (value & 0x0f) == 0x0a;
}
let rom_bank = value as u16;
rom.set_rom_bank(rom_bank);
}
// ROM bank selection 9th bit
0x3000 => {
let rom_bank = (rom.rom_bank() & 0x00ff) + (((value & 0x01) as u16) << 8);
rom.set_rom_bank(rom_bank);
}
// RAM bank selection
0x4000 | 0x5000 => {
let mut ram_bank = value & 0x0f;
// handles the rumble flag for the cartridges
// that support the rumble operation
if rom.has_rumble() {
ram_bank = value & 0x07;
let rumble = (value & 0x08) == 0x08;
rom.rumble_active = rumble;
rom.trigger_rumble();
}
}
if ram_bank as u16 >= rom.ram_bank_count {
return;
}
rom.set_ram_bank(ram_bank);
}
_ => warnln!("Writing to unknown Cartridge ROM location 0x{:04x}", addr),
read_ram: |rom: &Cartridge, addr: u16| -> u8 {
if !rom.ram_enabled {
return 0xff;
}
rom.ram_data[rom.ram_offset + (addr - 0xa000) as usize]
},
write_ram: |rom: &mut Cartridge, addr: u16, value: u8| {
warnln!("Attempt to write to ERAM while write protect is active");
rom.ram_data[rom.ram_offset + (addr - 0xa000) as usize] = value;
pub static GAME_GENIE: Mbc = Mbc {
name: "GameGenie",
read_rom: |rom: &Cartridge, addr: u16| -> u8 {
let game_genie = rom.game_genie.as_ref().unwrap();
if game_genie.contains_addr(addr) {
// retrieves the Game Genie code that matches the current address
// keep in mind that this assumes that no more that one code is
// registered for the same memory address
let genie_code = game_genie.get_addr(addr).unwrap();
// obtains the current byte that is stored at the address using
// the MBC, this value will probably be patched
let data = (rom.mbc.read_rom)(rom, addr);
// checks if the current data at the address is the same as the
// one that is expected by the Game Genie code, if that's the case
// applies the patch, otherwise returns the original strategy is
// going to be used
debugln!("Applying Game Genie code: {}", genie_code);
}
}
(rom.mbc.read_rom)(rom, addr)
},
write_rom: |rom: &mut Cartridge, addr: u16, value: u8| (rom.mbc.write_rom)(rom, addr, value),
read_ram: |rom: &Cartridge, addr: u16| -> u8 { (rom.mbc.read_ram)(rom, addr) },
write_ram: |rom: &mut Cartridge, addr: u16, value: u8| (rom.mbc.write_ram)(rom, addr, value),
};
#[cfg(test)]
mod tests {
use super::{Cartridge, RomType};
#[test]
fn test_has_rumble() {
let mut rom = Cartridge::new();
rom.set_data(&vec![0; 0x8000]).unwrap();
rom.set_rom_type(RomType::Mbc5Rumble).unwrap();
rom.set_rom_type(RomType::Mbc5RumbleRam).unwrap();
rom.set_rom_type(RomType::Mbc5RumbleRamBattery).unwrap();
rom.set_rom_type(RomType::Mbc1).unwrap();
assert!(!rom.has_rumble());
}
}