use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use cpclib_common::bitsets;
use num_enum::TryFromPrimitive;
mod chunks;
mod error;
pub mod flags;
mod memory;
pub mod parse;
#[cfg(feature = "snapshot")]
pub mod cli;
pub use chunks::*;
pub use error::*;
pub use flags::*;
pub use memory::*;
pub const HEADER_SIZE: usize = 256;
#[derive(Copy, Clone, Debug, PartialEq, Eq, TryFromPrimitive)]
#[repr(u8)]
pub enum SnapshotVersion {
V1 = 1,
V2,
V3
}
impl SnapshotVersion {
pub fn is_v3(self) -> bool {
if let SnapshotVersion::V3 = self {
true
}
else {
false
}
}
}
#[derive(Clone)]
#[allow(missing_docs)]
pub struct Snapshot {
header: [u8; HEADER_SIZE],
memory: SnapshotMemory,
memory_already_written: bitsets::DenseBitSet,
chunks: Vec<SnapshotChunk>,
pub debug: bool
}
impl std::fmt::Debug for Snapshot {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Snapshot ({{")?;
write!(f, "\theader: TODO")?;
write!(f, "memory: {:?}", &self.memory)?;
write!(f, "chunks: {:?}", &self.chunks)?;
write!(f, "}})")
}
}
impl Default for Snapshot {
fn default() -> Self {
Self {
header: [
0x4D, 0x56, 0x20, 0x2D, 0x20, 0x53, 0x4E, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x0C, 0x8D, 0xC0, 0x00, 0x3F, 0x28, 0x2E,
0x8E, 0x26, 0x00, 0x19, 0x1E, 0x00, 0x07, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFF, 0x1E, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x02, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x32, 0x00, 0x08, 0x02, 0x00, 0x04, 0x00, 0x01, 0x00, 0x02, 0x20, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00
],
memory: SnapshotMemory::default_128(),
chunks: Vec::new(),
memory_already_written: bitsets::DenseBitSet::with_capacity_and_state(PAGE_SIZE * 8, 0),
debug: false
}
}
}
impl Snapshot {
pub fn new_6128() -> Result<Self, String> {
let content = include_bytes!("cpc6128.sna").to_vec();
Self::from_buffer(content)
}
pub fn new_6128_v2() -> Result<Self, String> {
let content = include_bytes!("cpc6128_v2.sna").to_vec();
Self::from_buffer(content)
}
}
#[allow(missing_docs)]
#[allow(unused)]
impl Snapshot {
pub fn log<S: std::fmt::Display>(&self, msg: S) {
if self.debug {
println!("> {}", msg);
}
}
pub fn load<P: AsRef<Path>>(filename: P) -> Result<Self, String> {
let filename = filename.as_ref();
let mut file_content = {
let mut f = File::open(filename).map_err(|e| e.to_string())?;
let mut content = Vec::new();
f.read_to_end(&mut content);
content
};
Self::from_buffer(file_content)
}
pub fn from_buffer(mut file_content: Vec<u8>) -> Result<Self, String> {
let mut sna = Self::default();
sna.header
.copy_from_slice(file_content.drain(0..0x100).as_slice());
let memory_dump_size = sna.memory_size_header() as usize;
let version = sna.version_header();
assert!(memory_dump_size * 1024 <= file_content.len());
sna.memory = SnapshotMemory::new(file_content.drain(0..memory_dump_size * 1024).as_slice());
if version == 3 {
while let Some(chunk) = Self::read_chunk(&mut file_content, &mut sna) {
sna.chunks.push(chunk);
}
}
Ok(sna)
}
fn compute_memory_size_in_chunks(&self) -> u16 {
let nb_pages = self.chunks.iter().filter(|c| c.is_memory_chunk()).count() as u16;
nb_pages * 64
}
pub fn memory_size_header(&self) -> u16 {
u16::from(self.header[0x6B]) + 256 * u16::from(self.header[0x6C])
}
fn set_memory_size_header(&mut self, size: u16) {
self.header[0x6B] = (size % 256) as _;
self.header[0x6C] = (size / 256) as _;
}
pub fn version_header(&self) -> u8 {
self.header[0x10]
}
pub fn version(&self) -> SnapshotVersion {
self.version_header().try_into().unwrap()
}
pub fn fix_version(&self, version: SnapshotVersion) -> Self {
let mut cloned = self.clone();
match version {
SnapshotVersion::V1 => {
for idx in 0x6D..=0xFF {
cloned.header[idx] = 0;
}
}
SnapshotVersion::V2 => {
}
SnapshotVersion::V3 => {}
};
cloned.header[0x10] = version as u8;
if !cloned.chunks.is_empty() && version != SnapshotVersion::V3 {
let memory = self.memory_dump();
let memory_size = memory.len() / 1024;
if memory_size > 128 {
panic!("V1 or V2 snapshots cannot code more than 128kb of memory");
}
if memory_size != 0 && memory_size != 64 && memory_size != 128 {
panic!("Memory of {}kb", memory_size);
}
cloned.set_memory_size_header(memory_size as _);
cloned.memory = SnapshotMemory::new(&memory);
cloned.chunks.clear();
assert_eq!(cloned.chunks.len(), 0);
}
if version == SnapshotVersion::V3 && !self.has_memory_chunk() {
let chunks = cloned.memory.to_chunks();
for idx in 0..chunks.len() {
cloned.chunks.insert(idx, chunks[idx].clone());
}
cloned.memory = SnapshotMemory::default();
cloned.set_memory_size_header(0);
}
cloned
}
#[deprecated]
pub fn save_sna<P: AsRef<Path>>(&self, fname: P) -> Result<(), std::io::Error> {
self.save(fname, SnapshotVersion::V2)
}
pub fn save<P: AsRef<Path>>(
&self,
fname: P,
version: SnapshotVersion
) -> Result<(), std::io::Error> {
let mut buffer = File::create(fname.as_ref())?;
self.write(&mut buffer, version)
}
pub fn write<B: Write>(
&self,
buffer: &mut B,
version: SnapshotVersion
) -> Result<(), std::io::Error> {
let sna = self.fix_version(version);
buffer.write_all(&sna.header)?;
if sna.memory_size_header() > 0 {
assert_eq!(
sna.memory.memory().len(),
sna.memory_size_header() as usize * 1024
);
buffer.write_all(sna.memory.memory())?;
}
for chunk in &sna.chunks {
println!(
"Add chunk: {}",
chunk.code().iter().map(|c| *c as char).collect::<String>()
);
buffer.write_all(chunk.code())?;
buffer.write_all(&chunk.size_as_array())?;
buffer.write_all(chunk.data())?;
}
Ok(())
}
pub fn set_value(&mut self, flag: SnapshotFlag, value: u16) -> Result<(), SnapshotError> {
let offset = flag.offset();
match flag.elem_size() {
1 => {
if value > 255 {
Err(SnapshotError::InvalidValue)
}
else {
self.header[offset] = value as u8;
Ok(())
}
}
2 => {
self.header[offset] = (value % 256) as u8;
self.header[offset + 1] = (value / 256) as u8;
Ok(())
}
_ => panic!("Unable to handle size != 1 or 2")
}
}
pub fn get_value(&self, flag: &SnapshotFlag) -> FlagValue {
if flag.indice().is_some() {
let offset = flag.offset();
match flag.elem_size() {
1 => FlagValue::Byte(self.header[offset]),
2 => {
FlagValue::Word(
u16::from(self.header[offset + 1]) * 256 + u16::from(self.header[offset])
)
}
_ => panic!()
}
}
else {
let mut vals: Vec<FlagValue> = Vec::new();
for idx in 0..flag.nb_elems() {
let mut flag2 = *flag;
flag2.set_indice(idx).unwrap();
vals.push(self.get_value(&flag2));
}
FlagValue::Array(vals)
}
}
pub fn print_info(&self) {
println!("# Flags");
for flag in SnapshotFlag::enumerate().iter() {
println!("{:?} => {}", &flag, &self.get_value(flag));
}
println!("# Chunks");
for chunk in self.chunks() {
chunk.print_info();
}
}
}
impl Snapshot {
#[deprecated]
pub fn set_memory(&mut self, address: u32, value: u8) {
self.set_byte(address, value);
}
pub fn unwrap_memory_chunks(&mut self) {
if self.memory.is_empty() {
self.memory = SnapshotMemory::new(&self.memory_dump());
let mut idx = 0;
while idx < self.chunks.len() {
if self.chunks[idx].is_memory_chunk() {
self.chunks.remove(idx);
}
else {
idx += 1;
}
}
self.set_memory_size_header((self.memory.len() / 1024) as u16);
}
}
pub fn set_byte(&mut self, address: u32, value: u8) {
self.unwrap_memory_chunks();
let address = address as usize;
while self.memory.len() - 1 < address {
self.memory = self.memory.increased_size();
}
self.memory.memory_mut()[address] = value;
}
pub fn get_byte(&self, address: u32) -> u8 {
self.memory.memory()[address as usize]
}
pub fn memory_dump(&self) -> Vec<u8> {
let mut memory = self.memory.clone();
let mut max_memory = self.memory_size_header() as usize * 1024;
for chunk in &self.chunks {
if let Some(memory_chunk) = chunk.memory_chunk() {
let address = memory_chunk.abstract_address();
let content = memory_chunk.uncrunched_memory();
max_memory = address + 64 * 1024;
if memory.len() < max_memory {
memory = memory.increased_size();
}
memory.memory_mut()[address..max_memory].copy_from_slice(&content);
}
}
memory.memory()[..max_memory].to_vec()
}
pub fn has_memory_chunk(&self) -> bool {
self.chunks.iter().any(|c| c.is_memory_chunk())
}
pub fn memory_block(&self) -> &SnapshotMemory {
&self.memory
}
pub fn add_file(&mut self, fname: &str, address: usize) -> Result<(), SnapshotError> {
let f = File::open(fname).unwrap();
let data: Vec<u8> = f.bytes().map(Result::unwrap).collect();
let size = data.len();
self.log(format!(
"Add {} in 0x{:x} (0x{:x} bytes)",
fname, address, size
));
self.add_data(&data, address)
}
pub fn add_data(&mut self, data: &[u8], address: usize) -> Result<(), SnapshotError> {
if address + data.len() > 0x10000 * 2 {
Err(SnapshotError::NotEnougSpaceAvailable)
}
else {
if address < 0x10000 && (address + data.len()) >= 0x10000 {
eprintln!("[Warning] Start of file is in main memory (0x{:x}) and end of file is in extra banks (0x{:x}).", address, (address + data.len()));
}
for (idx, byte) in data.iter().enumerate() {
let current_pos = address + idx;
if self.memory_already_written.test(current_pos) {
eprintln!("[WARNING] Replace memory in 0x{:x}", current_pos);
}
self.memory.memory_mut()[current_pos] = *byte;
self.memory_already_written.set(current_pos);
}
Ok(())
}
}
}
impl Snapshot {
fn read_chunk(file_content: &mut Vec<u8>, _sna: &mut Self) -> Option<SnapshotChunk> {
if file_content.len() < 4 {
return None;
}
let code = file_content.drain(0..4).as_slice().to_vec();
let data_length = file_content.drain(0..4).as_slice().to_vec();
let data_length = {
let mut count = 0;
for i in 0..4 {
count = 256 * count + data_length[3 - i] as usize;
}
count
};
let content = file_content.drain(0..data_length).as_slice().to_vec();
let code = {
let mut new_code = [0; 4];
assert_eq!(code.len(), 4);
new_code.copy_from_slice(&code);
new_code
};
let chunk = match code {
[b'M', b'E', b'M', _] => MemoryChunk::from(code, content).into(), _ => UnknownChunk::from(code, content).into()
};
Some(chunk)
}
pub fn nb_chunks(&self) -> usize {
self.chunks.len()
}
pub fn chunks(&self) -> &[SnapshotChunk] {
&self.chunks
}
pub fn add_chunk<C: Into<SnapshotChunk>>(&mut self, c: C) {
self.chunks.push(c.into());
}
}
#[cfg(test)]
mod tests {
use super::SnapshotMemory;
#[test]
fn test_memory() {
assert!(SnapshotMemory::default().is_empty());
assert!(SnapshotMemory::default_64().is_64k());
assert!(SnapshotMemory::default_128().is_128k());
assert_eq!(SnapshotMemory::default().len(), 0);
assert_eq!(SnapshotMemory::default_64().len(), 64 * 1024);
assert_eq!(SnapshotMemory::default_128().len(), 128 * 1024);
assert_eq!(SnapshotMemory::default().memory().len(), 0);
assert_eq!(SnapshotMemory::default_64().memory().len(), 64 * 1024);
assert_eq!(SnapshotMemory::default_128().memory().len(), 128 * 1024);
}
#[test]
fn test_increase() {
let memory = SnapshotMemory::default();
assert!(memory.is_empty());
let memory = memory.increased_size();
assert!(memory.is_64k());
assert_eq!(memory.memory().len(), 64 * 1024);
let memory = memory.increased_size();
assert!(memory.is_128k());
assert_eq!(memory.memory().len(), 128 * 1024);
}
#[test]
#[should_panic]
fn test_increase2() {
SnapshotMemory::default_576().increased_size();
}
}