GB/游戏卡带/MBC2
MBC2 拥有固定且自带的 512 x 4 Bits RAM 并同时支持最大 256 KB ROM. MBC2 与 MBC1 非常相似但更加的简单.
MBC2 地址空间划分
MBC2 芯片将 Game Boy 分配给游戏卡带的系统地址空间 0x0000...0x7FFF 和 0xA000...0xBFFF 划分为以下几个独立区间, 详细介绍如下.
1) 0000-3FFF ROM 存储体 00
只读区域. 该区域总是映射到 ROM 的前 16 KB 字节物理存储.
2) 4000-7FFF ROM 存储体 01-0F
只读区域. 该区域可以映射为第 0x01 到 0x0F 编号的 ROM 存储体.
3) A000-A1FF 512 x 4 Bits RAM
读写区域. MBC2 芯片不支持外部 RAM, 作为替代的是内置的 512 x 4 Bits RAM. 这部分内置的 RAM 仍然需要外部电池来静态保存数据. 由于数据由 4 Bits 组成, 因此该存储区中只有每个字节的低 4 位才会被使用.
4) 0000-1FFF RAM 启用/禁用标志
只写区域. 用于启用或关闭 RAM. 只有高位地址字节的最低有效位为零才能启用/关闭 RAM. 例如, 向以下地址写入数据可用于启用/禁用 RAM:0x0000-0x00FF, 0x0200-0x02FF, 0x0400-0x04FF, ..., 0x1E00-0x1EFF. 当输入数据的低 4 位为 0b1010 时, 启用 RAM, 其余情况则禁用. 例如向 0x0000 写入 0x0A 可用于启用 RAM.
5) 2000-3FFF ROM Bank Number
只写区域. 向 0x2000-0x3FFF 写入的数值的低 4 位将作为当前的 ROM Bank Number. 但注意, 只有写入地址的高位字节的最低有效位为 1 才能正确写入. 例如, 向以下地址写入数据可用于选择 ROM Bank Number:0x2100-0x21FF, 0x2300-0x23FF, 0x2500-0x25FF, ..., 0x3F00-0x3FFF.
代码实现
MBC2 的代码仿真过程与 MBC1 十分相似, 甚至只是需要略微修改 MBC1 的代码即可
pub struct Mbc2 {
rom: Vec<u8>,
ram: Vec<u8>,
rom_bank: usize,
ram_enable: bool,
sav_path: PathBuf,
}
impl Mbc2 {
pub fn power_up(rom: Vec<u8>, ram: Vec<u8>, sav: impl AsRef<Path>) -> Self {
Self {
rom,
ram,
rom_bank: 1,
ram_enable: false,
sav_path: PathBuf::from(sav.as_ref()),
}
}
}
// 为 MBC2 实现 Memory 泛型, 使得 CPU 可以通过内存地址读写这个对象.
impl Memory for Mbc2 {
fn get(&self, a: u16) -> u8 {
match a {
0x0000..=0x3fff => self.rom[a as usize],
0x4000..=0x7fff => {
let i = self.rom_bank * 0x4000 + a as usize - 0x4000;
self.rom[i]
}
0xa000..=0xa1ff => {
if self.ram_enable {
self.ram[(a - 0xa000) as usize]
} else {
0x00
}
}
_ => 0x00,
}
}
fn set(&mut self, a: u16, v: u8) {
// 只有低 4 位会被使用
let v = v & 0x0f;
match a {
0xa000..=0xa1ff => {
if self.ram_enable {
self.ram[(a - 0xa000) as usize] = v
}
}
0x0000..=0x1fff => {
if a & 0x0100 == 0 {
self.ram_enable = v == 0x0a;
}
}
0x2000..=0x3fff => {
if a & 0x0100 != 0 {
self.rom_bank = v as usize;
}
}
_ => {}
}
}
}
impl Stable for Mbc2 {
fn sav(&self) {
rog::debugln!("Ram is being persisted");
if self.sav_path.to_str().unwrap().is_empty() {
return;
}
File::create(self.sav_path.clone())
.and_then(|mut f| f.write_all(&self.ram))
.unwrap()
}
}