cpclib_sna/
lib.rs

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
47/// ! Re-implementation of createsnapshot by Ramlaid/Arkos
48/// ! in rust by Krusty/Benediction
49
50/// Original options
51///
52/// {'i',"inSnapshot",0,1,1,"Load <$1> snapshot file"},
53/// {'l',"loadFileData",0,0,2,"Load <$1> file data at <$2> address in snapshot memory (or use AMSDOS header load address if <$2> is negative)"},
54/// {'p',"putData",0,0,2,"Put <$2> byte at <$1> address in snapshot memory"},
55/// {'s',"setToken",0,0,2,"Set snapshot token <$1> to value <$2>\n\t\t"
56/// "Use <$1>:<val> to set array value\n\t\t"
57/// "ex '-s CRTC_REG:6 20' : Set CRTC register 6 to 20"},
58/// {'x',"clearMemory",0,1,0,"Clear snapshot memory"},
59/// {'r',"romDeconnect",0,1,0,"Disconnect lower and upper rom"},
60/// {'e',"enableInterrupt",0,1,0,"Enable interrupt"},
61/// {'d',"disableInterrupt",0,1,0,"Disable interrupt"},
62/// {'c',"configFile",0,1,1,"Load a config file with createSnapshot option"},
63/// {'t',"tokenList",0,1,0,"Display setable snapshot token ID"},
64/// {'o',"output",0,0,3,"Output <$3> bytes of data from address <$2> to file <$1>"},
65/// {'f',"fillData",0,0,3,"Fill snapshot from <$1> over <$2> bytes, with <$3> datas"},
66/// {'g',"fillText",0,0,3,"Fill snapshot from <$1> over <$2> bytes, with <$3> text"},
67/// {'j',"loadIniFile",0,1,1,"Load <$1> init file"},
68/// {'k',"saveIniFile",0,1,1,"Save <$1> init file"},
69
70pub const HEADER_SIZE: usize = 256;
71
72#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
73#[repr(u8)]
74pub enum SnapshotVersion {
75    /// Version 1 of Snapshsots
76    V1 = 1,
77    /// Version 2 of Snapshsots
78    V2,
79    /// Version 3 of Snapshsots (use of chunks)
80    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    /// Check if snapshot ius V3 version
98    pub fn is_v3(self) -> bool {
99        if let SnapshotVersion::V3 = self {
100            true
101        }
102        else {
103            false
104        }
105    }
106}
107
108/// Snapshot V3 representation. Can be saved in snapshot V1 or v2.
109#[derive(Clone)]
110#[allow(missing_docs)]
111pub struct Snapshot {
112    /// Header of the snaphsot
113    header: [u8; HEADER_SIZE],
114    /// Memory for V2 snapshot or V3 before saving
115    memory: SnapshotMemory,
116    memory_already_written: BitVec,
117    /// list of chuncks; memory chuncks are removed once memory is written
118    chunks: Vec<SnapshotChunk>,
119
120    // nothing to do with the snapshot. Should be moved elsewhere
121    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, // MV - SNA
146                0x00,
147                0x00,
148                0x00,
149                0x00,
150                0x00,
151                0x00,
152                0x00,
153                0x00,
154                0x03, // version
155                0x00, // F
156                0x00, // A
157                0x00, // C
158                0x00, // B
159                0x00, // E
160                0x00, // D
161                0x00, // L
162                0x00, // H
163                0x00, // R
164                0x00, // I
165                0x00, // IFF0
166                0x00, // IFF1
167                0x00, // IXL
168                0x00, // IXH
169                0x00, // IYL
170                0x00, // IYH
171                0x00, // SP L
172                0xC0, // SP h
173                0x00, // PCL
174                0x00, // PCH
175                0x01, // interput mode
176                0x00, // F'
177                0x00, // A'
178                0x00, // C'
179                0x00, // B'
180                0x00, // E'
181                0x00, // D'
182                0x00, // L'
183                0x00, // H'
184                0x00, // selected pen
185                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,              // palette
202                0x8D,              // gate array multi conf
203                0xC0 & 0b00111111, // ram cfg
204                0x00,              // crtc reg selected
205                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, // crtc values
223                0x00, // rom selection
224                0x00, // 0xFF
225                // PPI A
226                0x00, // 0x1E
227                // PPI B
228                0x00, // PPI C
229                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, // 0x80 <= nb of kilobytes
248                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, // printer strobe
302                0x00,
303                0x00,
304                0x00,
305                0x00,
306                0x00,
307                0x00,
308                0x00,
309                0x00, // 0x32
310                // CRTC horizontal character counter register
311                0x00,
312                0x00, // 0x08
313                // CRTC character-line counter register
314                0x00, // 0x02
315                // CRTC raster-line counter register
316                0x00, // CRTC vertical total adjust counter register
317                0x00, // 0x04
318                //  	CRTC horizontal sync width counter
319                0x00,
320                0x00, // 0x01
321                0x00,
322                0x02,
323                0x00, // 0x20
324                // ga interrupt scanline
325                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), // 64kbits
405            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        // Read the full content of the file
446        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        // Copy the header
464        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    /// Count the number of kilobytes that are within chunks
482    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    /// Create a new snapshot that contains only information understandable
505    /// by the required version
506    /// TODO return an error in case of failure instead of panicing
507    pub fn fix_version(&self, version: SnapshotVersion) -> Self {
508        // Clone the snapshot in order to patch it
509        let mut cloned = self.clone();
510
511        // Delete un-needed data
512        match version {
513            SnapshotVersion::V1 => {
514                for idx in 0x6D..=0xFF {
515                    cloned.header[idx] = 0;
516                }
517            },
518            SnapshotVersion::V2 => {
519                // for idx in 0x75..=0xFF {
520                //     cloned.header[idx] = 0;
521                // }
522                // unused but not set to 0
523            },
524            SnapshotVersion::V3 => {}
525        };
526
527        // Write the version number
528        cloned.header[0x10] = version as u8;
529
530        // We have to modify the memory coding and remove the chunks
531        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            // specify the right memory size ...
542            cloned.set_memory_size_header(memory_size as _);
543
544            // ... and replace it by the expected content
545            cloned.memory = SnapshotMemory::new(&memory);
546
547            // remove all the chunks
548            cloned.chunks.clear();
549            assert_eq!(cloned.chunks.len(), 0);
550        }
551
552        // Compress memory chunks for V3
553        if version == SnapshotVersion::V3 && !self.has_memory_chunk() {
554            // println!("Generate chunks from standard memory");
555            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    /// Save the snapshot V2 on disc
567    #[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        // Convert the snapshot to ensure header is correct
587        let sna = self.fix_version(version);
588
589        // Write header
590        buffer.write_all(&sna.header)?;
591
592        // Write main memory if any
593        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        // println!("Memory header: {}", sna.memory_size_header());
601
602        // Write chunks if any
603        for chunk in &sna.chunks {
604            chunk.riff().write_all(buffer)?;
605        }
606
607        Ok(())
608    }
609
610    /// Change the value of a flag
611    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            // Here we treate the case where we read only one value
636            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            // Here we treat the case where we read an array
649            let mut vals: Vec<FlagValue> = Vec::new();
650            for idx in 0..flag.nb_elems() {
651                // By construction, >1
652                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
661/// Memory relaterd code
662impl 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    /// Ensure the sna has the appropriate number of pages
674    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    /// To play easier with memory, remove all the memory chunks and use a linearized memory version
690    /// Memory array MUST be empty before calling this method
691    pub fn unwrap_memory_chunks(&mut self) {
692        if self.memory.is_empty() {
693            // uncrunch the memory blocks
694            self.memory = SnapshotMemory::new(&self.memory_dump());
695
696            // remove the memory chunks
697            let mut idx = 0;
698            while idx < self.chunks.len() {
699                if self.chunks[idx].is_memory_chunk() {
700                    self.chunks.remove(idx);
701                    // no need to increment idx as the next chunk is at the same position
702                }
703                else {
704                    idx += 1;
705                }
706            }
707
708            // update the memory size
709            self.set_memory_size_header((self.memory.len() / 1024) as u16);
710        }
711    }
712
713    /// Change a memory value. Panic if memory size is not appropriate
714    /// If memory is saved insided chuncks, the chuncks are unwrapped
715    pub fn set_byte(&mut self, address: u32, value: u8) {
716        self.unwrap_memory_chunks();
717        let address = address as usize;
718
719        // resize if needed
720        while self.memory.len() - 1 < address {
721            self.memory = self.memory.increased_size();
722        }
723
724        // finally write in memory
725        self.memory.memory_mut()[address] = value;
726    }
727
728    /// Can only work on snapshot where memory is not stored in chunks.
729    /// TODO modify this behavior
730    pub fn get_byte(&self, address: u32) -> u8 {
731        self.memory.memory()[address as usize]
732    }
733
734    /// Returns all the memory of the snapshot in a linear way by mixing both the hardcoded memory of the snapshot and the memory of chunks
735    pub fn memory_dump(&self) -> Vec<u8> {
736        // by default, the memory i already coded
737        let mut memory = self.memory.clone();
738
739        let mut max_memory = self.memory_size_header() as usize * 1024;
740
741        // but it can be patched by chunks
742        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                // TODO treat the case where extra memory is used as `memroy` may need to be extended
753            }
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    /// Check if the snapshot has some memory chunk
764    pub fn has_memory_chunk(&self) -> bool {
765        self.chunks.iter().any(|c| c.is_memory_chunk())
766    }
767
768    /// Returns the memory that is hardcoded in the snapshot
769    pub fn memory_block(&self) -> &SnapshotMemory {
770        &self.memory
771    }
772
773    /// Add the content of a file at the required position
774    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    /// Add the memory content at the required posiiton
784    ///
785    /// ```
786    /// use cpclib_sna::Snapshot;
787    ///
788    /// let mut sna = Snapshot::default();
789    /// let data = vec![0,2,3,5];
790    /// sna.add_data(&data, 0x4000);
791    /// ```
792    /// TODO: re-implement with set_byte
793    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            // TODO add warning when writting in other banks
807
808            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
822/// Chunks related code
823impl Snapshot {
824    /// Read a chunk if available
825    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(), //
834            [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            // ['D', 'S', 'C', _] => InsertedDiscChunk::from(code, content)
838            // ['C', 'P', 'C', '+'] => CPCPlusChunk::from(content)
839            _ => 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// TODO: use observers instead of printing on terminal !
895#[cfg(feature = "cmdline")]
896pub fn process<E: EventObserver>(matches: &ArgMatches, o: &E) -> Result<(), SnapshotError> {
897    // Display all tokens
898
899    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    // Load a snapshot or generate an empty one
912    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    // Activate the debug mode
923    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    // Manage the files insertion
938    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    // Patch memory
964    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    // Read the tokens
980    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    // Set the tokens
989    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            // Read the parameters from the command line
996            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            // Get the token
1005            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}