GB/游戏卡带/MBC5
MBC5 是在 Game Boy 游戏卡带技术发展末期推出的游戏卡带类型. 它的 ROM 容量达到了 8M, RAM 容量则是 128K. 它是历史上第二受开发者喜爱的 MBC 类型, 在总共 6000 余个 Game Boy 游戏中占比 45%, 第一名则是 MBC1, 占比高达 50%.
MBC5 地址空间划分
MBC5 芯片将 Game Boy 分配给游戏卡带的系统地址空间 0x0000...0x7FFF 和 0xA000...0xBFFF 划分为以下几个独立区间, 详细介绍如下.
1) 0000-3FFF ROM 存储体 00
只读区域. 该区域总是映射到 ROM 的前 16 KB 字节物理存储.
2) 4000-7FFF ROM 存储体 00-1FF
只读区域. 该区域可以映射为第 0x01 到 0x01FF 编号的 ROM 存储体.
3) A000-BFFF RAM 存储体 00-0F
读写区域. 该区域用于读写游戏卡带中的外部 RAM(如果有的话).
4) 0000-1FFF RAM 启用/禁用标志
只写区域. 逻辑与 MBC1 完全一致.
5) 2000-2FFF ROM Bank Number 的低 8 位
只写区域. 存储 ROM Bank Number 的低 8 位. 注意与其他 MBC 类型不同的是, 如果写入 0x00不会被自动转换成 0x01.
6) 3000-3FFF ROM Bank Number的第 9 位
只写区域. 存储 ROM Bank Number 的第 9 位. 只使用输入数据的最低位, 其它位的数据会被忽略.
7) 4000-5FFF RAM Bank Number
只写区域. 输入数据的低 4 位将作为当前的 RAM Bank Number.
代码实现
pub struct Mbc5 {
rom: Vec<u8>,
ram: Vec<u8>,
rom_bank: usize,
ram_bank: usize,
ram_enable: bool,
sav_path: PathBuf,
}
impl Mbc5 {
pub fn power_up(rom: Vec<u8>, ram: Vec<u8>, sav: impl AsRef<Path>) -> Self {
Self {
rom,
ram,
rom_bank: 1,
ram_bank: 0,
ram_enable: false,
sav_path: PathBuf::from(sav.as_ref()),
}
}
}
impl Memory for Mbc5 {
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..=0xbfff => {
if self.ram_enable {
let i = self.ram_bank * 0x2000 + a as usize - 0xa000;
self.ram[i]
} else {
0x00
}
}
_ => 0x00,
}
}
fn set(&mut self, a: u16, v: u8) {
match a {
0xa000..=0xbfff => {
if self.ram_enable {
let i = self.ram_bank * 0x2000 + a as usize - 0xa000;
self.ram[i] = v;
}
}
0x0000..=0x1fff => {
self.ram_enable = v & 0x0f == 0x0a;
}
0x2000..=0x2fff => self.rom_bank = (self.rom_bank & 0x100) | (v as usize),
0x3000..=0x3fff => self.rom_bank = (self.rom_bank & 0x0ff) | (((v & 0x01) as usize) << 8),
0x4000..=0x5fff => self.ram_bank = (v & 0x0f) as usize,
_ => {}
}
}
}
impl Stable for Mbc5 {
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()
}
}