use core::fmt;
use core::ops::{Bound, Range, RangeBounds};
use std::rc::Rc;
use std::io::{self, Read};
mod extension;
#[cfg(feature = "snapshot")] pub mod serde;
pub use extension::*;
pub const MEM16K_SIZE : usize = 0x4000;
pub const MEM32K_SIZE : usize = 2 * MEM16K_SIZE;
pub const MEM48K_SIZE : usize = 3 * MEM16K_SIZE;
pub const MEM64K_SIZE : usize = 4 * MEM16K_SIZE;
pub const MEM128K_SIZE: usize = 8 * MEM16K_SIZE;
pub const MEM8K_SIZE : usize = MEM16K_SIZE / 2;
pub const SCREEN_SIZE: u16 = 0x1B00;
pub type ScreenArray = [u8;SCREEN_SIZE as usize];
pub type ExRom = Rc<[u8]>;
#[non_exhaustive]
#[derive(Debug)]
pub enum ZxMemoryError {
InvalidPageIndex,
InvalidBankIndex,
UnsupportedAddressRange,
UnsupportedExRomPaging,
InvalidExRomSize,
Io(io::Error)
}
impl std::error::Error for ZxMemoryError {}
impl fmt::Display for ZxMemoryError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", match self {
ZxMemoryError::InvalidPageIndex => "Memory page index is out of range",
ZxMemoryError::InvalidBankIndex => "Memory bank index is out of range",
ZxMemoryError::UnsupportedAddressRange => "Address range is not supported",
ZxMemoryError::UnsupportedExRomPaging => "EX-ROM mapping is not supported",
ZxMemoryError::InvalidExRomSize => "EX-ROM size is smaller than the memory page size",
ZxMemoryError::Io(err) => return err.fmt(f)
})
}
}
impl From<ZxMemoryError> for io::Error {
fn from(err: ZxMemoryError) -> Self {
match err {
ZxMemoryError::Io(err) => err,
e => io::Error::new(io::ErrorKind::InvalidInput, e)
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[repr(u8)]
pub enum MemoryKind {
Rom,
Ram
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct MemPageOffset {
pub kind: MemoryKind,
pub index: u8,
pub offset: u16
}
#[derive(Debug, PartialEq)]
pub enum PageMutSlice<'a> {
Rom(&'a mut [u8]),
Ram(&'a mut [u8])
}
impl<'a> PageMutSlice<'a> {
pub fn as_mut_slice(&mut self) -> &mut [u8] {
match self {
PageMutSlice::Rom(slice)|PageMutSlice::Ram(slice) => slice
}
}
pub fn into_mut_slice(self) -> &'a mut[u8] {
match self {
PageMutSlice::Rom(slice)|PageMutSlice::Ram(slice) => slice
}
}
pub fn is_rom(&self) -> bool {
match self {
PageMutSlice::Rom(..) => true,
_ => false
}
}
pub fn as_mut_rom(&mut self) -> Option<&mut [u8]> {
match self {
PageMutSlice::Rom(slice) => Some(slice),
_ => None
}
}
pub fn into_mut_rom(self) -> Option<&'a mut [u8]> {
match self {
PageMutSlice::Rom(slice) => Some(slice),
_ => None
}
}
pub fn is_ram(&self) -> bool {
match self {
PageMutSlice::Ram(..) => true,
_ => false
}
}
pub fn as_mut_ram(&mut self) -> Option<&mut [u8]> {
match self {
PageMutSlice::Ram(slice) => Some(slice),
_ => None
}
}
pub fn into_mut_ram(self) -> Option<&'a mut [u8]> {
match self {
PageMutSlice::Ram(slice) => Some(slice),
_ => None
}
}
}
pub type Result<T> = core::result::Result<T, ZxMemoryError>;
pub trait ZxMemory {
const PAGE_SIZE: usize = 0x4000;
const ROM_SIZE: usize;
const RAMTOP: u16;
const PAGES_MAX: u8;
const SCR_BANKS_MAX: usize;
const ROM_BANKS_MAX: usize;
const RAM_BANKS_MAX: usize;
fn reset(&mut self);
fn read(&self, addr: u16) -> u8;
fn read16(&self, addr: u16) -> u16;
fn read_screen(&self, screen_bank: usize, addr: u16) -> u8;
fn write(&mut self, addr: u16, val: u8);
fn write16(&mut self, addr: u16, val: u16);
fn mem_ref(&self) -> &[u8];
fn mem_mut(&mut self) -> &mut[u8];
fn screen_ref(&self, screen_bank: usize) -> Result<&ScreenArray>;
fn screen_mut(&mut self, screen_bank: usize) -> Result<&mut ScreenArray>;
fn page_kind(&self, page: u8) -> Result<MemoryKind>;
fn page_bank(&self, page: u8) -> Result<(MemoryKind, usize)>;
fn page_ref(&self, page: u8) -> Result<&[u8]>;
fn page_mut(&mut self, page: u8) -> Result<&mut[u8]>;
fn rom_bank_ref(&self, rom_bank: usize) -> Result<&[u8]>;
fn rom_bank_mut(&mut self, rom_bank: usize) -> Result<&mut[u8]>;
fn ram_bank_ref(&self, ram_bank: usize) -> Result<&[u8]>;
fn ram_bank_mut(&mut self, ram_bank: usize) -> Result<&mut[u8]>;
fn map_rom_bank(&mut self, rom_bank: usize, page: u8) -> Result<()>;
fn map_exrom(&mut self, _exrom_bank: ExRom, _page: u8) -> Result<()> {
Err(ZxMemoryError::UnsupportedExRomPaging)
}
fn unmap_exrom(&mut self, _exrom_bank: &ExRom) { }
fn is_exrom_at(&self, _page: u8) -> bool { false }
fn has_mapped_exrom(&self, _exrom_bank: &ExRom) -> bool { false }
fn map_ram_bank(&mut self, ram_bank: usize, page: u8) -> Result<()>;
fn page_index_at(&self, address: u16) -> Result<MemPageOffset> {
let index = (address / Self::PAGE_SIZE as u16) as u8;
let offset = address % Self::PAGE_SIZE as u16;
let kind = self.page_kind(index)?;
Ok(MemPageOffset {kind, index, offset})
}
fn rom_ref(&self) -> &[u8] {
&self.mem_ref()[0..Self::ROM_SIZE]
}
fn rom_mut(&mut self) -> &mut [u8] {
&mut self.mem_mut()[0..Self::ROM_SIZE]
}
fn ram_ref(&self) -> &[u8] {
&self.mem_ref()[Self::ROM_SIZE..]
}
fn ram_mut(&mut self) -> &mut [u8] {
&mut self.mem_mut()[Self::ROM_SIZE..]
}
fn load_into_rom<R: Read>(&mut self, mut rd: R) -> Result<()> {
let slice = self.rom_mut();
rd.read_exact(slice).map_err(ZxMemoryError::Io)
}
fn load_into_rom_bank<R: Read>(&mut self, rom_bank: usize, mut rd: R) -> Result<()> {
let slice = self.rom_bank_mut(rom_bank)?;
rd.read_exact(slice).map_err(ZxMemoryError::Io)
}
fn iter_pages<A: RangeBounds<u16>>(
&self,
address_range: A,
) -> Result<MemPageRefIter<'_, Self>> {
let range = normalize_address_range(address_range, 0, Self::RAMTOP)
.map_err(|_| ZxMemoryError::UnsupportedAddressRange)?;
let cursor = range.start;
let end = range.end;
Ok(MemPageRefIter { mem: self, cursor, end })
}
fn for_each_page_mut<A: RangeBounds<u16>, F>(
&mut self,
address_range: A,
mut f: F
) -> Result<()>
where for<'a> F: FnMut(PageMutSlice<'a>) -> Result<()>
{
let range = normalize_address_range(address_range, 0, Self::RAMTOP)
.map_err(|_| ZxMemoryError::UnsupportedAddressRange)?;
let cursor = range.start;
let end = range.end;
let iter = MemPageMutIter { mem: self, cursor, end };
for page in iter {
f(page)?
}
Ok(())
}
fn load_into_mem<A: RangeBounds<u16>, R: Read>(&mut self, address_range: A, mut rd: R) -> Result<()> {
self.for_each_page_mut(address_range, |page| {
match page {
PageMutSlice::Rom(slice)|PageMutSlice::Ram(slice) => {
rd.read_exact(slice).map_err(ZxMemoryError::Io)
}
}
})
}
fn fill_mem<R, F>(&mut self, address_range: R, mut f: F) -> Result<()>
where R: RangeBounds<u16>, F: FnMut() -> u8
{
self.for_each_page_mut(address_range, |page| {
for p in page.into_mut_slice().iter_mut() {
*p = f()
}
Ok(())
})
}
}
pub struct MemPageRefIter<'a, Z: ?Sized> {
mem: &'a Z,
cursor: usize,
end: usize
}
impl<'a, Z: ZxMemory + ?Sized> Iterator for MemPageRefIter<'a, Z> {
type Item = &'a [u8];
fn next(&mut self) -> Option<Self::Item> {
let cursor = self.cursor;
let end = self.end;
if cursor < end {
let MemPageOffset { index, offset, .. } = self.mem.page_index_at(cursor as u16).unwrap();
let offset = offset as usize;
let page = self.mem.page_ref(index).unwrap();
let read_len = end - cursor;
let read_end = page.len().min(offset + read_len);
let read_page = &page[offset..read_end];
self.cursor += read_page.len();
Some(read_page)
}
else {
None
}
}
}
struct MemPageMutIter<'a, Z: ?Sized> {
mem: &'a mut Z,
cursor: usize,
end: usize
}
impl<'a, Z: ZxMemory + ?Sized> Iterator for MemPageMutIter<'a, Z> {
type Item = PageMutSlice<'a>;
fn next(&mut self) -> Option<Self::Item> {
let cursor = self.cursor;
let end = self.end;
if cursor < end {
let MemPageOffset { kind, index, offset } = self.mem.page_index_at(cursor as u16).unwrap();
let offset = offset as usize;
let page = self.mem.page_mut(index).unwrap();
let page = unsafe { core::mem::transmute::<&mut[u8], &'a mut[u8]>(page) };
let read_len = end - cursor;
let read_end = page.len().min(offset + read_len);
let read_page = &mut page[offset..read_end];
self.cursor += read_page.len();
match kind {
MemoryKind::Rom => {
Some(PageMutSlice::Rom(read_page))
},
MemoryKind::Ram => {
Some(PageMutSlice::Ram(read_page))
}
}
}
else {
None
}
}
}
enum AddressRangeError {
StartBoundTooLow,
EndBoundTooHigh
}
fn normalize_address_range<R: RangeBounds<u16>>(
range: R,
min_inclusive: u16,
max_inclusive: u16
) -> core::result::Result<Range<usize>, AddressRangeError>
{
let start = match range.start_bound() {
Bound::Included(start) => if *start < min_inclusive {
return Err(AddressRangeError::StartBoundTooLow)
} else { *start as usize },
Bound::Excluded(start) => if *start < min_inclusive.saturating_sub(1) {
return Err(AddressRangeError::StartBoundTooLow)
} else { *start as usize + 1 },
Bound::Unbounded => min_inclusive as usize
};
let end = match range.end_bound() {
Bound::Included(end) => if *end > max_inclusive {
return Err(AddressRangeError::EndBoundTooHigh)
} else { *end as usize + 1},
Bound::Excluded(end) => if *end > max_inclusive.saturating_add(1) {
return Err(AddressRangeError::EndBoundTooHigh)
} else { *end as usize },
Bound::Unbounded => max_inclusive as usize + 1
};
Ok(start..end)
}