1use std::fs::File;
2use std::io::prelude::*;
3use std::ops::Deref;
4
5use cpclib_common::bitvec::vec::BitVec;
6use cpclib_common::camino::Utf8Path;
7#[cfg(feature = "cmdline")]
8use cpclib_common::parse_value;
9use cpclib_common::riff::{RiffChunk, RiffCode};
10#[cfg(feature = "cmdline")]
11use cpclib_common::winnow::error::ContextError;
12#[cfg(feature = "cmdline")]
13use cpclib_common::winnow::stream::AsBStr;
14
15mod chunks;
16mod error;
17pub mod flags;
18mod memory;
19pub mod parse;
20
21#[cfg(feature = "cmdline")]
22use cpclib_common::clap::{Arg, ArgAction, ArgMatches, Command};
23
24#[cfg(feature = "interactive")]
25pub mod cli;
26#[cfg(feature = "cmdline")]
27use std::str::FromStr;
28
29pub use chunks::*;
30#[cfg(feature = "cmdline")]
31use comfy_table::{Table, *};
32#[cfg(feature = "cmdline")]
33use cpclib_common::itertools::Itertools;
34pub use error::*;
35pub use flags::*;
36pub use memory::*;
37
38#[cfg(feature = "cmdline")]
39fn string_to_nb<S: AsRef<str>>(s: S) -> Result<u32, SnapshotError> {
40 let s = s.as_ref();
41 let mut bytes = s.as_bstr();
42 parse_value::<_, ContextError>(&mut bytes)
43 .map_err(|e| format!("Unable to parse {s}. {e}"))
44 .map_err(SnapshotError::AnyError)
45}
46
47pub const HEADER_SIZE: usize = 256;
71
72#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
73#[repr(u8)]
74pub enum SnapshotVersion {
75 V1 = 1,
77 V2,
79 V3
81}
82
83impl TryInto<SnapshotVersion> for u8 {
84 type Error = String;
85
86 fn try_into(self) -> Result<SnapshotVersion, Self::Error> {
87 match self {
88 1 => Ok(SnapshotVersion::V1),
89 2 => Ok(SnapshotVersion::V2),
90 3 => Ok(SnapshotVersion::V3),
91 _ => Err(format!("{self} is an invalid version number."))
92 }
93 }
94}
95
96impl SnapshotVersion {
97 pub fn is_v3(self) -> bool {
99 if let SnapshotVersion::V3 = self {
100 true
101 }
102 else {
103 false
104 }
105 }
106}
107
108#[derive(Clone)]
110#[allow(missing_docs)]
111pub struct Snapshot {
112 header: [u8; HEADER_SIZE],
114 memory: SnapshotMemory,
116 memory_already_written: BitVec,
117 chunks: Vec<SnapshotChunk>,
119
120 pub debug: bool
122}
123
124impl std::fmt::Debug for Snapshot {
125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 write!(f, "Snapshot ({{")?;
127 write!(f, "\theader: TODO")?;
128 write!(f, "memory: {:?}", &self.memory)?;
129 write!(f, "chunks: {:?}", &self.chunks)?;
130 write!(f, "}})")
131 }
132}
133
134impl Default for Snapshot {
135 fn default() -> Self {
136 let mut sna = Self {
137 header: [
138 0x4D,
139 0x56,
140 0x20,
141 0x2D,
142 0x20,
143 0x53,
144 0x4E,
145 0x41, 0x00,
147 0x00,
148 0x00,
149 0x00,
150 0x00,
151 0x00,
152 0x00,
153 0x00,
154 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, 0x04,
186 0x0A,
187 0x15,
188 0x1C,
189 0x18,
190 0x1D,
191 0x0C,
192 0x05,
193 0x0D,
194 0x16,
195 0x06,
196 0x17,
197 0x1E,
198 0x00,
199 0x1F,
200 0x0E,
201 0x04, 0x8D, 0xC0 & 0b00111111, 0x00, 0x3F,
206 0x28,
207 0x2E,
208 0x8E,
209 0x26,
210 0x00,
211 0x19,
212 0x1E,
213 0x00,
214 0x07,
215 0x00,
216 0x00,
217 0x30,
218 0x00,
219 0x00,
220 0x00,
221 0x00,
222 0x00, 0x00, 0x00, 0x00, 0x00, 0x82,
230 0x00,
231 0x00,
232 0x00,
233 0x00,
234 0x00,
235 0x00,
236 0x00,
237 0x00,
238 0x3F,
239 0x00,
240 0x00,
241 0x00,
242 0x00,
243 0x00,
244 0x00,
245 0x00,
246 0x00,
247 0x40, 0x00,
249 0x02,
250 0x00,
251 0x00,
252 0x00,
253 0x00,
254 0x00,
255 0x00,
256 0x00,
257 0x00,
258 0x00,
259 0x00,
260 0x00,
261 0x00,
262 0x00,
263 0x00,
264 0x00,
265 0x00,
266 0x00,
267 0x00,
268 0x00,
269 0x00,
270 0x00,
271 0x00,
272 0x00,
273 0x00,
274 0x00,
275 0x00,
276 0x00,
277 0x00,
278 0x00,
279 0x00,
280 0x00,
281 0x00,
282 0x00,
283 0x00,
284 0x00,
285 0x00,
286 0x00,
287 0x00,
288 0x00,
289 0x00,
290 0x00,
291 0x00,
292 0x00,
293 0x00,
294 0x00,
295 0x00,
296 0x00,
297 0x00,
298 0x00,
299 0x00,
300 0x00,
301 0x7F, 0x00,
303 0x00,
304 0x00,
305 0x00,
306 0x00,
307 0x00,
308 0x00,
309 0x00, 0x00,
312 0x00, 0x00, 0x00, 0x00, 0x00,
320 0x00, 0x00,
322 0x02,
323 0x00, 0x00,
326 0x00,
327 0x00,
328 0x00,
329 0x00,
330 0x00,
331 0x00,
332 0x00,
333 0x00,
334 0x00,
335 0x00,
336 0x00,
337 0x00,
338 0x00,
339 0x00,
340 0x00,
341 0x00,
342 0x00,
343 0x00,
344 0x00,
345 0x00,
346 0x00,
347 0x00,
348 0x00,
349 0x00,
350 0x00,
351 0x00,
352 0x00,
353 0x00,
354 0x00,
355 0x00,
356 0x00,
357 0x00,
358 0x00,
359 0x00,
360 0x00,
361 0x00,
362 0x00,
363 0x00,
364 0x00,
365 0x00,
366 0x00,
367 0x00,
368 0x00,
369 0x00,
370 0x00,
371 0x00,
372 0x00,
373 0x00,
374 0x00,
375 0x00,
376 0x00,
377 0x00,
378 0x00,
379 0x00,
380 0x00,
381 0x00,
382 0x00,
383 0x00,
384 0x00,
385 0x00,
386 0x00,
387 0x00,
388 0x00,
389 0x00,
390 0x00,
391 0x00,
392 0x00,
393 0x00,
394 0x00,
395 0x00,
396 0x00,
397 0x00,
398 0x00,
399 0x00,
400 0x00
401 ],
402 memory: SnapshotMemory::default_64(),
403 chunks: Vec::new(),
404 memory_already_written: BitVec::repeat(false, BANK_SIZE * 4), debug: false
406 };
407
408 let end_string = b"BND FRAMEWORK RULEZ!";
409 assert_eq!(end_string.len(), 20);
410 let start = sna.header.len() - 20;
411 sna.header[start..].copy_from_slice(end_string);
412
413 assert_eq!(
414 sna.memory.memory().len(),
415 sna.memory_size_header() as usize * 1024
416 );
417 sna
418 }
419}
420
421impl Snapshot {
422 pub fn new_6128() -> Result<Self, String> {
423 let content = include_bytes!("cpc6128.sna").to_vec();
424 Self::from_buffer(content)
425 }
426
427 pub fn new_6128_v2() -> Result<Self, String> {
428 let content = include_bytes!("cpc6128_v2.sna").to_vec();
429 Self::from_buffer(content)
430 }
431}
432
433#[allow(missing_docs)]
434#[allow(unused)]
435impl Snapshot {
436 pub fn log<S: std::fmt::Display>(&self, msg: S) {
437 if self.debug {
438 println!("> {msg}");
439 }
440 }
441
442 pub fn load<P: AsRef<Utf8Path>>(filename: P) -> Result<Self, String> {
443 let filename = filename.as_ref();
444
445 let mut file_content = {
447 let mut f = File::open(filename).map_err(|e| e.to_string())?;
448 let mut content = Vec::new();
449 f.read_to_end(&mut content);
450 content
451 };
452
453 Self::from_buffer(file_content)
454 }
455
456 pub fn from_buffer(mut file_content: Vec<u8>) -> Result<Self, String> {
457 let mut sna = Self::default();
458
459 if file_content.len() < 0x100 {
460 return Err("SNA file is invalid".to_owned());
461 }
462
463 sna.header
465 .copy_from_slice(file_content.drain(0..0x100).as_slice());
466 let memory_dump_size = sna.memory_size_header() as usize;
467 let version = sna.version_header();
468
469 assert!(memory_dump_size * 1024 <= file_content.len());
470 sna.memory = SnapshotMemory::new(file_content.drain(0..memory_dump_size * 1024).as_slice());
471
472 if version == 3 {
473 while let Some(chunk) = Self::read_chunk(&mut file_content, &mut sna) {
474 sna.chunks.push(chunk);
475 }
476 }
477
478 Ok(sna)
479 }
480
481 fn compute_memory_size_in_chunks(&self) -> u16 {
483 let nb_pages = self.chunks.iter().filter(|c| c.is_memory_chunk()).count() as u16;
484 nb_pages * 64
485 }
486
487 pub fn memory_size_header(&self) -> u16 {
488 u16::from(self.header[0x6B]) + 256 * u16::from(self.header[0x6C])
489 }
490
491 fn set_memory_size_header(&mut self, size: u16) {
492 self.header[0x6B] = (size % 256) as _;
493 self.header[0x6C] = (size / 256) as _;
494 }
495
496 pub fn version_header(&self) -> u8 {
497 self.header[0x10]
498 }
499
500 pub fn version(&self) -> SnapshotVersion {
501 self.version_header().try_into().unwrap()
502 }
503
504 pub fn fix_version(&self, version: SnapshotVersion) -> Self {
508 let mut cloned = self.clone();
510
511 match version {
513 SnapshotVersion::V1 => {
514 for idx in 0x6D..=0xFF {
515 cloned.header[idx] = 0;
516 }
517 },
518 SnapshotVersion::V2 => {
519 },
524 SnapshotVersion::V3 => {}
525 };
526
527 cloned.header[0x10] = version as u8;
529
530 if !cloned.chunks.is_empty() && version != SnapshotVersion::V3 {
532 let memory = self.memory_dump();
533 let memory_size = memory.len() / 1024;
534 if memory_size > 128 {
535 panic!("V1 or V2 snapshots cannot code more than 128kb of memory");
536 }
537 if memory_size != 0 && memory_size != 64 && memory_size != 128 {
538 panic!("Memory of {memory_size}kb");
539 }
540
541 cloned.set_memory_size_header(memory_size as _);
543
544 cloned.memory = SnapshotMemory::new(&memory);
546
547 cloned.chunks.clear();
549 assert_eq!(cloned.chunks.len(), 0);
550 }
551
552 if version == SnapshotVersion::V3 && !self.has_memory_chunk() {
554 let chunks = cloned.memory.to_chunks();
556 for idx in 0..chunks.len() {
557 cloned.chunks.insert(idx, chunks[idx].clone());
558 }
559 cloned.memory = SnapshotMemory::default();
560 cloned.set_memory_size_header(0);
561 }
562
563 cloned
564 }
565
566 #[deprecated]
568 pub fn save_sna<P: AsRef<Utf8Path>>(&self, fname: P) -> Result<(), std::io::Error> {
569 self.save(fname, SnapshotVersion::V2)
570 }
571
572 pub fn save<P: AsRef<Utf8Path>>(
573 &self,
574 fname: P,
575 version: SnapshotVersion
576 ) -> Result<(), std::io::Error> {
577 let mut buffer = File::create(fname.as_ref())?;
578 self.write_all(&mut buffer, version)
579 }
580
581 pub fn write_all<B: Write>(
582 &self,
583 buffer: &mut B,
584 version: SnapshotVersion
585 ) -> Result<(), std::io::Error> {
586 let sna = self.fix_version(version);
588
589 buffer.write_all(&sna.header)?;
591
592 if sna.memory_size_header() > 0 {
594 assert_eq!(
595 sna.memory.memory().len(),
596 sna.memory_size_header() as usize * 1024
597 );
598 buffer.write_all(sna.memory.memory())?;
599 }
600 for chunk in &sna.chunks {
604 chunk.riff().write_all(buffer)?;
605 }
606
607 Ok(())
608 }
609
610 pub fn set_value(&mut self, flag: SnapshotFlag, value: u16) -> Result<(), SnapshotError> {
612 let offset = flag.offset();
613 match flag.elem_size() {
614 1 => {
615 if value > 255 {
616 Err(SnapshotError::InvalidValue)
617 }
618 else {
619 self.header[offset] = value as u8;
620 Ok(())
621 }
622 },
623
624 2 => {
625 self.header[offset] = (value % 256) as u8;
626 self.header[offset + 1] = (value / 256) as u8;
627 Ok(())
628 },
629 _ => panic!("Unable to handle size != 1 or 2")
630 }
631 }
632
633 pub fn get_value(&self, flag: &SnapshotFlag) -> FlagValue {
634 if flag.indice().is_some() {
635 let offset = flag.offset();
637 match flag.elem_size() {
638 1 => FlagValue::Byte(self.header[offset]),
639 2 => {
640 FlagValue::Word(
641 u16::from(self.header[offset + 1]) * 256 + u16::from(self.header[offset])
642 )
643 },
644 _ => panic!()
645 }
646 }
647 else {
648 let mut vals: Vec<FlagValue> = Vec::new();
650 for idx in 0..flag.nb_elems() {
651 let mut flag2 = *flag;
653 flag2.set_indice(idx).unwrap();
654 vals.push(self.get_value(&flag2));
655 }
656 FlagValue::Array(vals)
657 }
658 }
659}
660
661impl Snapshot {
663 #[deprecated]
664 pub fn set_memory(&mut self, address: u32, value: u8) {
665 self.set_byte(address, value);
666 }
667
668 #[inline(always)]
669 pub fn nb_pages(&self) -> usize {
670 self.memory.nb_pages()
671 }
672
673 pub fn resize(&mut self, nb_pages: usize) {
675 self.unwrap_memory_chunks();
676
677 while self.nb_pages() < nb_pages {
678 self.memory = self.memory.increased_size();
679 }
680 while self.nb_pages() > nb_pages {
681 self.memory = self.memory.decreased_size();
682 }
683
684 self.set_memory_size_header(64 * self.nb_pages() as u16);
685 self.memory_already_written
686 .resize_with(self.nb_pages() * 0x1_0000, |_| false)
687 }
688
689 pub fn unwrap_memory_chunks(&mut self) {
692 if self.memory.is_empty() {
693 self.memory = SnapshotMemory::new(&self.memory_dump());
695
696 let mut idx = 0;
698 while idx < self.chunks.len() {
699 if self.chunks[idx].is_memory_chunk() {
700 self.chunks.remove(idx);
701 }
703 else {
704 idx += 1;
705 }
706 }
707
708 self.set_memory_size_header((self.memory.len() / 1024) as u16);
710 }
711 }
712
713 pub fn set_byte(&mut self, address: u32, value: u8) {
716 self.unwrap_memory_chunks();
717 let address = address as usize;
718
719 while self.memory.len() - 1 < address {
721 self.memory = self.memory.increased_size();
722 }
723
724 self.memory.memory_mut()[address] = value;
726 }
727
728 pub fn get_byte(&self, address: u32) -> u8 {
731 self.memory.memory()[address as usize]
732 }
733
734 pub fn memory_dump(&self) -> Vec<u8> {
736 let mut memory = self.memory.clone();
738
739 let mut max_memory = self.memory_size_header() as usize * 1024;
740
741 for chunk in &self.chunks {
743 if let Some(memory_chunk) = chunk.memory_chunk() {
744 let address = memory_chunk.abstract_address();
745 let content = memory_chunk.uncrunched_memory();
746 max_memory = address + 64 * 1024;
747
748 if memory.len() < max_memory {
749 memory = memory.increased_size();
750 }
751 memory.memory_mut()[address..max_memory].copy_from_slice(&content);
752 }
754 }
755
756 while memory.len() < max_memory {
757 memory = memory.increased_size();
758 }
759
760 memory.memory()[..max_memory].to_vec()
761 }
762
763 pub fn has_memory_chunk(&self) -> bool {
765 self.chunks.iter().any(|c| c.is_memory_chunk())
766 }
767
768 pub fn memory_block(&self) -> &SnapshotMemory {
770 &self.memory
771 }
772
773 pub fn add_file(&mut self, fname: &str, address: usize) -> Result<(), SnapshotError> {
775 let f = File::open(fname).unwrap();
776 let data: Vec<u8> = f.bytes().map(Result::unwrap).collect();
777 let size = data.len();
778
779 self.log(format!("Add {fname} in 0x{address:x} (0x{size:x} bytes)"));
780 self.add_data(&data, address)
781 }
782
783 pub fn add_data(&mut self, data: &[u8], address: usize) -> Result<(), SnapshotError> {
794 let last_used_address = address + data.len() - 1;
795 if last_used_address >= 0x10000 * 2 {
796 Err(SnapshotError::NotEnougSpaceAvailable)
797 }
798 else {
799 if address < 0x10000 && last_used_address >= 0x10000 {
800 eprintln!(
801 "[Warning] Start of file is in main memory (0x{:x}) and end of file is in extra banks (0x{:x}).",
802 address,
803 (address + data.len())
804 );
805 }
806 for (idx, byte) in data.iter().enumerate() {
809 let current_pos = address + idx;
810 if *self.memory_already_written.get(current_pos).unwrap() {
811 eprintln!("[WARNING] Replace memory in 0x{current_pos:x}");
812 }
813 self.memory.memory_mut()[current_pos] = *byte;
814 self.memory_already_written.set(current_pos, true);
815 }
816
817 Ok(())
818 }
819 }
820}
821
822impl Snapshot {
824 fn read_chunk(file_content: &mut Vec<u8>, _sna: &mut Self) -> Option<SnapshotChunk> {
826 if file_content.len() < 4 {
827 return None;
828 }
829
830 let chunk = RiffChunk::from_buffer(file_content);
831
832 let chunk: SnapshotChunk = match chunk.code().deref() {
833 [b'M', b'E', b'M', _] => MemoryChunk::from(chunk).into(), [b'B', b'R', b'K', b'S'] => WinapeBreakPointChunk::from(chunk).into(),
835 [b'B', b'R', b'K', b'C'] => AceBreakPointChunk::from(chunk).into(),
836 [b'S', b'Y', b'M', b'B'] => AceSymbolChunk::from(chunk).into(),
837 _ => UnknownChunk::from(chunk).into()
840 };
841
842 Some(chunk)
843 }
844
845 pub fn nb_chunks(&self) -> usize {
846 self.chunks.len()
847 }
848
849 pub fn chunks(&self) -> &[SnapshotChunk] {
850 &self.chunks
851 }
852
853 pub fn add_chunk<C: Into<SnapshotChunk>>(&mut self, c: C) {
854 self.chunks.push(c.into());
855 }
856
857 pub fn get_chunk<C: Into<RiffCode>>(&self, code: C) -> Option<&SnapshotChunk> {
858 let code = code.into();
859 self.chunks().iter().find(|chunk| chunk.code() == &code)
860 }
861}
862
863pub mod built_info {
864 include!(concat!(env!("OUT_DIR"), "/built.rs"));
865}
866
867#[cfg(feature = "cmdline")]
868pub fn print_info(sna: &Snapshot) {
869 let mut table = Table::new();
870 table.set_content_arrangement(ContentArrangement::Dynamic);
871 table.set_header(vec!["Flag", "Value"]);
872 table.add_rows(
873 SnapshotFlag::enumerate()
874 .iter()
875 .map(|flag| {
876 (
877 flag.comment().lines().map(|l| l.trim()).join("\n"),
878 sna.get_value(flag)
879 )
880 })
881 .map(|(f, v)| vec![f.to_owned(), v.to_string()])
882 );
883 println!("{table}");
884
885 println!("# Chunks");
886 for chunk in sna.chunks() {
887 chunk.print_info();
888 }
889}
890
891#[cfg(feature = "cmdline")]
892use cpclib_common::event::EventObserver;
893
894#[cfg(feature = "cmdline")]
896pub fn process<E: EventObserver>(matches: &ArgMatches, o: &E) -> Result<(), SnapshotError> {
897 if matches.get_flag("flags") {
900 for flag in SnapshotFlag::enumerate().iter() {
901 o.emit_stdout(&format!(
902 "{:?} / {:?} bytes.{}",
903 flag,
904 flag.elem_size(),
905 flag.comment()
906 ));
907 }
908 return Ok(());
909 }
910
911 let mut sna = if matches.contains_id("inSnapshot") {
913 let fname = matches.get_one::<String>("inSnapshot").unwrap();
914 let path = Utf8Path::new(&fname);
915 Snapshot::load(path)
916 .map_err(|e| SnapshotError::AnyError(format!("Unable to load file {fname}. {e}")))?
917 }
918 else {
919 Snapshot::default()
920 };
921
922 sna.debug = matches.contains_id("debug");
924
925 if matches.get_flag("info") {
926 print_info(&sna);
927 return Ok(());
928 }
929
930 #[cfg(feature = "interactive")]
931 if matches.get_flag("cli") {
932 let fname = matches.get_one::<String>("inSnapshot").unwrap();
933 cli::cli(fname, sna);
934 return Ok(());
935 }
936
937 if matches.contains_id("load") {
939 let loads = matches
940 .get_many::<String>("load")
941 .unwrap()
942 .collect::<Vec<_>>();
943 for i in 0..(loads.len() / 2) {
944 let fname = loads[i * 2];
945 let place = loads[i * 2 + 1];
946
947 let address = {
948 if place.starts_with("0x") {
949 u32::from_str_radix(&place[2..], 16).unwrap()
950 }
951 else if place.starts_with('0') {
952 u32::from_str_radix(&place[1..], 8).unwrap()
953 }
954 else {
955 place.parse::<u32>().unwrap()
956 }
957 };
958 sna.add_file(fname, address as usize)
959 .expect("Unable to add file");
960 }
961 }
962
963 if matches.contains_id("putData") {
965 let data = matches
966 .get_many::<String>("putData")
967 .unwrap()
968 .collect::<Vec<_>>();
969
970 for i in 0..(data.len() / 2) {
971 let address = string_to_nb(data[i * 2])?;
972 let value = string_to_nb(data[i * 2 + 1])?;
973 assert!(value < 0x100);
974
975 sna.set_byte(address, value as u8);
976 }
977 }
978
979 if matches.contains_id("getToken") {
981 for token in matches.get_many::<String>("getToken").unwrap() {
982 let token = SnapshotFlag::from_str(token).unwrap();
983 println!("{:?} => {}", &token, sna.get_value(&token));
984 }
985 return Ok(());
986 }
987
988 if matches.contains_id("setToken") {
990 let loads = matches
991 .get_many::<String>("setToken")
992 .unwrap()
993 .collect::<Vec<_>>();
994 for i in 0..(loads.len() / 2) {
995 let token = dbg!(loads[i * 2]);
997 let token = SnapshotFlag::from_str(token).unwrap();
998
999 let value = {
1000 let source = loads[i * 2 + 1];
1001 string_to_nb(source)?
1002 };
1003
1004 sna.set_value(token, value as u16).unwrap();
1006
1007 sna.log(format!(
1008 "Token {token:?} set at value {value} (0x{value:x})"
1009 ));
1010 }
1011 }
1012
1013 let fname = matches.get_one::<String>("OUTPUT").unwrap();
1014 let version = matches
1015 .get_one::<String>("version")
1016 .unwrap()
1017 .parse::<u8>()
1018 .unwrap()
1019 .try_into()
1020 .unwrap();
1021 sna.save(fname, version)
1022 .expect("Unable to save the snapshot");
1023
1024 Ok(())
1025}
1026
1027#[cfg(feature = "cmdline")]
1028pub fn build_arg_parser() -> Command {
1029 let desc_before = format!(
1030 "Profile {} compiled: {}",
1031 built_info::PROFILE,
1032 built_info::BUILT_TIME_UTC
1033 );
1034
1035 let cmd = Command::new("createSnapshot")
1036 .version(built_info::PKG_VERSION)
1037 .disable_version_flag(true)
1038 .author("Krusty/Benediction")
1039 .about("Amstrad CPC snapshot manipulation")
1040 .before_help(desc_before)
1041 .after_help("This tool tries to be similar than Ramlaid's one")
1042 .arg(Arg::new("info")
1043 .help("Display informations on the loaded snapshot")
1044 .long("info")
1045 .requires("inSnapshot")
1046 .action(ArgAction::SetTrue)
1047 )
1048 .arg(Arg::new("debug")
1049 .help("Display debugging information while manipulating the snapshot")
1050 .long("debug")
1051 .action(ArgAction::SetTrue)
1052 )
1053 .arg(Arg::new("OUTPUT")
1054 .help("Sets the output file to generate")
1055 .conflicts_with("flags")
1056 .conflicts_with("info")
1057 .conflicts_with("getToken")
1058 .last(true)
1059 .required(true))
1060 .arg(Arg::new("inSnapshot")
1061 .short('i')
1062 .long("inSnapshot")
1063 .value_name("INFILE")
1064 .number_of_values(1)
1065 .help("Load <INFILE> snapshot file")
1066 )
1067 .arg(Arg::new("load")
1068 .short('l')
1069 .long("load")
1070 .action(ArgAction::Append)
1071 .number_of_values(2)
1072 .help("Specify a file to include. -l fname address"))
1073 .arg(Arg::new("getToken")
1074 .short('g')
1075 .long("getToken")
1076 .action(ArgAction::Append)
1077 .number_of_values(1)
1078 .help("Display the value of a snapshot token")
1079 .requires("inSnapshot")
1080 )
1081 .arg(Arg::new("setToken")
1082 .short('s')
1083 .long("setToken")
1084 .action(ArgAction::Append)
1085 .number_of_values(2)
1086 .help("Set snapshot token <$1> to value <$2>\nUse <$1>:<val> to set array value\n\t\tex '-s CRTC_REG:6 20' : Set CRTC register 6 to 20"))
1087 .arg(Arg::new("putData")
1088 .short('p')
1089 .long("putData")
1090 .action(ArgAction::Append)
1091 .number_of_values(2)
1092 .help("Put <$2> byte at <$1> address in snapshot memory")
1093
1094 )
1095 .arg(Arg::new("version")
1096 .short('v')
1097 .long("version")
1098 .number_of_values(1)
1099 .value_parser(["1", "2", "3"])
1100 .help("Version of the saved snapshot.")
1101 .default_value("3")
1102 )
1103 .arg(Arg::new("flags")
1104 .help("List the flags and exit")
1105 .long("flags")
1106 .action(ArgAction::SetTrue)
1107 );
1108
1109 #[cfg(feature = "interactive")]
1110 let cmd = cmd.arg(
1111 Arg::new("cli")
1112 .help("Run the CLI interface for an interactive manipulation of snapshot")
1113 .long("cli")
1114 .requires("inSnapshot")
1115 .conflicts_with("OUTPUT")
1116 .action(ArgAction::SetTrue)
1117 );
1118
1119 cmd
1120}
1121
1122#[cfg(test)]
1123mod tests {
1124 use similar_asserts::assert_eq;
1125
1126 use super::SnapshotMemory;
1127 use crate::{BANK_SIZE, Snapshot};
1128
1129 #[test]
1130 fn test_resize() {
1131 let mut sna = Snapshot::default();
1132 assert_eq!(sna.nb_pages(), 1);
1133 assert_eq!(sna.memory_dump().len(), BANK_SIZE * 4);
1134
1135 sna.resize(2);
1136 assert_eq!(sna.nb_pages(), 2);
1137 assert_eq!(sna.memory_dump().len(), BANK_SIZE * 4 * 2);
1138 }
1139
1140 #[test]
1141 fn test_memory() {
1142 assert!(SnapshotMemory::default().is_empty());
1143 assert!(SnapshotMemory::default_64().is_64k());
1144 assert!(SnapshotMemory::default_128().is_128k());
1145
1146 assert_eq!(SnapshotMemory::default().len(), 0);
1147 assert_eq!(SnapshotMemory::default_64().len(), 64 * 1024);
1148 assert_eq!(SnapshotMemory::default_128().len(), 128 * 1024);
1149
1150 assert_eq!(SnapshotMemory::default().memory().len(), 0);
1151 assert_eq!(SnapshotMemory::default_64().memory().len(), 64 * 1024);
1152 assert_eq!(SnapshotMemory::default_128().memory().len(), 128 * 1024);
1153 }
1154
1155 #[test]
1156 fn test_increase() {
1157 let memory = SnapshotMemory::default();
1158 assert!(memory.is_empty());
1159
1160 let memory = memory.increased_size();
1161 assert!(memory.is_64k());
1162 assert_eq!(memory.memory().len(), 64 * 1024);
1163
1164 let memory = memory.increased_size();
1165 assert!(memory.is_128k());
1166 assert_eq!(memory.memory().len(), 128 * 1024);
1167 }
1168
1169 #[test]
1170 #[should_panic]
1171 fn test_increase2() {
1172 SnapshotMemory::default_576().increased_size();
1173 }
1174}