cpclib_cpr/
lib.rs

1use std::fmt::Display;
2use std::fs::File;
3use std::hash::{DefaultHasher, Hash, Hasher};
4use std::io::{Read, Write};
5use std::ops::Deref;
6
7use cpclib_common::camino::Utf8Path;
8use cpclib_common::itertools::Itertools;
9use cpclib_common::riff::{RiffChunk, RiffCode, RiffLen};
10use cpclib_common::winnow::Parser;
11
12const CODE_BANKS: [&str; 32] = [
13    "cb00", "cb01", "cb02", "cb03", "cb04", "cb05", "cb06", "cb07", "cb08", "cb09", "cb10", "cb11",
14    "cb12", "cb13", "cb14", "cb15", "cb16", "cb17", "cb18", "cb19", "cb20", "cb21", "cb22", "cb23",
15    "cb24", "cb25", "cb26", "cb27", "cb28", "cb29", "cb30", "cb31"
16];
17
18#[derive(PartialEq, Debug, Clone)]
19pub struct CartridgeBank(RiffChunk);
20
21impl Deref for CartridgeBank {
22    type Target = RiffChunk;
23
24    fn deref(&self) -> &Self::Target {
25        &self.0
26    }
27}
28
29impl TryFrom<RiffChunk> for CartridgeBank {
30    type Error = String;
31
32    fn try_from(value: RiffChunk) -> Result<Self, Self::Error> {
33        let code = value.code().to_string();
34        if !code.starts_with("cb") {
35            return Err(format!("{code} is an invalid cartridge bloc id"));
36        }
37
38        let nb: u16 = u16::from_str_radix(&code[2..], 10).unwrap_or(0xDEAD);
39        if nb > 32 {
40            return Err(format!("{code} is an invalid cartridge bloc id"));
41        }
42
43        if value.len().value() > 0x4000 {
44            return Err(format!("{} is an invalid size", value.len().value()));
45        }
46
47        Ok(Self(value))
48    }
49}
50
51impl CartridgeBank {
52    pub fn new(nb: u8) -> CartridgeBank {
53        assert!(nb < 32);
54
55        let data = vec![0; 0x4000];
56        let code = Self::code_for(nb);
57        let chunk = RiffChunk::new(code, data);
58        chunk.try_into().unwrap()
59    }
60
61    pub fn number(&self) -> u8 {
62        Self::nb_for_code(self.code().as_str())
63    }
64
65    pub fn code_for(nb: u8) -> &'static str {
66        CODE_BANKS[nb as usize]
67    }
68
69    pub fn nb_for_code(code: &str) -> u8 {
70        let idx = CODE_BANKS.iter().position(|&c| c == code).unwrap();
71        idx as u8
72    }
73
74    pub fn set_byte(&mut self, address: u16, byte: u8) {
75        self.0.set_byte(address, byte)
76    }
77}
78
79#[derive(PartialEq, Debug, Clone)]
80pub struct Cpr {
81    banks: Vec<CartridgeBank>
82}
83
84impl Default for Cpr {
85    fn default() -> Self {
86        Cpr::empty()
87    }
88}
89
90impl From<Vec<CartridgeBank>> for Cpr {
91    fn from(banks: Vec<CartridgeBank>) -> Self {
92        Self { banks }
93    }
94}
95
96impl Cpr {
97    pub fn empty() -> Self {
98        Cpr {
99            banks: Vec::default()
100        }
101    }
102
103    pub fn banks(&self) -> &[CartridgeBank] {
104        &self.banks
105    }
106
107    pub fn bank_mut(&mut self, idx: usize) -> Option<&mut CartridgeBank> {
108        self.banks.get_mut(idx)
109    }
110
111    pub fn bank_at_index(&self, idx: usize) -> Option<&CartridgeBank> {
112        self.banks.get(idx)
113    }
114
115    pub fn bank_by_num(&self, nb: u8) -> Option<&CartridgeBank> {
116        self.bank_index(nb).map(|idx| &self.banks[idx])
117    }
118
119    pub fn bank_by_code(&self, code: &RiffCode) -> Option<&CartridgeBank> {
120        self.bank_index(CartridgeBank::nb_for_code(code.as_str()))
121            .map(|idx| &self.banks[idx])
122    }
123
124    /// The len of a CPR is the len of each bloc + BAMS size
125    pub fn len(&self) -> RiffLen {
126        let size: u32 = self
127            .banks
128            .iter()
129            .map(|b| b.code().len() as u32 + b.len().len() as u32 + b.len().value())
130            .sum::<u32>()
131            + 4;
132        size.into()
133    }
134
135    pub fn add_bank(&mut self, bloc: CartridgeBank) {
136        // TODO check if it is already present
137        self.banks.push(bloc);
138    }
139
140    pub fn remove_bank(&mut self, nb: u8) -> Option<CartridgeBank> {
141        if let Some(idx) = self.bank_index(nb) {
142            Some(self.banks.remove(idx))
143        }
144        else {
145            None
146        }
147    }
148
149    fn bank_index(&self, nb: u8) -> Option<usize> {
150        self.banks()
151            .iter()
152            .position(|bank| bank.code().as_str() == CODE_BANKS[nb as usize])
153    }
154}
155
156impl Cpr {
157    pub fn save<P: AsRef<Utf8Path>>(&self, fname: P) -> Result<(), std::io::Error> {
158        let mut buffer = File::create(fname.as_ref())?;
159        self.write_all(&mut buffer)
160    }
161
162    pub fn write_all<B: Write>(&self, buffer: &mut B) -> Result<(), std::io::Error> {
163        let riff_code: RiffCode = "RIFF".into();
164        let len: RiffLen = (self.len().value()).into();
165        let ams_code: RiffCode = "AMS!".into();
166
167        buffer.write_all(riff_code.deref())?;
168        buffer.write_all(len.deref())?;
169        buffer.write_all(ams_code.deref())?;
170
171        for bank in &self.banks {
172            bank.write_all(buffer)?;
173        }
174
175        Ok(())
176    }
177
178    pub fn load<P: AsRef<Utf8Path>>(filename: P) -> Result<Self, String> {
179        let filename = filename.as_ref();
180
181        // Read the full content of the file
182        let file_content = {
183            let mut f = File::open(filename).map_err(|e| e.to_string())?;
184            let mut content = Vec::new();
185            f.read_to_end(&mut content).map_err(|e| e.to_string())?;
186            content
187        };
188
189        Self::from_buffer(file_content)
190    }
191
192    pub fn from_buffer(mut file_content: Vec<u8>) -> Result<Self, String> {
193        let tag: RiffCode = file_content.drain(0..4).as_slice().into();
194        if tag != [b'R', b'I', b'F', b'F'].into() {
195            return Err(format!("{tag:?} found instead of RIFF"));
196        }
197
198        let length: RiffLen = file_content.drain(0..4).as_slice().into();
199        if length.value() as usize != file_content.len() {
200            return Err(format!(
201                "Wrong size coding in CPR file
202        {} != {}",
203                length.value(),
204                file_content.len()
205            ));
206        }
207
208        let tag: RiffCode = file_content.drain(0..4).as_slice().into();
209
210        if tag != [b'A', b'M', b'S', b'!'].into() {
211            return Err(format!("{tag} found instead of AMS!"));
212        }
213
214        let mut banks = Vec::new();
215        while !file_content.is_empty() {
216            let chunk = RiffChunk::from_buffer(&mut file_content);
217            if chunk.code().to_string().as_bytes() != b"fmt " {
218                let cb: CartridgeBank = chunk.try_into()?;
219                banks.push(cb);
220            }
221        }
222
223        let cpr = Cpr { banks };
224
225        assert!(file_content.is_empty());
226        if length.value() != cpr.len().value() {
227            assert!(length.value() > cpr.len().value());
228            eprintln!(
229                "CPR indicates a length of {} whereas current length is {}.",
230                length.value(),
231                cpr.len().value()
232            )
233        }
234
235        Ok(cpr)
236    }
237}
238
239pub struct CartridgeBankInfo<'c> {
240    riff_size: u32,
241    checksum: u64,
242    bank: &'c CartridgeBank
243}
244
245impl Display for CartridgeBankInfo<'_> {
246    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
247        writeln!(
248            f,
249            "Code: {}\nRiff size: {}\nChecksum: {:X}",
250            self.code().as_str(),
251            self.riff_size,
252            self.checksum
253        )
254    }
255}
256
257impl<'c> From<&'c CartridgeBank> for CartridgeBankInfo<'c> {
258    fn from(bank: &'c CartridgeBank) -> Self {
259        let checksum = {
260            let mut hasher = DefaultHasher::new();
261            bank.deref().data().hash(&mut hasher);
262            hasher.finish()
263        };
264
265        Self {
266            riff_size: bank.len().value(),
267            checksum,
268            bank
269        }
270    }
271}
272
273impl Deref for CartridgeBankInfo<'_> {
274    type Target = CartridgeBank;
275
276    fn deref(&self) -> &Self::Target {
277        self.bank
278    }
279}
280
281pub struct CprInfo<'c> {
282    banks: Vec<CartridgeBankInfo<'c>>,
283    cpr: &'c Cpr
284}
285
286impl Display for CprInfo<'_> {
287    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
288        writeln!(f, "Cartridge with {} banks", self.cpr.banks().len())?;
289        for (idx, bank) in self.banks.iter().enumerate() {
290            writeln!(f, "# Bank {idx}\n{bank}")?;
291        }
292        Ok(())
293    }
294}
295
296impl<'c> From<&'c Cpr> for CprInfo<'c> {
297    fn from(cpr: &'c Cpr) -> Self {
298        let banks: Vec<CartridgeBankInfo<'c>> = cpr.banks().iter().map(|b| b.into()).collect_vec();
299        Self { banks, cpr }
300    }
301}
302
303impl Deref for CprInfo<'_> {
304    type Target = Cpr;
305
306    fn deref(&self) -> &Self::Target {
307        self.cpr
308    }
309}
310#[cfg(test)]
311mod test {
312    use crate::CartridgeBank;
313
314    #[test]
315    fn test_cartrdige_code() {
316        assert_eq!("cb00", CartridgeBank::code_for(0));
317        assert_eq!("cb31", CartridgeBank::code_for(31));
318    }
319}