1use alloc::{string::String, vec::Vec};
26use thiserror::Error;
27
28pub const MAGIC: &[u8; 8] = b"RUSTYNES";
30
31pub const FORMAT_VERSION: u16 = 2;
45
46pub const ROM_HASH_TAG_LEN: usize = 6;
48
49pub const HEADER_LEN: usize = 8 + 2 + ROM_HASH_TAG_LEN;
51
52pub mod tag {
55 pub const CPU: [u8; 4] = *b"CPU ";
57 pub const PPU: [u8; 4] = *b"PPU ";
59 pub const APU: [u8; 4] = *b"APU ";
61 pub const MAP: [u8; 4] = *b"MAP ";
63 pub const BUS: [u8; 4] = *b"BUS ";
65 pub const THM: [u8; 4] = *b"THM ";
69}
70
71pub const THUMBNAIL_WIDTH: usize = 128;
73pub const THUMBNAIL_HEIGHT: usize = 120;
75pub const THUMBNAIL_LEN: usize = THUMBNAIL_WIDTH * THUMBNAIL_HEIGHT * 4;
77pub const THUMBNAIL_VERSION: u8 = 1;
79
80#[derive(Debug, Error)]
82#[non_exhaustive]
83pub enum SnapshotError {
84 #[error("save state truncated: header needs {expected} bytes, got {got}")]
86 HeaderTruncated {
87 expected: usize,
89 got: usize,
91 },
92
93 #[error("save state magic mismatch: expected {:?}, got {got:?}", MAGIC)]
95 BadMagic {
96 got: [u8; 8],
98 },
99
100 #[error("save state container format version {got} not supported (max {max})")]
102 UnsupportedFormat {
103 got: u16,
105 max: u16,
107 },
108
109 #[error("save state section {tag} body truncated: declared {declared} bytes, got {got}")]
111 SectionTruncated {
112 tag: String,
114 declared: usize,
116 got: usize,
118 },
119
120 #[error(
122 "save state section {tag} version {file_version} not supported (chip supports {chip_supports})"
123 )]
124 VersionMismatch {
125 tag: String,
127 file_version: u8,
129 chip_supports: u8,
131 },
132
133 #[error("save state section {tag}: {reason}")]
135 SectionInvalid {
136 tag: String,
138 reason: String,
140 },
141
142 #[error("save state missing required section {0}")]
144 MissingSection(String),
145
146 #[error("save state truncated mid-section at offset {0}")]
148 Eof(usize),
149}
150
151impl SnapshotError {
152 pub fn invalid(tag: [u8; 4], reason: impl Into<String>) -> Self {
154 Self::SectionInvalid {
155 tag: tag_string(tag),
156 reason: reason.into(),
157 }
158 }
159}
160
161#[must_use]
163pub fn tag_string(t: [u8; 4]) -> String {
164 let mut s = String::with_capacity(4);
165 for b in t {
166 s.push(if (0x20..=0x7E).contains(&b) {
167 char::from(b)
168 } else {
169 '?'
170 });
171 }
172 s
173}
174
175#[derive(Debug, Default)]
180pub struct BinWriter {
181 buf: Vec<u8>,
182}
183
184impl BinWriter {
185 #[must_use]
187 pub const fn new() -> Self {
188 Self { buf: Vec::new() }
189 }
190
191 #[must_use]
193 pub fn with_capacity(cap: usize) -> Self {
194 Self {
195 buf: Vec::with_capacity(cap),
196 }
197 }
198
199 #[must_use]
201 pub fn into_vec(self) -> Vec<u8> {
202 self.buf
203 }
204
205 #[must_use]
207 pub const fn len(&self) -> usize {
208 self.buf.len()
209 }
210
211 #[must_use]
213 pub const fn is_empty(&self) -> bool {
214 self.buf.is_empty()
215 }
216
217 pub fn u8(&mut self, v: u8) {
219 self.buf.push(v);
220 }
221
222 pub fn u16(&mut self, v: u16) {
224 self.buf.extend_from_slice(&v.to_le_bytes());
225 }
226
227 pub fn u32(&mut self, v: u32) {
229 self.buf.extend_from_slice(&v.to_le_bytes());
230 }
231
232 pub fn u64(&mut self, v: u64) {
234 self.buf.extend_from_slice(&v.to_le_bytes());
235 }
236
237 pub fn i16(&mut self, v: i16) {
239 self.buf.extend_from_slice(&v.to_le_bytes());
240 }
241
242 pub fn bool(&mut self, v: bool) {
244 self.buf.push(u8::from(v));
245 }
246
247 pub fn bytes(&mut self, v: &[u8]) {
249 self.buf.extend_from_slice(v);
250 }
251
252 pub fn lp_bytes(&mut self, v: &[u8]) {
254 self.u32(u32::try_from(v.len()).expect("slice too large for save state"));
255 self.bytes(v);
256 }
257}
258
259#[derive(Debug)]
261pub struct BinReader<'a> {
262 src: &'a [u8],
263 pos: usize,
264}
265
266impl<'a> BinReader<'a> {
267 #[must_use]
269 pub const fn new(src: &'a [u8]) -> Self {
270 Self { src, pos: 0 }
271 }
272
273 #[must_use]
275 pub const fn remaining(&self) -> usize {
276 self.src.len() - self.pos
277 }
278
279 #[must_use]
281 pub const fn is_empty(&self) -> bool {
282 self.remaining() == 0
283 }
284
285 #[must_use]
287 pub const fn pos(&self) -> usize {
288 self.pos
289 }
290
291 const fn need(&self, n: usize) -> Result<(), SnapshotError> {
292 if self.remaining() < n {
293 return Err(SnapshotError::Eof(self.pos));
294 }
295 Ok(())
296 }
297
298 pub fn u8(&mut self) -> Result<u8, SnapshotError> {
304 self.need(1)?;
305 let v = self.src[self.pos];
306 self.pos += 1;
307 Ok(v)
308 }
309
310 pub fn u16(&mut self) -> Result<u16, SnapshotError> {
316 self.need(2)?;
317 let v = u16::from_le_bytes([self.src[self.pos], self.src[self.pos + 1]]);
318 self.pos += 2;
319 Ok(v)
320 }
321
322 pub fn u32(&mut self) -> Result<u32, SnapshotError> {
328 self.need(4)?;
329 let mut a = [0u8; 4];
330 a.copy_from_slice(&self.src[self.pos..self.pos + 4]);
331 self.pos += 4;
332 Ok(u32::from_le_bytes(a))
333 }
334
335 pub fn u64(&mut self) -> Result<u64, SnapshotError> {
341 self.need(8)?;
342 let mut a = [0u8; 8];
343 a.copy_from_slice(&self.src[self.pos..self.pos + 8]);
344 self.pos += 8;
345 Ok(u64::from_le_bytes(a))
346 }
347
348 pub fn i16(&mut self) -> Result<i16, SnapshotError> {
354 self.need(2)?;
355 let v = i16::from_le_bytes([self.src[self.pos], self.src[self.pos + 1]]);
356 self.pos += 2;
357 Ok(v)
358 }
359
360 pub fn bool(&mut self) -> Result<bool, SnapshotError> {
366 Ok(self.u8()? != 0)
367 }
368
369 pub fn take(&mut self, n: usize) -> Result<&'a [u8], SnapshotError> {
375 self.need(n)?;
376 let s = &self.src[self.pos..self.pos + n];
377 self.pos += n;
378 Ok(s)
379 }
380
381 pub fn read_into(&mut self, dst: &mut [u8]) -> Result<(), SnapshotError> {
387 let s = self.take(dst.len())?;
388 dst.copy_from_slice(s);
389 Ok(())
390 }
391
392 pub fn lp_bytes(&mut self) -> Result<&'a [u8], SnapshotError> {
398 let n = self.u32()? as usize;
399 self.take(n)
400 }
401}
402
403pub fn write_section(out: &mut Vec<u8>, tag: [u8; 4], version: u8, body: &[u8]) {
406 out.extend_from_slice(&tag);
407 out.push(version);
408 let len = u32::try_from(body.len()).expect("section body too large");
409 out.extend_from_slice(&len.to_le_bytes());
410 out.extend_from_slice(body);
411}
412
413#[derive(Debug, Clone, Copy)]
415pub struct Section<'a> {
416 pub tag: [u8; 4],
418 pub version: u8,
420 pub body: &'a [u8],
422}
423
424#[derive(Debug, Clone)]
426pub struct Header {
427 pub format_version: u16,
430 pub rom_hash_tag: [u8; ROM_HASH_TAG_LEN],
432}
433
434pub fn parse_header(bytes: &[u8]) -> Result<(Header, usize), SnapshotError> {
443 if bytes.len() < HEADER_LEN {
444 return Err(SnapshotError::HeaderTruncated {
445 expected: HEADER_LEN,
446 got: bytes.len(),
447 });
448 }
449 let mut magic = [0u8; 8];
450 magic.copy_from_slice(&bytes[..8]);
451 if &magic != MAGIC {
452 return Err(SnapshotError::BadMagic { got: magic });
453 }
454 let format_version = u16::from_le_bytes([bytes[8], bytes[9]]);
455 if format_version > FORMAT_VERSION {
456 return Err(SnapshotError::UnsupportedFormat {
457 got: format_version,
458 max: FORMAT_VERSION,
459 });
460 }
461 let mut rom_hash_tag = [0u8; ROM_HASH_TAG_LEN];
462 rom_hash_tag.copy_from_slice(&bytes[10..16]);
463 Ok((
464 Header {
465 format_version,
466 rom_hash_tag,
467 },
468 HEADER_LEN,
469 ))
470}
471
472pub struct SectionIter<'a> {
475 src: &'a [u8],
476 pos: usize,
477}
478
479impl<'a> SectionIter<'a> {
480 #[must_use]
482 pub const fn new(body: &'a [u8]) -> Self {
483 Self { src: body, pos: 0 }
484 }
485}
486
487impl<'a> Iterator for SectionIter<'a> {
488 type Item = Result<Section<'a>, SnapshotError>;
489
490 fn next(&mut self) -> Option<Self::Item> {
491 if self.pos >= self.src.len() {
492 return None;
493 }
494 if self.src.len() - self.pos < 9 {
496 return Some(Err(SnapshotError::Eof(self.pos)));
497 }
498 let mut tag = [0u8; 4];
499 tag.copy_from_slice(&self.src[self.pos..self.pos + 4]);
500 let version = self.src[self.pos + 4];
501 let mut len_bytes = [0u8; 4];
502 len_bytes.copy_from_slice(&self.src[self.pos + 5..self.pos + 9]);
503 let len = u32::from_le_bytes(len_bytes) as usize;
504 let body_start = self.pos + 9;
505 let body_end = body_start + len;
506 if body_end > self.src.len() {
507 return Some(Err(SnapshotError::SectionTruncated {
508 tag: tag_string(tag),
509 declared: len,
510 got: self.src.len() - body_start,
511 }));
512 }
513 let body = &self.src[body_start..body_end];
514 self.pos = body_end;
515 Some(Ok(Section { tag, version, body }))
516 }
517}
518
519pub fn write_header(out: &mut Vec<u8>, rom_hash_tag: [u8; ROM_HASH_TAG_LEN]) {
521 out.extend_from_slice(MAGIC);
522 out.extend_from_slice(&FORMAT_VERSION.to_le_bytes());
523 out.extend_from_slice(&rom_hash_tag);
524}
525
526#[cfg(test)]
527mod tests {
528 use super::*;
529
530 #[test]
531 fn header_round_trip() {
532 let mut out = Vec::new();
533 write_header(&mut out, [1, 2, 3, 4, 5, 6]);
534 assert_eq!(out.len(), HEADER_LEN);
535 let (h, off) = parse_header(&out).unwrap();
536 assert_eq!(h.format_version, FORMAT_VERSION);
537 assert_eq!(h.rom_hash_tag, [1, 2, 3, 4, 5, 6]);
538 assert_eq!(off, HEADER_LEN);
539 }
540
541 #[test]
542 fn header_rejects_bad_magic() {
543 let mut out = Vec::new();
544 out.extend_from_slice(b"NOTRUSTY");
545 out.extend_from_slice(&[0u8; HEADER_LEN - 8]);
546 assert!(matches!(
547 parse_header(&out),
548 Err(SnapshotError::BadMagic { .. })
549 ));
550 }
551
552 #[test]
553 fn header_rejects_too_new_format() {
554 let mut out = Vec::new();
555 out.extend_from_slice(MAGIC);
556 out.extend_from_slice(&u16::MAX.to_le_bytes());
557 out.extend_from_slice(&[0u8; ROM_HASH_TAG_LEN]);
558 assert!(matches!(
559 parse_header(&out),
560 Err(SnapshotError::UnsupportedFormat { .. })
561 ));
562 }
563
564 #[test]
565 fn section_iter_round_trip() {
566 let mut out = Vec::new();
567 write_section(&mut out, *b"AAAA", 1, &[1, 2, 3]);
568 write_section(&mut out, *b"BBBB", 7, &[4, 5, 6, 7, 8]);
569 let mut it = SectionIter::new(&out);
570 let s1 = it.next().unwrap().unwrap();
571 assert_eq!(&s1.tag, b"AAAA");
572 assert_eq!(s1.version, 1);
573 assert_eq!(s1.body, &[1, 2, 3]);
574 let s2 = it.next().unwrap().unwrap();
575 assert_eq!(&s2.tag, b"BBBB");
576 assert_eq!(s2.version, 7);
577 assert_eq!(s2.body, &[4, 5, 6, 7, 8]);
578 assert!(it.next().is_none());
579 }
580
581 #[test]
582 fn binwriter_round_trip() {
583 let mut w = BinWriter::new();
584 w.u8(0x12);
585 w.u16(0x3456);
586 w.u32(0x789A_BCDE);
587 w.u64(0xFEED_FACE_CAFE_BEEF);
588 w.bool(true);
589 w.bool(false);
590 w.bytes(&[0xAA, 0xBB]);
591 let buf = w.into_vec();
592 let mut r = BinReader::new(&buf);
593 assert_eq!(r.u8().unwrap(), 0x12);
594 assert_eq!(r.u16().unwrap(), 0x3456);
595 assert_eq!(r.u32().unwrap(), 0x789A_BCDE);
596 assert_eq!(r.u64().unwrap(), 0xFEED_FACE_CAFE_BEEF);
597 assert!(r.bool().unwrap());
598 assert!(!r.bool().unwrap());
599 assert_eq!(r.take(2).unwrap(), &[0xAA, 0xBB]);
600 assert!(r.is_empty());
601 }
602
603 #[test]
604 fn binreader_eof_errors() {
605 let buf = [0x12u8];
606 let mut r = BinReader::new(&buf);
607 assert!(r.u8().is_ok());
608 assert!(matches!(r.u8(), Err(SnapshotError::Eof(_))));
609 }
610}