use crate::{address, sizes, VM};
use maikor_platform::mem::address::is_special_memory;
impl VM {
#[must_use]
pub fn read_word_mem(&self, addr: u16) -> (u16, usize) {
let mut value = self.memory[addr as usize] as u16;
value <<= 8;
value += self.memory[(addr + 1) as usize] as u16;
(value, 2)
}
#[must_use]
pub fn write_word_mem(&mut self, addr: u16, value: u16) -> usize {
let cost1 = self.write_byte_mem(addr, ((value >> 8) & 0xFF) as u8);
let cost2 = self.write_byte_mem(addr + 1, (value & 0xFF) as u8);
cost1 + cost2
}
}
impl VM {
#[inline(always)]
#[must_use]
pub fn read_byte_mem(&self, addr: u16) -> (u8, usize) {
(self.memory[addr as usize], 1)
}
#[must_use]
pub fn write_byte_mem(&mut self, addr: u16, value: u8) -> usize {
let addr_idx = addr as usize;
self.memory[addr_idx] = value;
let bank_update_cost = self.write_mem_change_to_bank(addr_idx, value);
let bank_load_cost = if is_special_memory(addr) {
self.load_banks(addr_idx, value as usize)
} else {
0
};
#[allow(clippy::manual_range_contains)] if ((addr_idx >= address::SOUND && addr_idx <= address::SOUND + sizes::SOUND)
|| (addr_idx >= address::WAVE_TABLE
&& addr_idx <= address::WAVE_TABLE + sizes::WAVE_TABLE))
&& self.sound.update(addr, value)
{
self.memory[address::SOUND..address::SOUND + sizes::SOUND]
.as_mut()
.fill(0);
}
1 + bank_load_cost + bank_update_cost
}
#[inline]
fn load_bank(&mut self, address: usize, size: usize, bank: *const u8) {
unsafe {
let dst = self.get_memory_mut(address, size).as_mut_ptr();
std::ptr::copy_nonoverlapping(bank, dst, size);
}
}
#[must_use]
fn load_banks(&mut self, addr: usize, value: usize) -> usize {
match addr {
address::CODE_BANK_1_ID => {
if value < self.code_banks.len() {
self.load_bank(
address::CODE_BANK_1,
sizes::CODE_BANK,
self.code_banks[value].as_ptr(),
);
return 20;
}
}
address::CODE_BANK_2_ID => {
if value < self.code_banks.len() {
self.load_bank(
address::CODE_BANK_2,
sizes::CODE_BANK,
self.code_banks[value].as_ptr(),
);
return 20;
}
}
address::RAM_BANK_1_ID => {
if value < self.ram_banks.len() {
self.load_bank(
address::RAM_BANK_1,
sizes::RAM_BANK,
self.ram_banks[value].as_ptr(),
);
return 20;
}
}
address::RAM_BANK_2_ID => {
if value < self.ram_banks.len() {
self.load_bank(
address::RAM_BANK_2,
sizes::RAM_BANK,
self.ram_banks[value].as_ptr(),
);
return 20;
}
}
address::SAVE_BANK_ID => {
if value < self.save_banks.len() {
self.load_bank(
address::SAVE_BANK,
sizes::SAVE_BANK,
self.save_banks[value].as_ptr(),
);
return 20;
}
}
address::ATLAS1_BANK_ID => {
if value < self.atlas_banks.len() {
self.load_bank(
address::ATLAS1,
sizes::ATLAS,
self.atlas_banks[value].as_ptr(),
);
return 20;
}
}
address::ATLAS2_BANK_ID => {
if value < self.atlas_banks.len() {
self.load_bank(
address::ATLAS2,
sizes::ATLAS,
self.atlas_banks[value].as_ptr(),
);
return 20;
}
}
address::ATLAS3_BANK_ID => {
if value < self.atlas_banks.len() {
self.load_bank(
address::ATLAS3,
sizes::ATLAS,
self.atlas_banks[value].as_ptr(),
);
return 20;
}
}
address::ATLAS4_BANK_ID => {
if value < self.atlas_banks.len() {
self.load_bank(
address::ATLAS4,
sizes::ATLAS,
self.atlas_banks[value].as_ptr(),
);
return 20;
}
}
address::CONTROLLER_TYPE => {
if value < self.controller_graphics_banks.len() {
self.load_bank(
address::CONTROLLER_GRAPHICS,
sizes::CONTROLLER_GRAPHICS,
self.controller_graphics_banks[value].as_ptr(),
);
return 20;
}
}
_ => {}
}
0
}
#[inline(always)]
pub fn get_memory_mut(&mut self, start: usize, len: usize) -> &mut [u8] {
&mut self.memory[start..start + len]
}
fn write_mem_change_to_bank(&mut self, addr: usize, value: u8) -> usize {
if is_inside_ram_bank_1(addr) {
let idx = self.memory[address::RAM_BANK_1_ID] as usize;
if idx < self.ram_banks.len() {
let ram_bank = &mut self.ram_banks[idx];
let ram_bank_addr = addr - address::RAM_BANK_1;
ram_bank[ram_bank_addr] = value;
return 1;
} else {
self.fail(format!(
"Attempted load RAM bank {}, but only {} available",
idx,
self.ram_banks.len()
));
}
} else if is_inside_ram_bank_2(addr) {
let idx = self.memory[address::RAM_BANK_1_ID] as usize;
if idx < self.ram_banks.len() {
let ram_bank = &mut self.ram_banks[idx];
let ram_bank_addr = addr - address::RAM_BANK_2;
ram_bank[ram_bank_addr] = value;
return 1;
} else {
self.fail(format!(
"Attempted load RAM bank {}, but only {} available",
idx,
self.ram_banks.len()
));
}
} else if is_inside_save_bank(addr) {
let bank_id = self.memory[address::SAVE_BANK_ID] as usize;
if bank_id < self.save_banks.len() {
let save_bank = &mut self.save_banks[bank_id];
let save_bank_addr = addr - address::SAVE_BANK;
save_bank[save_bank_addr] = value;
self.save_dirty_flag[bank_id] = true;
return 1;
} else {
self.fail(format!(
"Attempted load save bank {}, but only {} available",
bank_id,
self.save_banks.len()
));
}
}
0
}
}
#[inline(always)]
#[allow(clippy::manual_range_contains)] fn is_inside_ram_bank_1(addr: usize) -> bool {
addr >= address::RAM_BANK_1 && addr < address::RAM_BANK_1 + sizes::RAM_BANK
}
#[inline(always)]
#[allow(clippy::manual_range_contains)] fn is_inside_ram_bank_2(addr: usize) -> bool {
addr >= address::RAM_BANK_2 && addr < address::RAM_BANK_2 + sizes::RAM_BANK
}
#[inline(always)]
#[allow(clippy::manual_range_contains)] fn is_inside_save_bank(addr: usize) -> bool {
addr >= address::SAVE_BANK && addr < address::SAVE_BANK + sizes::SAVE_BANK
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn byte_mem_write() {
let mut vm = VM::new_test();
let cost1 = vm.write_byte_mem(2, 124);
let cost2 = vm.write_byte_mem(875, 1);
assert_eq!(vm.memory[2], 124);
assert_eq!(vm.memory[875], 1);
assert_eq!(cost1, 1);
assert_eq!(cost2, 1);
}
#[test]
fn word_mem_write() {
let mut vm = VM::new_test();
let cost = vm.write_word_mem(875, 10);
assert_eq!(vm.memory[875], 0);
assert_eq!(vm.memory[876], 10);
assert_eq!(cost, 2);
}
#[test]
fn byte_mem_read() {
let mut vm = VM::new_test();
vm.memory[12] = 56;
let (value, cost) = vm.read_byte_mem(12);
assert_eq!(value, 56);
assert_eq!(cost, 1);
}
#[test]
fn word_mem_read() {
let mut vm = VM::new_test();
vm.memory[12] = 1;
vm.memory[13] = 56;
let (value, cost) = vm.read_word_mem(12);
assert_eq!(value, 312);
assert_eq!(cost, 2);
}
}