//! `RATC` — a nested resource bundle. //! //! After a small header, a RATC holds named children — `foo.t32` (T8aD //! textures), `foo.rat` (nested RATC), fonts, PNG — each immediately preceded by //! its ASCII name string. The children are self-locating by their 4-char magic, //! so we list them by scanning for those magics and pairing each with the name //! run that precedes it. This is reliable for *listing* (names / types / sizes //! are literal bytes); decoding a child's pixels is delegated to that child's //! own parser ([`crate::t8ad`]). /// A child resource inside a RATC bundle. #[derive(Debug, Clone, PartialEq, Eq)] pub struct RatcChild { /// Name string preceding the child (e.g. `prselectbtn_g04b.t32`), or empty. pub name: String, /// Child magic tag: `T8aD`, `RATC`, `ttcf`, `png`, … pub kind: String, /// Byte offset of the child (its magic) within the RATC payload. pub offset: usize, /// Byte length of the child, up to the next child (or end of payload). pub size: usize, } /// Magic at the start of a RATC bundle. pub const RATC_MAGIC: [u8; 4] = *b"RATC"; /// Whether `bytes` is a RATC bundle. pub fn is_ratc(bytes: &[u8]) -> bool { bytes.len() >= 4 && bytes[0..4] == RATC_MAGIC } /// Recognized child magics and their display tag. fn child_kind(m: &[u8]) -> Option<&'static str> { match m { b"T8aD" => Some("T8aD"), b"RATC" => Some("RATC"), b"ttcf" => Some("ttc"), b"\x89PNG" => Some("png"), _ => None, } } /// List the children of a RATC bundle (the self-magic at offset 0 is skipped). pub fn parse(bytes: &[u8]) -> Option> { if !is_ratc(bytes) { return None; } // Offsets of every recognized child magic (skip the self RATC at 0). let mut offs: Vec<(usize, &'static str)> = Vec::new(); let mut i = 4; while i + 4 <= bytes.len() { if let Some(kind) = child_kind(&bytes[i..i + 4]) { offs.push((i, kind)); i += 4; } else { i += 1; } } let mut children = Vec::with_capacity(offs.len()); for (idx, &(off, kind)) in offs.iter().enumerate() { let next = offs.get(idx + 1).map(|&(o, _)| o).unwrap_or(bytes.len()); children.push(RatcChild { name: name_before(bytes, off), kind: kind.to_string(), offset: off, size: next.saturating_sub(off), }); } Some(children) } /// The nearest name string preceding `off`: the *last* printable run (len ≥ 3) /// in the 96 bytes before the child magic. A few record-header bytes usually sit /// between the name and the magic, so an exact-adjacency scan isn't enough. fn name_before(bytes: &[u8], off: usize) -> String { let start = off.saturating_sub(96); let window = &bytes[start..off]; let mut best = String::new(); let mut run_start: Option = None; let flush = |from: usize, to: usize, best: &mut String| { if to - from >= 3 { *best = String::from_utf8_lossy(&window[from..to]).trim().to_string(); } }; for (i, &c) in window.iter().enumerate() { if (0x20..=0x7e).contains(&c) { run_start.get_or_insert(i); } else if let Some(s) = run_start.take() { flush(s, i, &mut best); } } if let Some(s) = run_start { flush(s, window.len(), &mut best); } best } #[cfg(test)] mod tests { use super::*; #[test] fn lists_named_children() { let mut b = RATC_MAGIC.to_vec(); b.extend_from_slice(&[0u8; 28]); // header padding b.extend_from_slice(b"logo.t32"); let t8_off = b.len(); b.extend_from_slice(b"T8aD"); b.extend_from_slice(&[0u8; 40]); // some child bytes b.extend_from_slice(b"sub.rat"); let ratc_off = b.len(); b.extend_from_slice(b"RATC"); b.extend_from_slice(&[0u8; 8]); let kids = parse(&b).unwrap(); assert_eq!(kids.len(), 2); assert_eq!(kids[0].kind, "T8aD"); assert_eq!(kids[0].name, "logo.t32"); assert_eq!(kids[0].offset, t8_off); assert_eq!(kids[0].size, ratc_off - t8_off); assert_eq!(kids[1].kind, "RATC"); assert_eq!(kids[1].name, "sub.rat"); } #[test] fn rejects_non_ratc() { assert!(parse(b"T8aD....").is_none()); } }