use std::str::FromStr;
use std::collections::{BTreeMap,HashMap};
use super::{FileImage,Packing,UnpackedData,Records,Error};
use super::{cpm,dos3x,fat,pascal,prodos};
use crate::commands::ItemType;
use crate::{STDRESULT,DYNERR};
pub mod r#as;
const A2_DOS: &str = "a2 dos";
const A2_PASCAL: &str = "a2 pascal";
const PRODOS: &str = "prodos";
const CPM: &str = "cpm";
const FAT: &str = "fat";
impl FileImage {
pub fn fimg_version() -> String {
"2.1.0".to_string()
}
pub fn mostly_copy(&mut self,fimg: Self,keep_path: bool,keep_time: bool,strip_dot_json: bool) {
let existing_path = self.full_path.clone();
let existing_created = self.created.clone();
let existing_accessed = self.accessed.clone();
let existing_modified = self.modified.clone();
*self = fimg;
if (keep_path && existing_path.len() > 0) || (!keep_path && self.full_path.len() == 0) {
self.full_path = existing_path;
}
if keep_time && existing_created.len() > 0 {
self.created = existing_created;
}
if keep_time && existing_accessed.len() > 0 {
self.accessed = existing_accessed;
}
if keep_time && existing_modified.len() > 0 {
self.modified = existing_modified;
}
let l = self.full_path.len();
if strip_dot_json && l > 5 && self.full_path.to_lowercase().ends_with(".json") {
self.full_path.truncate(l-5);
}
}
pub fn version_tuple(vers: &str) -> Result<(usize,usize,usize),DYNERR> {
let v: Vec<Result<usize,std::num::ParseIntError>> = vers.split(".").map(|s| usize::from_str(s)).collect();
if v.len() != 3 {
return Err(Box::new(Error::UnexpectedVersion));
}
Ok((v[0].clone()?,v[1].clone()?,v[2].clone()?))
}
pub fn test(dat: &[u8]) -> bool {
match str::from_utf8(dat) {
Ok(s) => match json::parse(s) {
Ok(parsed) => parsed.has_key("fimg_version") && parsed.has_key("file_system") && parsed.has_key("chunks"),
_ => false
},
_ => false
}
}
pub fn ordered_indices(&self) -> Vec<usize> {
let copy = self.chunks.clone();
let mut idx_list = copy.into_keys().collect::<Vec<usize>>();
idx_list.sort_unstable();
return idx_list;
}
pub fn end(&self) -> usize {
match self.ordered_indices().pop() {
Some(idx) => idx+1,
None => 0
}
}
pub fn get_eof(&self) -> usize {
Self::usize_from_truncated_le_bytes(&self.eof)
}
pub fn set_eof(&mut self,eof: usize) {
self.eof = Self::fix_le_vec(eof,self.eof.len());
}
pub fn get_ftype(&self) -> usize {
Self::usize_from_truncated_le_bytes(&self.fs_type)
}
pub fn get_aux(&self) -> usize {
Self::usize_from_truncated_le_bytes(&self.aux)
}
pub fn is_sparse(&self) -> bool {
let mut test = 0;
for i in self.ordered_indices() {
if i!=test {
return true;
}
test += 1;
}
false
}
pub fn sequence(&self) -> Vec<u8> {
let mut ans: Vec<u8> = Vec::new();
for chunk in self.ordered_indices() {
match self.chunks.get(&chunk) {
Some(v) => ans.append(&mut v.clone()),
_ => panic!("unreachable")
};
}
return ans;
}
pub fn sequence_limited(&self,max_len: usize) -> Vec<u8> {
let mut ans = self.sequence();
if max_len < ans.len() {
ans = ans[0..max_len].to_vec();
}
return ans;
}
pub fn desequence(&mut self, dat: &[u8], pad: Option<u8>) {
self.chunks = HashMap::new();
let mut mark = 0;
let mut idx = 0;
if dat.len()==0 {
self.eof = vec![0;self.eof.len()];
return;
}
loop {
let mut end = mark + self.chunk_len;
if end > dat.len() {
end = dat.len();
}
let chunk_dat = match (pad,end<mark+self.chunk_len) {
(Some(padding),true) => [dat[mark..end].to_vec(),vec![padding;self.chunk_len+mark-end]].concat(),
_ => dat[mark..end].to_vec()
};
self.chunks.insert(idx,chunk_dat);
mark = end;
if mark == dat.len() {
self.eof = Self::fix_le_vec(dat.len(),self.eof.len());
return;
}
idx += 1;
}
}
fn fix_le_vec(val: usize,exact_len: usize) -> Vec<u8> {
let mut ans = usize::to_le_bytes(val).to_vec();
let mut count = 0;
for byte in ans.iter().rev() {
if *byte>0 {
break;
}
count += 1;
}
for _i in 0..count {
ans.pop();
}
for _i in ans.len()..exact_len {
ans.push(0);
}
ans[0..exact_len].to_vec()
}
fn usize_from_truncated_le_bytes(bytes: &[u8]) -> usize {
let mut ans: usize = 0;
for i in 0..bytes.len() {
if i == usize::BITS as usize/8 {
break;
}
ans += (bytes[i] as usize) << (i*8);
}
ans
}
pub fn parse_hex_to_vec(key: &str,parsed: &json::JsonValue) -> Result<Vec<u8>,DYNERR> {
if let Some(s) = parsed[key].as_str() {
if let Ok(bytes) = hex::decode(s) {
return Ok(bytes);
}
}
log::error!("a record is missing in the file image");
return Err(Box::new(Error::FileImageFormat));
}
pub fn parse_usize(key: &str,parsed: &json::JsonValue) -> Result<usize,DYNERR> {
if let Some(val) = parsed[key].as_usize() {
return Ok(val);
}
log::error!("a record is missing in the file image");
return Err(Box::new(Error::FileImageFormat));
}
pub fn parse_str(key: &str,parsed: &json::JsonValue) -> Result<String,DYNERR> {
if let Some(s) = parsed[key].as_str() {
return Ok(s.to_string());
}
log::error!("a record is missing in the file image");
return Err(Box::new(Error::FileImageFormat));
}
pub fn from_json(json_str: &str) -> Result<FileImage,DYNERR> {
let parsed = json::parse(json_str)?;
let fimg_version = FileImage::parse_str("fimg_version",&parsed)?;
let vers_tup = Self::version_tuple(&fimg_version)?;
if vers_tup < (2,0,0) {
log::error!("file image v2 or higher is required");
return Err(Box::new(Error::FileFormat));
}
let fs = Self::parse_str("file_system",&parsed)?;
let chunk_len = Self::parse_usize("chunk_len", &parsed)?;
let fs_type = Self::parse_hex_to_vec("fs_type",&parsed)?;
let aux = Self::parse_hex_to_vec("aux",&parsed)?;
let eof = Self::parse_hex_to_vec("eof",&parsed)?;
let access = Self::parse_hex_to_vec("access",&parsed)?;
let created = Self::parse_hex_to_vec("created",&parsed)?;
let modified = Self::parse_hex_to_vec("modified",&parsed)?;
let version = Self::parse_hex_to_vec("version",&parsed)?;
let min_version = Self::parse_hex_to_vec("min_version",&parsed)?;
let accessed = match vers_tup >= (2,1,0) {
true => Self::parse_hex_to_vec("accessed",&parsed)?,
false => vec![]
};
let full_path = match vers_tup >= (2,1,0) {
true => Self::parse_str("full_path",&parsed)?,
false => String::new()
};
let mut chunks: HashMap<usize,Vec<u8>> = HashMap::new();
let map_obj = &parsed["chunks"];
if map_obj.entries().len()==0 {
log::warn!("file image contains metadata, but no data");
}
for (key,hex) in map_obj.entries() {
let prev_len = chunks.len();
if let Ok(num) = usize::from_str(key) {
if let Some(hex_str) = hex.as_str() {
if let Ok(dat) = hex::decode(hex_str) {
chunks.insert(num,dat);
}
}
}
if chunks.len()==prev_len {
log::error!("could not read hex string from chunk");
return Err(Box::new(Error::FileImageFormat));
}
}
return Ok(Self {
fimg_version,
file_system: fs.to_string(),
chunk_len,
eof,
fs_type,
aux,
access,
accessed,
created,
modified,
version,
min_version,
full_path,
chunks
});
}
pub fn to_json(&self,indent: Option<u16>) -> String {
let mut json_map = json::JsonValue::new_object();
let mut sorted : BTreeMap<usize,&[u8]> = BTreeMap::new();
for (c,v) in &self.chunks {
sorted.insert(*c,v);
}
for (c,v) in &sorted {
json_map[c.to_string()] = json::JsonValue::String(hex::encode_upper(v));
}
let ans = json::object! {
fimg_version: self.fimg_version.clone(),
file_system: self.file_system.clone(),
chunk_len: self.chunk_len,
eof: hex::encode_upper(self.eof.clone()),
fs_type: hex::encode_upper(self.fs_type.clone()),
aux: hex::encode_upper(self.aux.clone()),
access: hex::encode_upper(self.access.clone()),
accessed: hex::encode_upper(self.accessed.clone()),
created: hex::encode_upper(self.created.clone()),
modified: hex::encode_upper(self.modified.clone()),
version: hex::encode_upper(self.version.clone()),
min_version: hex::encode_upper(self.min_version.clone()),
full_path: self.full_path.clone(),
chunks: json_map
};
if let Some(spaces) = indent {
return json::stringify_pretty(ans, spaces);
} else {
return json::stringify(ans);
}
}
fn packer(&self) -> Box<dyn Packing> {
match self.file_system.as_str() {
A2_DOS => Box::new(dos3x::Packer::new()),
A2_PASCAL => Box::new(pascal::Packer::new()),
PRODOS => Box::new(prodos::Packer::new()),
CPM => Box::new(cpm::Packer::new()),
FAT => Box::new(fat::Packer::new()),
_ => panic!("illegal file system in file image")
}
}
pub fn set_path(&mut self, path: &str) -> STDRESULT {
self.packer().set_path(self,path)
}
pub fn get_load_address(&self) -> usize {
self.packer().get_load_address(self)
}
pub fn pack(&mut self, dat: &[u8], load_addr: Option<usize>) -> STDRESULT {
self.packer().pack(self, dat, load_addr)
}
pub fn unpack(&self) -> Result<UnpackedData,DYNERR> {
self.packer().unpack(self)
}
pub fn pack_raw(&mut self, dat: &[u8]) -> STDRESULT {
self.packer().pack_raw(self,dat)
}
pub fn unpack_raw(&self,trunc: bool) -> Result<Vec<u8>,DYNERR> {
self.packer().unpack_raw(self,trunc)
}
pub fn pack_bin(&mut self,dat: &[u8],load_addr: Option<usize>,trailing: Option<&[u8]>) -> STDRESULT {
self.packer().pack_bin(self,dat,load_addr,trailing)
}
pub fn unpack_bin(&self) -> Result<Vec<u8>,DYNERR> {
self.packer().unpack_bin(self)
}
pub fn pack_txt(&mut self, txt: &str) -> STDRESULT {
self.packer().pack_txt(self,txt)
}
pub fn unpack_txt(&self) -> Result<String,DYNERR> {
self.packer().unpack_txt(self)
}
pub fn pack_tok(&mut self,tok: &[u8],lang: ItemType,trailing: Option<&[u8]>) -> STDRESULT {
self.packer().pack_tok(self,tok,lang,trailing)
}
pub fn unpack_tok(&self) -> Result<Vec<u8>,DYNERR> {
self.packer().unpack_tok(self)
}
pub fn pack_rec_str(&mut self, json: &str) -> STDRESULT {
self.packer().pack_rec_str(self, json)
}
pub fn unpack_rec_str(&self,rec_len: Option<usize>,indent: Option<u16>) -> Result<String,DYNERR> {
self.packer().unpack_rec_str(self, rec_len, indent)
}
pub fn pack_rec(&mut self, recs: &Records) -> STDRESULT {
self.packer().pack_rec(self,recs)
}
pub fn unpack_rec(&self,rec_len: Option<usize>) -> Result<Records,DYNERR> {
self.packer().unpack_rec(self,rec_len)
}
pub fn pack_apple_single(&mut self, dat: &[u8], load_addr: Option<usize>) -> STDRESULT {
self.packer().pack_apple_single(self, dat, load_addr)
}
pub fn unpack_apple_single(&self) -> Result<Vec<u8>,DYNERR> {
self.packer().unpack_apple_single(self)
}
}