#![feature(vec_drain_as_slice)]
use bitsets;
use std::fmt;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use std::str::FromStr;
use num_enum::TryFromPrimitive;
use std::ops::AddAssign;
use std::ops::DerefMut;
mod flags;
mod chunks;
mod error;
pub use flags::*;
pub use chunks::*;
pub use error::*;
#[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
}
}
}
const PAGE_SIZE: usize = 0x4000;
const HEADER_SIZE: usize = 256;
#[derive(Clone, Copy)]
#[allow(clippy::large_enum_variant)]
pub enum SnapshotMemory {
Empty([u8; 0]),
SixtyFourKb([u8; PAGE_SIZE * 4]),
OneHundredTwentyHeightKb([u8; PAGE_SIZE * 8]),
}
impl Default for SnapshotMemory {
fn default() -> Self {
Self::Empty(Default::default())
}
}
impl std::fmt::Debug for SnapshotMemory {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let code = match self {
Self::Empty(_) => "Empty",
Self::SixtyFourKb(_) => "64kb",
Self::OneHundredTwentyHeightKb(_) => "128kb",
};
write!(f, "SnapshotMemory ({})", code)
}
}
#[allow(missing_docs)]
impl SnapshotMemory {
pub fn is_empty(&self) -> bool {
match self {
Self::Empty(_) => true,
_ => false,
}
}
pub fn is_64k(&self) -> bool {
match self {
Self::SixtyFourKb(_) => true,
_ => false,
}
}
pub fn is_128k(&self) -> bool {
match self {
Self::OneHundredTwentyHeightKb(_) => true,
_ => false,
}
}
fn memory(&self) -> &[u8] {
match self {
Self::Empty(ref mem) => mem,
Self::SixtyFourKb(ref mem) => mem,
Self::OneHundredTwentyHeightKb(ref mem) => mem,
}
}
fn memory_mut(&mut self) -> &mut [u8] {
match self {
Self::Empty(ref mut mem) => mem,
Self::SixtyFourKb(ref mut mem) => mem,
Self::OneHundredTwentyHeightKb(ref mut mem) => mem,
}
}
fn len(&self) -> usize {
match self {
Self::Empty(_x) => 0,
Self::SixtyFourKb(_) => 64 * 1024,
Self::OneHundredTwentyHeightKb(_) => 128 * 1024,
}
}
fn increased_size(self) -> Self {
match self {
Self::Empty(ref _mem) => Self::default_64(),
Self::SixtyFourKb(ref mem) => {
let mut new = Self::default_128();
new.memory_mut()[0..64 * 1024].copy_from_slice(mem);
new
}
Self::OneHundredTwentyHeightKb(ref _mem) => unreachable!(),
}
}
fn new(source: &[u8]) -> Self {
match source.len() {
0 => Self::default(),
0x10000 => Self::new_64(source),
0x20000 => Self::new_128(source),
_ => unreachable!(),
}
}
pub fn to_chunks(&self) -> Vec<SnapshotChunk> {
let memory = self.memory();
let mut chunks = Vec::new();
let mut current_idx = [b'M', b'E', b'M', b'0'];
let mut cursor = 0;
while cursor < memory.len() {
let next_cursor = cursor + 64 * 1024;
let current_memory = &memory[cursor..next_cursor.min(memory.len())];
let current_chunk = MemoryChunk::build(current_idx, current_memory, true);
chunks.push(SnapshotChunk::Memory(current_chunk));
cursor = next_cursor;
current_idx[3] += 1;
}
chunks
}
fn new_64(source: &[u8]) -> Self {
assert_eq!(source.len(), 64 * 1024);
let mut mem = [0; PAGE_SIZE * 4];
mem.copy_from_slice(source);
Self::SixtyFourKb(mem)
}
fn new_128(source: &[u8]) -> Self {
assert_eq!(source.len(), 128 * 1024);
let mut mem = [0; PAGE_SIZE * 8];
mem.copy_from_slice(source);
Self::OneHundredTwentyHeightKb(mem)
}
fn default_64() -> Self {
Self::SixtyFourKb([0; PAGE_SIZE * 4])
}
fn default_128() -> Self {
Self::OneHundredTwentyHeightKb([0; PAGE_SIZE * 8])
}
}
#[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,
}
}
}
#[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) -> Self {
let mut sna = Self::default();
let filename = filename.as_ref();
let mut file_content = {
let mut f = File::open(filename).expect("file not found");
let mut content = Vec::new();
f.read_to_end(&mut content);
content
};
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);
}
}
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 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 => {
for idx in 0x75..=0xff {
cloned.header[idx] = 0;
}
}
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() {
println!("Generate chunks from standard memory");
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
}
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()
}
#[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(&self, buffer: &mut File, 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())?;
}
println!("Memory header: {}", sna.memory_size_header() );
for chunk in &sna.chunks {
println!("Add chunk: {:?}", chunk.code());
buffer.write_all(chunk.code())?;
buffer.write_all(&chunk.size_as_array())?;
buffer.write_all(chunk.data())?;
}
Ok(())
}
pub fn memory_dump(&self) -> Vec<u8> {
let mut memory = self.memory;
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(())
}
}
pub fn set_memory(&mut self, address: u32, value: u8) {
let address = address as usize;
if self.memory.is_empty() {
self.memory = SnapshotMemory::new(&self.memory_dump());
let mut idx = 0;
while idx < self.chunks.len() {
if self.chunks[0].is_memory_chunk() {
self.chunks.remove(idx);
}
else {
idx +=1;
}
}
self.set_memory_size_header( (self.memory.len() / 1024) as u16);
}
self.memory.memory_mut()[address] = value;
}
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 + 0] = (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) {
for flag in SnapshotFlag::enumerate().iter() {
println!("{:?} => {}", &flag, &self.get_value(flag));
}
}
}
#[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_128().increased_size();
}
}