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 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 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 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}