1pub mod fs;
104pub mod lang;
105pub mod bios;
106pub mod img;
107pub mod commands;
108
109use img::DiskImage;
110use img::tracks::DiskFormat;
111use fs::DiskFS;
112use std::io::Read;
113use std::fmt::Write;
114use log::{warn,info,debug,error};
115use regex::Regex;
116use hex;
117
118type DYNERR = Box<dyn std::error::Error>;
119type STDRESULT = Result<(),Box<dyn std::error::Error>>;
120
121const KNOWN_FILE_EXTENSIONS: &str = "2mg,2img,dsk,d13,do,nib,po,woz,imd,td0,img,ima";
122const MAX_FILE_SIZE: u64 = 1 << 26;
123
124pub fn save_img(disk: &mut Box<dyn DiskFS>,img_path: &str) -> STDRESULT {
126 std::fs::write(img_path,disk.get_img().to_bytes())?;
127 Ok(())
128}
129
130fn try_img(mut img: Box<dyn DiskImage>,maybe_fmt: Option<&DiskFormat>) -> Result<Option<Box<dyn DiskFS>>,DYNERR> {
135 if let Some(fmt) = maybe_fmt {
136 img.change_format(fmt.clone())?;
137 }
138 if fs::dos3x::Disk::test_img(&mut img,maybe_fmt) {
139 info!("identified DOS 3.x file system");
140 return Ok(Some(Box::new(fs::dos3x::Disk::from_img(img)?)));
141 }
142 if fs::prodos::Disk::test_img(&mut img) {
143 info!("identified ProDOS file system");
144 return Ok(Some(Box::new(fs::prodos::Disk::from_img(img)?)));
145 }
146 if fs::pascal::Disk::test_img(&mut img) {
147 info!("identified Pascal file system");
148 return Ok(Some(Box::new(fs::pascal::Disk::from_img(img)?)));
149 }
150 if fs::fat::Disk::test_img(&mut img) {
151 info!("identified FAT file system");
152 return Ok(Some(Box::new(fs::fat::Disk::from_img(img,None)?)));
153 }
154 if fs::fat::Disk::test_img_dos1x(&mut img) {
155 info!("identified MS-DOS 1.x file system");
156 return Ok(Some(Box::new(fs::fat::Disk::from_img_dos1x(img)?)));
157 }
158 let dpb_list = match img.what_am_i() {
160 img::DiskImageType::DO | img::DiskImageType::NIB | img::DiskImageType::DOT2MG | img::DiskImageType::WOZ1 => vec![
161 bios::dpb::A2_525],
162 img::DiskImageType::WOZ2 => vec![
163 bios::dpb::A2_525],
165 img::DiskImageType::IMD | img::DiskImageType::TD0 => vec![
166 bios::dpb::CPM1,
167 bios::dpb::SSSD_525,
168 bios::dpb::SSDD_525_OFF1,
169 bios::dpb::SSDD_525_OFF3,
170 bios::dpb::SSDD_3,
171 bios::dpb::DSDD_525_OFF1,
172 bios::dpb::TRS80_M2,
173 bios::dpb::NABU],
174 _ => vec![]
175 };
176 for dpb in &dpb_list {
177 if fs::cpm::Disk::test_img(&mut img,dpb,[3,1,0]) {
178 info!("identified CP/M file system on {}",dpb);
179 return Ok(Some(Box::new(fs::cpm::Disk::from_img(img,dpb.clone(),[3,1,0])?)));
180 }
181 }
182 return Ok(None);
183}
184
185pub fn create_fs_from_bytestream(disk_img_data: &Vec<u8>,maybe_ext: Option<&str>,maybe_fmt: Option<&DiskFormat>) -> Result<Box<dyn DiskFS>,DYNERR> {
189 let ext = match maybe_ext {
190 Some(x) => x.to_string().to_lowercase(),
191 None => "".to_string()
192 };
193 if disk_img_data.len() < 100 {
194 return Err(Box::new(img::Error::ImageSizeMismatch));
195 }
196 debug!("matching image type {}",ext);
197 if img::imd::file_extensions().contains(&ext) || ext=="" {
198 if let Ok(img) = img::imd::Imd::from_bytes(disk_img_data) {
199 info!("identified IMD image");
200 if let Some(disk) = try_img(Box::new(img),maybe_fmt)? {
201 return Ok(disk);
202 }
203 }
204 }
205 if img::woz1::file_extensions().contains(&ext) || ext=="" {
206 if let Ok(img) = img::woz1::Woz1::from_bytes(disk_img_data) {
207 info!("identified woz1 image");
208 if let Some(disk) = try_img(Box::new(img),maybe_fmt)? {
209 return Ok(disk);
210 }
211 }
212 }
213 if img::woz2::file_extensions().contains(&ext) || ext=="" {
214 if let Ok(img) = img::woz2::Woz2::from_bytes(disk_img_data) {
215 info!("identified woz2 image");
216 if let Some(disk) = try_img(Box::new(img),maybe_fmt)? {
217 return Ok(disk);
218 }
219 }
220 }
221 if img::dot2mg::file_extensions().contains(&ext) || ext=="" {
222 if let Ok(img) = img::dot2mg::Dot2mg::from_bytes(disk_img_data) {
223 info!("identified 2mg image");
224 if let Some(disk) = try_img(Box::new(img),maybe_fmt)? {
225 return Ok(disk);
226 }
227 }
228 }
229 if img::td0::file_extensions().contains(&ext) || ext=="" {
230 if let Ok(img) = img::td0::Td0::from_bytes(disk_img_data) {
231 info!("identified td0 image");
232 if let Some(disk) = try_img(Box::new(img),maybe_fmt)? {
233 return Ok(disk);
234 }
235 }
236 }
237 if img::nib::file_extensions().contains(&ext) || ext=="" {
238 if let Ok(img) = img::nib::Nib::from_bytes(disk_img_data) {
239 info!("Possible nib/nb2 image");
240 if let Some(disk) = try_img(Box::new(img),maybe_fmt)? {
241 return Ok(disk);
242 }
243 }
244 }
245 if img::dsk_d13::file_extensions().contains(&ext) || ext=="" {
246 if let Ok(img) = img::dsk_d13::D13::from_bytes(disk_img_data) {
247 info!("Possible D13 image");
248 if let Some(disk) = try_img(Box::new(img),maybe_fmt)? {
249 return Ok(disk);
250 }
251 }
252 }
253 if img::dsk_do::file_extensions().contains(&ext) || ext=="" {
254 if let Ok(img) = img::dsk_do::DO::from_bytes(disk_img_data) {
255 info!("Possible DO image");
256 if let Some(disk) = try_img(Box::new(img),maybe_fmt)? {
257 return Ok(disk);
258 }
259 }
260 }
261 if img::dsk_po::file_extensions().contains(&ext) || ext=="" {
262 if let Ok(img) = img::dsk_po::PO::from_bytes(disk_img_data) {
263 info!("Possible PO image");
264 if let Some(disk) = try_img(Box::new(img),maybe_fmt)? {
265 return Ok(disk);
266 }
267 }
268 }
269 if img::dsk_img::file_extensions().contains(&ext) || ext=="" {
270 if let Ok(img) = img::dsk_img::Img::from_bytes(disk_img_data) {
271 info!("Possible IMG image");
272 if let Some(disk) = try_img(Box::new(img),maybe_fmt)? {
273 return Ok(disk);
274 }
275 }
276 }
277 warn!("cannot match any file system");
278 return Err(Box::new(fs::Error::FileSystemMismatch));
279}
280
281pub fn create_img_from_bytestream(disk_img_data: &Vec<u8>,maybe_ext: Option<&str>) -> Result<Box<dyn DiskImage>,DYNERR> {
285 let ext = match maybe_ext {
286 Some(x) => x.to_string().to_lowercase(),
287 None => "".to_string()
288 };
289 if disk_img_data.len() < 100 {
290 return Err(Box::new(img::Error::ImageSizeMismatch));
291 }
292 debug!("matching image type {}",ext);
293 if img::imd::file_extensions().contains(&ext) || ext=="" {
294 if let Ok(img) = img::imd::Imd::from_bytes(disk_img_data) {
295 info!("identified IMD image");
296 return Ok(Box::new(img));
297 }
298 }
299 if img::woz1::file_extensions().contains(&ext) || ext=="" {
300 if let Ok(img) = img::woz1::Woz1::from_bytes(disk_img_data) {
301 info!("identified woz1 image");
302 return Ok(Box::new(img));
303 }
304 }
305 if img::woz2::file_extensions().contains(&ext) || ext=="" {
306 if let Ok(img) = img::woz2::Woz2::from_bytes(disk_img_data) {
307 info!("identified woz2 image");
308 return Ok(Box::new(img));
309 }
310 }
311 if img::dot2mg::file_extensions().contains(&ext) || ext=="" {
312 if let Ok(img) = img::dot2mg::Dot2mg::from_bytes(disk_img_data) {
313 info!("identified 2mg image");
314 return Ok(Box::new(img));
315 }
316 }
317 if img::td0::file_extensions().contains(&ext) || ext=="" {
318 if let Ok(img) = img::td0::Td0::from_bytes(disk_img_data) {
319 info!("identified td0 image");
320 return Ok(Box::new(img));
321 }
322 }
323 if img::nib::file_extensions().contains(&ext) || ext=="" {
324 if let Ok(img) = img::nib::Nib::from_bytes(disk_img_data) {
325 info!("Possible nib/nb2 image");
326 return Ok(Box::new(img));
327 }
328 }
329 if img::dsk_d13::file_extensions().contains(&ext) || ext=="" {
330 if let Ok(img) = img::dsk_d13::D13::from_bytes(disk_img_data) {
331 info!("Possible D13 image");
332 return Ok(Box::new(img));
333 }
334 }
335 if img::dsk_do::file_extensions().contains(&ext) || ext=="" {
338 if let Ok(img) = img::dsk_do::DO::from_bytes(disk_img_data) {
339 info!("Possible DO image");
340 if ext=="do" {
341 return Ok(Box::new(img));
342 }
343 if let Ok(Some(_)) = try_img(Box::new(img),None) {
344 if let Ok(copy) = img::dsk_do::DO::from_bytes(disk_img_data) {
345 return Ok(Box::new(copy));
346 }
347 }
348 debug!("reject DO based on FS heuristics")
349 }
350 }
351 if img::dsk_po::file_extensions().contains(&ext) || ext=="" {
352 if let Ok(img) = img::dsk_po::PO::from_bytes(disk_img_data) {
353 info!("Possible PO image");
354 if ext=="po" {
355 return Ok(Box::new(img));
356 }
357 if let Ok(Some(_)) = try_img(Box::new(img),None) {
358 if let Ok(copy) = img::dsk_po::PO::from_bytes(disk_img_data) {
359 return Ok(Box::new(copy));
360 }
361 }
362 debug!("reject PO based on FS heuristics")
363 }
364 }
365 if img::dsk_img::file_extensions().contains(&ext) || ext=="" {
366 if let Ok(img) = img::dsk_img::Img::from_bytes(disk_img_data) {
367 info!("Possible IMG image");
368 return Ok(Box::new(img));
369 }
370 }
371 warn!("cannot match any image format");
372 return Err(Box::new(img::Error::ImageTypeMismatch));
373}
374
375fn buffer_file(path: &str,max: u64) -> Result<Vec<u8>,DYNERR> {
377 let mut f = std::fs::OpenOptions::new().read(true).open(path)?;
378 match f.metadata()?.len() <= max {
379 true => {
380 let mut buf = Vec::new();
381 f.read_to_end(&mut buf)?;
382 Ok(buf)
383 },
384 false => Err(Box::new(img::Error::ImageSizeMismatch))
385 }
386}
387
388pub fn create_img_from_stdin() -> Result<Box<dyn DiskImage>,DYNERR> {
391 let mut disk_img_data = Vec::new();
392 if atty::is(atty::Stream::Stdin) {
393 error!("pipe a disk image or use `-d` option");
394 return Err(Box::new(commands::CommandError::InvalidCommand));
395 }
396 std::io::stdin().read_to_end(&mut disk_img_data)?;
397 create_img_from_bytestream(&disk_img_data,None)
398}
399
400pub fn create_img_from_file(img_path: &str) -> Result<Box<dyn DiskImage>,DYNERR> {
405 let disk_img_data = buffer_file(img_path,MAX_FILE_SIZE)?;
406 let maybe_ext = match img_path.split('.').last() {
407 Some(ext) if KNOWN_FILE_EXTENSIONS.contains(&ext.to_lowercase()) => Some(ext),
408 _ => None
409 };
410 create_img_from_bytestream(&disk_img_data,maybe_ext)
411}
412
413pub fn create_img_from_file_or_stdin(maybe_img_path: Option<&String>) -> Result<Box<dyn DiskImage>,DYNERR> {
415 match maybe_img_path {
416 Some(img_path) => create_img_from_file(img_path),
417 None => create_img_from_stdin()
418 }
419}
420
421pub fn create_fs_from_stdin(maybe_fmt: Option<&DiskFormat>) -> Result<Box<dyn DiskFS>,DYNERR> {
425 let mut disk_img_data = Vec::new();
426 if atty::is(atty::Stream::Stdin) {
427 error!("pipe a disk image or use `-d` option");
428 return Err(Box::new(commands::CommandError::InvalidCommand));
429 }
430 std::io::stdin().read_to_end(&mut disk_img_data)?;
431 create_fs_from_bytestream(&disk_img_data, None, maybe_fmt)
432}
433
434pub fn create_fs_from_file(img_path: &str,maybe_fmt: Option<&DiskFormat>) -> Result<Box<dyn DiskFS>,DYNERR> {
440 let disk_img_data = buffer_file(img_path,MAX_FILE_SIZE)?;
441 let maybe_ext = match img_path.split('.').last() {
442 Some(ext) if KNOWN_FILE_EXTENSIONS.contains(&ext.to_lowercase()) => Some(ext),
443 _ => None
444 };
445 create_fs_from_bytestream(&disk_img_data,maybe_ext,maybe_fmt)
446}
447
448fn create_fs_from_file_or_stdin(maybe_img_path: Option<&String>,maybe_fmt: Option<&DiskFormat>) -> Result<Box<dyn DiskFS>,DYNERR> {
450 match maybe_img_path {
451 Some(img_path) => create_fs_from_file(img_path,maybe_fmt),
452 None => create_fs_from_stdin(maybe_fmt)
453 }
454}
455
456pub fn display_block(start_addr: usize,block: &Vec<u8>) {
458 let mut slice_start = 0;
459 loop {
460 let row_label = start_addr + slice_start;
461 let mut slice_end = slice_start + 16;
462 if slice_end > block.len() {
463 slice_end = block.len();
464 }
465 let slice = block[slice_start..slice_end].to_vec();
466 let txt: Vec<u8> = slice.iter().map(|c| match *c {
467 x if x<32 => '.' as u8,
468 x if x<127 => x,
469 _ => '.' as u8
470 }).collect();
471 let neg_txt: Vec<u8> = slice.iter().map(|c| match *c {
472 x if x>=160 && x<255 => x - 128,
473 _ => 46
474 }).collect();
475 print!("{:04X} : ",row_label);
476 for byte in slice {
477 print!("{:02X} ",byte);
478 }
479 for _blank in slice_end..slice_start+16 {
480 print!(" ");
481 }
482 print!("|+| {} ",String::from_utf8_lossy(&txt));
483 for _blank in slice_end..slice_start+16 {
484 print!(" ");
485 }
486 println!("|-| {}",String::from_utf8_lossy(&neg_txt));
487 slice_start += 16;
488 if slice_end==block.len() {
489 break;
490 }
491 }
492}
493
494pub fn escaped_ascii_from_bytes(bytes: &Vec<u8>,escape_cc: bool,inverted: bool) -> String {
500 let mut result = String::new();
501 let (lb,ub) = match (escape_cc,inverted) {
502 (true,false) => (0x20,0x7e),
503 (false,false) => (0x00,0x7f),
504 (true,true) => (0xa0,0xfe),
505 (false,true) => (0x80,0xff)
506 };
507 for i in 0..bytes.len() {
508 if bytes[i]>=lb && bytes[i]<=ub {
509 if inverted {
510 result += std::str::from_utf8(&[bytes[i]-0x80]).expect("unreachable");
511 } else {
512 result += std::str::from_utf8(&[bytes[i]]).expect("unreachable");
513 }
514 } else {
515 let mut temp = String::new();
516 write!(&mut temp,"\\x{:02X}",bytes[i]).expect("unreachable");
517 result += &temp;
518 }
519 }
520 return result;
521}
522
523pub fn escaped_ascii_to_bytes(s: &str,inverted: bool,caps: bool) -> Vec<u8> {
531 let mut ans: Vec<u8> = Vec::new();
532 let hex_patt = Regex::new(r"\\x[0-9A-Fa-f][0-9A-Fa-f]").expect("unreachable");
533 let mut hexes = hex_patt.find_iter(s);
534 let mut maybe_hex = hexes.next();
535 let mut curs = 0;
536 let mut skip = 0;
537 for c in s.chars() {
538
539 if skip>0 {
540 skip -= 1;
541 continue;
542 }
543 if let Some(hex) = maybe_hex {
544 if curs==hex.start() {
545 ans.append(&mut hex::decode(s.get(curs+2..curs+4).unwrap()).expect("unreachable"));
546 curs += 4;
547 maybe_hex = hexes.next();
548 skip = 3;
549 continue;
550 }
551 }
552
553 if c.is_ascii() {
554 let mut buf: [u8;1] = [0;1];
555 if caps {
556 c.to_uppercase().next().unwrap().encode_utf8(&mut buf);
557 } else {
558 c.encode_utf8(&mut buf);
559 }
560 ans.push(buf[0] + match inverted { true => 128, false => 0 });
561 }
562 curs += 1;
563 }
564 return ans;
565}
566
567pub struct JsonCursor {
569 key: Vec<String>,
570 sibling: Vec<usize>,
571 leaf_key: String
572}
573
574impl JsonCursor {
575 pub fn new() -> Self {
576 Self {
577 key: Vec::new(),
578 sibling: vec![0],
579 leaf_key: String::new()
580 }
581 }
582 pub fn next<'a>(&mut self,obj: &'a json::JsonValue) -> Option<(String,&'a json::JsonValue)> {
586 assert!(self.key.len()+1==self.sibling.len());
587 let depth = self.key.len();
588 let pos = self.sibling[depth];
589 let mut curr = obj;
590 for i in 0..depth {
591 curr = &curr[&self.key[i]];
592 }
593 let mut entry = curr.entries();
594 for _i in 0..pos {
595 entry.next();
596 }
597 match entry.next() {
598 None => {
599 if depth==0 {
600 return None;
601 }
602 self.key.pop();
603 self.sibling.pop();
604 return self.next(obj);
605 }
606 Some((key,val)) => {
607 self.sibling[depth] += 1;
608 if val.is_object() {
609 self.sibling.push(0);
610 self.key.push(key.to_string());
611 return self.next(obj);
612 }
613 self.leaf_key = key.to_string();
614 return Some((key.to_string(),val));
615 }
616 }
617 }
618 pub fn parent<'a>(&self,obj: &'a json::JsonValue) -> Option<&'a json::JsonValue> {
619 assert!(self.key.len()+1==self.sibling.len());
620 let depth = self.key.len();
621 if depth==0 {
622 return None;
623 }
624 let mut curr = obj;
625 for i in 0..depth {
626 curr = &curr[&self.key[i]];
627 }
628 Some(curr)
629 }
630 pub fn key_path(&self) -> Vec<String> {
633 let mut ans: Vec<String> = Vec::new();
634 for i in 0..self.key.len() {
635 ans.push(self.key[i].clone())
636 }
637 ans.push(self.leaf_key.clone());
638 ans
639 }
640 pub fn key_path_string(&self) -> String {
644 let mut ans = String::new();
645 for i in 0..self.key.len() {
646 ans += "/";
647 ans += &self.key[i];
648 }
649 ans += "/";
650 ans += &self.leaf_key;
651 ans
652 }
653}
654
655#[test]
656fn test_json_cursor() {
657 let mut curs = JsonCursor::new();
658 let s = "{
659 \"root_str\": \"01\",
660 \"obj1\": {
661 \"list1\": [1,3,5],
662 \"str1\": \"hello\"
663 },
664 \"num1\": 1000,
665 \"obj2\": {
666 \"null1\": null
667 }
668 }";
669 let obj = json::parse(s).expect("could not parse test string");
670 let mut leaves: Vec<(String,&json::JsonValue,Vec<String>)> = Vec::new();
671 while let Some((key,leaf)) = curs.next(&obj) {
672 leaves.push((key,leaf,curs.key_path()));
673 }
674 assert_eq!(leaves.len(),5);
675
676 assert_eq!(leaves[0].0,"root_str");
677 assert_eq!(leaves[0].1.as_str().unwrap(),"01");
678 assert_eq!(leaves[0].2,vec!["root_str"]);
679
680 assert_eq!(leaves[1].0,"list1");
681 assert!(leaves[1].1.is_array());
682 assert_eq!(leaves[1].2,vec!["obj1","list1"]);
683
684 assert_eq!(leaves[2].0,"str1");
685 assert_eq!(leaves[2].1.as_str().unwrap(),"hello");
686 assert_eq!(leaves[2].2,vec!["obj1","str1"]);
687
688 assert_eq!(leaves[3].0,"num1");
689 assert_eq!(leaves[3].1.as_u16().unwrap(),1000);
690 assert_eq!(leaves[3].2,vec!["num1"]);
691
692 assert_eq!(leaves[4].0,"null1");
693 assert!(leaves[4].1.is_null());
694 assert_eq!(leaves[4].2,vec!["obj2","null1"]);
695}
696