1use crate::{
25 Block, Card, Entry, Error, Result, SaveIcon, CONTAINER_LEN, CONTAINER_MAGIC, DATA_BLOCKS,
26 FLAG_COMPRESSED, FRAMES_PER_BLOCK, FRAME_SIZE, MAX_NAME_LEN,
27};
28
29const E_STATE: usize = 0;
31const E_SIZE: usize = 4;
32const E_LINK: usize = 8;
33const E_NAME: usize = 10;
34const E_CHK: usize = 127;
35
36const ST_FIRST: u8 = 0x51;
38const ST_MIDDLE: u8 = 0x52;
39const ST_LAST: u8 = 0x53;
40const ST_FREE: u8 = 0xA0;
41fn is_free(state: u8) -> bool {
43 state & 0xF0 == 0xA0
44}
45
46const T_ICON_FLAG: usize = 2;
48const T_BLOCKS: usize = 3;
49const T_TITLE: usize = 4;
50const T_TITLE_LEN: usize = 64;
51const T_CLUT: usize = 0x60;
52
53const LINK_NONE: u16 = 0xFFFF;
54const FIRST_BLOCK_FRAMES: usize = FRAMES_PER_BLOCK - 2;
56const FIRST_BLOCK_CAP: usize = FIRST_BLOCK_FRAMES * FRAME_SIZE; const BLOCK_CAP: usize = FRAMES_PER_BLOCK * FRAME_SIZE; fn checksum(frame: &[u8; FRAME_SIZE]) -> u8 {
63 frame[..E_CHK].iter().fold(0u8, |a, &b| a ^ b)
64}
65
66fn blocks_for(total: usize) -> usize {
68 if total <= FIRST_BLOCK_CAP {
69 1
70 } else {
71 1 + (total - FIRST_BLOCK_CAP).div_ceil(BLOCK_CAP)
72 }
73}
74
75fn write_u32(buf: &mut [u8], v: u32) {
76 buf[0..4].copy_from_slice(&v.to_le_bytes());
77}
78fn read_u32(buf: &[u8]) -> u32 {
79 u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]])
80}
81
82struct Chain {
87 blocks: [u8; DATA_BLOCKS],
88 len: usize,
89}
90
91struct Replaced {
93 first_entries: u16,
94 all_entries: u16,
95}
96
97struct DataCursor<'a> {
102 blocks: &'a [u8],
103 li: usize, fib: usize, off: usize, frame: [u8; FRAME_SIZE],
107 loaded: bool,
108}
109
110impl<'a> DataCursor<'a> {
111 fn new(blocks: &'a [u8]) -> Self {
112 DataCursor {
113 blocks,
114 li: 0,
115 fib: 2, off: 0,
117 frame: [0; FRAME_SIZE],
118 loaded: false,
119 }
120 }
121
122 fn next_byte<B: Block>(&mut self, dev: &mut B) -> Result<u8> {
123 if self.li >= self.blocks.len() {
124 return Err(Error::Corrupt);
125 }
126 if !self.loaded {
127 let phys = self.blocks[self.li] as u16 * FRAMES_PER_BLOCK as u16 + self.fib as u16;
128 dev.read_frame(phys, &mut self.frame)?;
129 self.loaded = true;
130 }
131 let b = self.frame[self.off];
132 self.off += 1;
133 if self.off == FRAME_SIZE {
134 self.off = 0;
135 self.loaded = false;
136 self.fib += 1;
137 if self.fib == FRAMES_PER_BLOCK {
138 self.fib = 0;
139 self.li += 1;
140 }
141 }
142 Ok(b)
143 }
144
145 fn read_exact<B: Block>(&mut self, dev: &mut B, out: &mut [u8]) -> Result<()> {
146 for b in out.iter_mut() {
147 *b = self.next_byte(dev)?;
148 }
149 Ok(())
150 }
151}
152
153struct DataWriter<'a> {
158 blocks: &'a [u8],
159 li: usize,
160 fib: usize,
161 off: usize,
162 frame: [u8; FRAME_SIZE],
163}
164
165impl<'a> DataWriter<'a> {
166 fn new(blocks: &'a [u8]) -> Self {
167 DataWriter {
168 blocks,
169 li: 0,
170 fib: 2,
171 off: 0,
172 frame: [0; FRAME_SIZE],
173 }
174 }
175
176 fn push<B: Block>(&mut self, dev: &mut B, byte: u8) -> Result<()> {
177 self.frame[self.off] = byte;
178 self.off += 1;
179 if self.off == FRAME_SIZE {
180 self.flush_frame(dev)?;
181 }
182 Ok(())
183 }
184
185 fn flush_frame<B: Block>(&mut self, dev: &mut B) -> Result<()> {
186 if self.li >= self.blocks.len() {
187 return Err(Error::NoSpace);
188 }
189 let phys = self.blocks[self.li] as u16 * FRAMES_PER_BLOCK as u16 + self.fib as u16;
190 dev.write_frame(phys, &self.frame)?;
191 self.off = 0;
192 self.frame = [0; FRAME_SIZE];
193 self.fib += 1;
194 if self.fib == FRAMES_PER_BLOCK {
195 self.fib = 0;
196 self.li += 1;
197 }
198 Ok(())
199 }
200
201 fn finish<B: Block>(&mut self, dev: &mut B) -> Result<()> {
203 if self.off > 0 {
204 self.flush_frame(dev)?;
205 }
206 Ok(())
207 }
208}
209
210impl<B: Block> Card<B> {
215 fn read_dir(&mut self, index: usize) -> Result<[u8; FRAME_SIZE]> {
219 let mut f = [0u8; FRAME_SIZE];
220 self.dev.read_frame(1 + index as u16, &mut f)?;
221 if checksum(&f) != f[E_CHK] {
222 return Err(Error::Corrupt);
223 }
224 Ok(f)
225 }
226
227 fn write_dir(&mut self, index: usize, frame: &mut [u8; FRAME_SIZE]) -> Result<()> {
228 frame[E_CHK] = checksum(frame);
229 self.dev.write_frame(1 + index as u16, frame)
230 }
231
232 pub fn is_formatted(&mut self) -> Result<bool> {
234 let mut f = [0u8; FRAME_SIZE];
235 self.dev.read_frame(0, &mut f)?;
236 Ok(f[0] == b'M' && f[1] == b'C')
237 }
238
239 fn validate_structure(&mut self) -> Result<()> {
242 let mut header = [0u8; FRAME_SIZE];
243 self.dev.read_frame(0, &mut header)?;
244 if header[0] != b'M' || header[1] != b'C' {
245 return Err(Error::NotFormatted);
246 }
247 if checksum(&header) != header[E_CHK] {
248 return Err(Error::Corrupt);
249 }
250
251 let mut states = [0u8; DATA_BLOCKS];
252 let mut links = [LINK_NONE; DATA_BLOCKS];
253 for i in 0..DATA_BLOCKS {
254 let entry = self.read_dir(i)?;
255 let state = entry[E_STATE];
256 if !is_free(state) && !matches!(state, ST_FIRST | ST_MIDDLE | ST_LAST) {
257 return Err(Error::Corrupt);
258 }
259 states[i] = state;
260 links[i] = u16::from_le_bytes([entry[E_LINK], entry[E_LINK + 1]]);
261 }
262
263 let mut visited = 0u16;
264 for first in 0..DATA_BLOCKS {
265 if states[first] != ST_FIRST {
266 continue;
267 }
268 let mut current = first;
269 let mut first_node = true;
270 loop {
271 let bit = 1u16 << current;
272 if visited & bit != 0 {
273 return Err(Error::Corrupt);
274 }
275 visited |= bit;
276
277 let state = states[current];
278 if first_node {
279 if state != ST_FIRST {
280 return Err(Error::Corrupt);
281 }
282 first_node = false;
283 } else if !matches!(state, ST_MIDDLE | ST_LAST) {
284 return Err(Error::Corrupt);
285 }
286
287 let link = links[current];
288 if state == ST_LAST || (state == ST_FIRST && link == LINK_NONE) {
289 if link != LINK_NONE {
290 return Err(Error::Corrupt);
291 }
292 break;
293 }
294 if link == LINK_NONE || link as usize >= DATA_BLOCKS {
295 return Err(Error::Corrupt);
296 }
297 current = link as usize;
298 }
299 }
300
301 #[allow(clippy::needless_range_loop)]
304 for i in 0..DATA_BLOCKS {
305 if matches!(states[i], ST_MIDDLE | ST_LAST) && visited & (1u16 << i) == 0 {
306 return Err(Error::Corrupt);
307 }
308 }
309 Ok(())
310 }
311
312 pub fn validate_filesystem(&mut self) -> Result<()> {
320 self.validate_structure()?;
321 for first in 0..DATA_BLOCKS {
322 let entry = self.read_dir(first)?;
323 if entry[E_STATE] != ST_FIRST {
324 continue;
325 }
326 let chain = self.chain(first)?;
327 let expected = (chain.len * BLOCK_CAP) as u32;
328 if read_u32(&entry[E_SIZE..]) != expected {
329 return Err(Error::Corrupt);
330 }
331 }
332 Ok(())
333 }
334
335 pub fn format(&mut self) -> Result<()> {
338 let mut hdr = [0u8; FRAME_SIZE];
340 hdr[0] = b'M';
341 hdr[1] = b'C';
342 hdr[E_CHK] = checksum(&hdr);
343 self.dev.write_frame(0, &hdr)?;
344
345 for i in 0..DATA_BLOCKS {
347 let mut e = [0u8; FRAME_SIZE];
348 e[E_STATE] = ST_FREE;
349 e[E_LINK] = 0xFF;
350 e[E_LINK + 1] = 0xFF;
351 self.write_dir(i, &mut e)?;
352 }
353
354 for frame in 16..36u16 {
356 let mut f = [0u8; FRAME_SIZE];
357 f[0] = 0xFF;
358 f[1] = 0xFF;
359 f[2] = 0xFF;
360 f[3] = 0xFF;
361 f[8] = 0xFF;
362 f[9] = 0xFF;
363 self.dev.write_frame(frame, &f)?;
364 }
365 let zero = [0u8; FRAME_SIZE];
367 for frame in 36..FRAMES_PER_BLOCK as u16 {
368 self.dev.write_frame(frame, &zero)?;
369 }
370 Ok(())
371 }
372
373 pub fn free_blocks(&mut self) -> Result<usize> {
375 let mut n = 0;
376 for i in 0..DATA_BLOCKS {
377 if is_free(self.read_dir(i)?[E_STATE]) {
378 n += 1;
379 }
380 }
381 Ok(n)
382 }
383
384 fn find(&mut self, name: &[u8]) -> Result<Option<usize>> {
386 for i in 0..DATA_BLOCKS {
387 let e = self.read_dir(i)?;
388 if e[E_STATE] == ST_FIRST && entry_name_eq(&e, name) {
389 return Ok(Some(i));
390 }
391 }
392 Ok(None)
393 }
394
395 fn chain(&mut self, first: usize) -> Result<Chain> {
397 let mut blocks = [0u8; DATA_BLOCKS];
398 let mut len = 0;
399 let mut idx = first;
400 loop {
401 if len >= DATA_BLOCKS {
402 return Err(Error::Corrupt); }
404 let e = self.read_dir(idx)?;
405 blocks[len] = (idx + 1) as u8; len += 1;
407 let link = u16::from_le_bytes([e[E_LINK], e[E_LINK + 1]]);
408 match e[E_STATE] {
409 ST_LAST => break,
410 ST_FIRST | ST_MIDDLE => {
411 if link == LINK_NONE {
412 break; }
414 if link as usize >= DATA_BLOCKS {
415 return Err(Error::Corrupt);
416 }
417 idx = link as usize;
418 }
419 _ => return Err(Error::Corrupt),
420 }
421 }
422 Ok(Chain { blocks, len })
423 }
424
425 pub fn list(&mut self, out: &mut [Entry]) -> Result<usize> {
427 let mut n = 0;
428 for i in 0..DATA_BLOCKS {
429 let e = self.read_dir(i)?;
430 if e[E_STATE] != ST_FIRST {
431 continue;
432 }
433 if n >= out.len() {
434 break;
435 }
436 let mut name = [0u8; MAX_NAME_LEN + 1];
437 let name_len = copy_entry_name(&e, &mut name);
438 let blocks = self.chain(i)?.len as u8;
439 out[n] = Entry {
440 name,
441 name_len,
442 blocks,
443 };
444 n += 1;
445 }
446 Ok(n)
447 }
448
449 pub fn delete(&mut self, name: &str) -> Result<()> {
451 let name = name.as_bytes();
452 let Some(first) = self.find(name)? else {
453 return Ok(());
454 };
455 let chain = self.chain(first)?;
456 for k in 0..chain.len {
457 let index = chain.blocks[k] as usize - 1;
458 let mut e = [0u8; FRAME_SIZE];
459 e[E_STATE] = ST_FREE;
460 e[E_LINK] = 0xFF;
461 e[E_LINK + 1] = 0xFF;
462 self.write_dir(index, &mut e)?;
463 }
464 Ok(())
465 }
466
467 pub fn write(&mut self, name: &str, title: &str, data: &[u8]) -> Result<()> {
475 self.write_inner(name, title, data, false, data.len() as u32, None)
476 }
477
478 pub fn write_with_icon(
480 &mut self,
481 name: &str,
482 title: &str,
483 data: &[u8],
484 icon: &SaveIcon,
485 ) -> Result<()> {
486 self.write_inner(name, title, data, false, data.len() as u32, Some(icon))
487 }
488
489 #[cfg(feature = "compress")]
493 pub fn write_compressed(
494 &mut self,
495 name: &str,
496 title: &str,
497 data: &[u8],
498 scratch: &mut [u8],
499 ) -> Result<()> {
500 match crate::compress::compress(data, scratch) {
501 Some(clen) if clen < data.len() => {
502 self.write_inner(name, title, &scratch[..clen], true, data.len() as u32, None)
503 }
504 _ => self.write_inner(name, title, data, false, data.len() as u32, None),
505 }
506 }
507
508 fn write_inner(
509 &mut self,
510 name: &str,
511 title: &str,
512 stored: &[u8],
513 compressed: bool,
514 raw_len: u32,
515 icon: Option<&SaveIcon>,
516 ) -> Result<()> {
517 let name = name.as_bytes();
518 validate_name(name)?;
519 validate_title(title)?;
520
521 let total = CONTAINER_LEN + stored.len();
522 let need = blocks_for(total);
523 if need > DATA_BLOCKS {
524 return Err(Error::NoSpace);
525 }
526
527 let old = self.same_name_blocks(name)?;
530
531 let mut alloc = [0u8; DATA_BLOCKS];
535 let mut got = 0;
536 for i in 0..DATA_BLOCKS {
537 if got == need {
538 break;
539 }
540 if is_free(self.read_dir(i)?[E_STATE]) {
541 alloc[got] = i as u8;
542 got += 1;
543 }
544 }
545 if got < need {
546 return Err(Error::NoSpace);
547 }
548
549 let mut phys = [0u8; DATA_BLOCKS];
551 for k in 0..need {
552 phys[k] = alloc[k] + 1;
553 }
554
555 self.write_title(phys[0], title, need as u8, icon)?;
557
558 let mut hdr = [0u8; CONTAINER_LEN];
560 hdr[0..4].copy_from_slice(&CONTAINER_MAGIC);
561 hdr[4] = if compressed { FLAG_COMPRESSED } else { 0 };
562 write_u32(&mut hdr[8..], raw_len);
563 write_u32(&mut hdr[12..], stored.len() as u32);
564
565 let mut w = DataWriter::new(&phys[..need]);
566 for &b in hdr.iter() {
567 w.push(&mut self.dev, b)?;
568 }
569 for &b in stored.iter() {
570 w.push(&mut self.dev, b)?;
571 }
572 w.finish(&mut self.dev)?;
573
574 for k in (0..need).rev() {
578 let mut e = [0u8; FRAME_SIZE];
579 e[E_STATE] = if k == 0 {
580 ST_FIRST } else if k == need - 1 {
582 ST_LAST
583 } else {
584 ST_MIDDLE
585 };
586 let link = if k + 1 < need {
588 alloc[k + 1] as u16
589 } else {
590 LINK_NONE
591 };
592 e[E_LINK] = link as u8;
593 e[E_LINK + 1] = (link >> 8) as u8;
594 if k == 0 {
595 write_u32(&mut e[E_SIZE..], (need * BLOCK_CAP) as u32);
598 let n = name.len().min(MAX_NAME_LEN);
599 e[E_NAME..E_NAME + n].copy_from_slice(&name[..n]);
600 }
601 self.write_dir(alloc[k] as usize, &mut e)?;
602 }
603
604 self.free_blocks_in(old.first_entries)?;
607 self.free_blocks_in(old.all_entries & !old.first_entries)
608 }
609
610 fn same_name_blocks(&mut self, name: &[u8]) -> Result<Replaced> {
614 let mut found = Replaced {
615 first_entries: 0,
616 all_entries: 0,
617 };
618 for i in 0..DATA_BLOCKS {
619 let e = self.read_dir(i)?;
620 if e[E_STATE] != ST_FIRST || !entry_name_eq(&e, name) {
621 continue;
622 }
623 found.first_entries |= 1 << i;
624 let chain = self.chain(i)?;
625 for &block in &chain.blocks[..chain.len] {
626 found.all_entries |= 1 << (block - 1);
627 }
628 }
629 Ok(found)
630 }
631
632 fn free_blocks_in(&mut self, mask: u16) -> Result<()> {
634 for i in 0..DATA_BLOCKS {
635 if mask & (1 << i) != 0 {
636 let mut e = [0u8; FRAME_SIZE];
637 e[E_STATE] = ST_FREE;
638 e[E_LINK] = 0xFF;
639 e[E_LINK + 1] = 0xFF;
640 self.write_dir(i, &mut e)?;
641 }
642 }
643 Ok(())
644 }
645
646 fn write_title(
648 &mut self,
649 phys_block: u8,
650 title: &str,
651 blocks: u8,
652 icon: Option<&SaveIcon>,
653 ) -> Result<()> {
654 let mut hdr = [0u8; FRAME_SIZE];
655 hdr[0] = b'S';
656 hdr[1] = b'C';
657 hdr[T_ICON_FLAG] = 0x11; hdr[T_BLOCKS] = blocks;
659 let tb = title.as_bytes();
661 let n = tb.len().min(T_TITLE_LEN);
662 hdr[T_TITLE..T_TITLE + n].copy_from_slice(&tb[..n]);
663 let clut = icon.map(|icon| icon.palette).unwrap_or_else(default_clut);
665 for (i, c) in clut.iter().enumerate() {
666 hdr[T_CLUT + i * 2] = *c as u8;
667 hdr[T_CLUT + i * 2 + 1] = (*c >> 8) as u8;
668 }
669 let base = phys_block as u16 * FRAMES_PER_BLOCK as u16;
670 self.dev.write_frame(base, &hdr)?;
671
672 let mut pixels = [0u8; FRAME_SIZE];
674 match icon {
675 Some(icon) => pixels.copy_from_slice(&icon.pixels),
676 None => default_icon(&mut pixels),
677 }
678 self.dev.write_frame(base + 1, &pixels)?;
679 Ok(())
680 }
681
682 pub fn read(&mut self, name: &str, buf: &mut [u8]) -> Result<usize> {
686 let name = name.as_bytes();
687 let first = self.find(name)?.ok_or(Error::NotFound)?;
688 let chain = self.chain(first)?;
689 let blocks = &chain.blocks[..chain.len];
690
691 let mut cur = DataCursor::new(blocks);
692 let mut hdr = [0u8; CONTAINER_LEN];
693 cur.read_exact(&mut self.dev, &mut hdr)?;
694 if hdr[0..4] != CONTAINER_MAGIC {
695 return Err(Error::BadContainer);
696 }
697 let flags = hdr[4];
698 let raw_len = read_u32(&hdr[8..]) as usize;
699 let stored_len = read_u32(&hdr[12..]) as usize;
700 if buf.len() < raw_len {
701 return Err(Error::BufferTooSmall);
702 }
703
704 if flags & FLAG_COMPRESSED == 0 {
705 if stored_len != raw_len {
710 return Err(Error::BadContainer);
711 }
712 cur.read_exact(&mut self.dev, &mut buf[..stored_len])?;
713 return Ok(stored_len);
714 }
715
716 #[cfg(feature = "compress")]
719 {
720 let mut remaining = stored_len;
721 let mut transport_error = None;
724 let produced = crate::compress::decompress_from(
725 || {
726 if remaining == 0 {
727 return None;
728 }
729 remaining -= 1;
730 match cur.next_byte(&mut self.dev) {
731 Ok(byte) => Some(byte),
732 Err(e) => {
733 transport_error = Some(e);
734 None
735 }
736 }
737 },
738 &mut buf[..raw_len],
739 );
740 if let Some(e) = transport_error {
741 return Err(e);
742 }
743 let produced = produced.ok_or(Error::Compression)?;
744 if produced != raw_len {
745 return Err(Error::Compression);
746 }
747 Ok(raw_len)
748 }
749 #[cfg(not(feature = "compress"))]
750 {
751 let _ = stored_len;
752 Err(Error::Compression)
753 }
754 }
755}
756
757fn validate_name(name: &[u8]) -> Result<()> {
762 if name.is_empty() || name.len() > MAX_NAME_LEN {
763 return Err(Error::BadName);
764 }
765 if name.iter().any(|&b| !(0x20..=0x7E).contains(&b)) {
766 return Err(Error::BadName);
767 }
768 Ok(())
769}
770
771fn validate_title(title: &str) -> Result<()> {
772 if title.is_empty() || title.len() > 32 {
773 return Err(Error::BadName);
774 }
775 Ok(())
776}
777
778fn entry_name_eq(entry: &[u8; FRAME_SIZE], name: &[u8]) -> bool {
779 let mut buf = [0u8; MAX_NAME_LEN + 1];
780 let len = copy_entry_name(entry, &mut buf) as usize;
781 &buf[..len] == name
782}
783
784fn copy_entry_name(entry: &[u8; FRAME_SIZE], out: &mut [u8; MAX_NAME_LEN + 1]) -> u8 {
785 let mut len = 0;
786 while len < MAX_NAME_LEN {
787 let b = entry[E_NAME + len];
788 if b == 0 {
789 break;
790 }
791 out[len] = b;
792 len += 1;
793 }
794 len as u8
795}
796
797fn default_clut() -> [u16; 16] {
803 let mut c = [0u16; 16];
804 c[0] = 0x0000; c[1] = 0x7FFF; c[2] = 0x7C00; c[3] = 0x03FF; c
809}
810
811fn default_icon(out: &mut [u8; FRAME_SIZE]) {
813 for y in 0..16usize {
814 for x in 0..16usize {
815 let idx: u8 = if x == 0 || x == 15 || y == 0 || y == 15 {
816 1 } else if (2..=5).contains(&x) && (2..=3).contains(&y) {
818 3 } else {
820 2 };
822 let byte = (y * 16 + x) / 2;
823 if x & 1 == 0 {
824 out[byte] = (out[byte] & 0xF0) | idx; } else {
826 out[byte] = (out[byte] & 0x0F) | (idx << 4); }
828 }
829 }
830}