use anyhow::{Context as _, Result, bail};
use crate::{bus::Bus, system::Device};
#[non_exhaustive]
#[derive(Debug)]
pub struct Memory {
pub data: Vec<u8>,
pub end: u16,
pub start: u16,
}
impl Default for Memory {
#[inline]
fn default() -> Self {
Self {
start: 0x0000,
end: 0xBFFF,
data: vec![0; 0xC000],
}
}
}
impl Device for Memory {
#[inline]
fn tick(&mut self, bus: &mut Bus) {
if bus.mem && self.in_range(bus.addr) {
match (bus.mem, bus.write) {
(true, false) => {
let data = self.get(bus.addr);
bus.write_data(data);
}
(true, true) => {
self.set(bus.addr, bus.data);
}
_ => {}
}
}
}
}
impl Memory {
#[inline]
#[must_use]
pub fn get(&self, addr: u16) -> u8 {
let phys_addr = addr.strict_sub(self.start);
self.data
.get(usize::from(phys_addr))
.copied()
.unwrap_or_default()
}
#[inline]
#[must_use]
pub fn in_range(&self, addr: u16) -> bool {
self.start <= addr && addr <= self.end
}
#[inline]
pub fn load(&mut self, addr: u16, data: &[u8]) -> Result<()> {
if !self.in_range(addr) {
bail!("out of range")
}
let phys_addr = addr.saturating_sub(self.start);
let start = usize::from(phys_addr);
let end = start.checked_add(data.len()).context("data too long")?;
let slice = self.data.get_mut(start..end).context("out of bounds")?;
slice.copy_from_slice(data);
Ok(())
}
#[inline]
pub fn set(&mut self, addr: u16, val: u8) {
let phys_addr = addr.saturating_sub(self.start);
if let Some(loc) = self.data.get_mut(usize::from(phys_addr)) {
*loc = val;
}
}
}
#[cfg(test)]
#[expect(clippy::expect_used, reason = "test")]
mod tests {
use super::*;
#[test]
fn load_checks_bounds_correctly() {
let mut mem = Memory::default();
mem.load(0x0000, &[0x00]).expect("valid load failed");
mem.load(0xBFFF, &[0x00]).expect("valid load failed");
mem.load(0xBFFE, &[0x00, 0x00, 0x00])
.expect_err("invalid load succeeded");
mem.load(0xC000, &[0x00])
.expect_err("invalid load succeeded");
mem.load(0xFFFF, &[0x00, 0x00])
.expect_err("invalid load succeeded");
}
}