I’m in the process of writing a Gameboy Color emulator in Rust. To account for the limited address space of the Gameboy CPU, many game cartridges included switchable memory banks, some of which could be ROM and some of which could be RAM.
To the CPU, the selected bank “slots into” main memory contiguously between the addresses of 0x4000
and 0x7FFF
. Swapping banks simply changes what data the CPU finds in that range.
I’d like to reproduce this behavior in my emulator. I’m currently modeling memory as a mutable Vec<u8>
. Switchable memory is modeled as a Vec<[u8]>
, where each entry represents a bank.
The behavior I’d like to achieve is that addresses in main memory corresponding to the banked range transparently refer to the banked array.
For example, I’ve created this dummy code to sandbox this. My goal would be for indexes 128 to 256 of memory
to transparently point to bank[0]
for both reads and writes. Essentially, a sort of memory overlay.
struct Memory {
bank_index: usize,
banks: Vec<[u8; 128]>,
memory: Vec<u8>
}
impl Memory {
pub fn new(&mut self) -> Self {
let bank_index = 0;
let banks = vec![[0u8; 128]; 8];
for idx in 0 .. banks.len() {
banks[idx].fill(idx as u8);
}
let memory = vec![0u8; 512];
memory[128..256] = banks[bank_index];
Memory{
bank_index,
banks,
memory
}
}
pub fn switch_bank(&mut self, idx: usize) {
self.bank_index = idx;
self.memory[128..256] = self.banks[self.bank_index];
}
}
Currently, this code does not compile, which is to be expected.
The following does work, but is not desirable:
memory[128..256].copy_from_slice(&banks[bank_index]);
This would result in much messier state-management code that I’d prefer to avoid.
The last thing I’ve considered is laying out every region of memory as a struct and implementing the Indexing traits. This is less elegant in my opinion, but it would likely work.
The last solution is what I’ll go with if this isn’t possible, but I thought I’d ask.