Skip to main content

tmx/
tmxb.rs

1//! 3mxb container: "3MXBO" + u32 header length + JSON header + concatenated
2//! resource buffers (each `size` bytes, in header order).
3
4use serde_json::Value;
5use std::collections::HashMap;
6
7pub struct Geometry {
8    pub id: String,
9    pub texture_id: Option<String>,
10    pub ctm: Vec<u8>,
11}
12
13pub struct Tmxb {
14    pub header: Value,
15    pub textures: HashMap<String, Vec<u8>>,
16    pub geometries: Vec<Geometry>,
17}
18
19pub fn parse(data: &[u8]) -> Result<Tmxb, String> {
20    if data.len() < 9 || &data[0..5] != b"3MXBO" {
21        return Err("bad magic (not a 3mxb)".into());
22    }
23    let header_len = u32::from_le_bytes(data[5..9].try_into().unwrap()) as usize;
24    let header_end = 9 + header_len;
25    if data.len() < header_end {
26        return Err("truncated header".into());
27    }
28    let header: Value =
29        serde_json::from_slice(&data[9..header_end]).map_err(|e| format!("header json: {e}"))?;
30
31    let mut textures = HashMap::new();
32    let mut geometries = Vec::new();
33
34    let mut off = header_end;
35    if let Some(resources) = header["resources"].as_array() {
36        for r in resources {
37            let size = r["size"].as_u64().unwrap_or(0) as usize;
38            let end = off.checked_add(size).ok_or("resource size overflow")?;
39            if end > data.len() {
40                return Err(format!(
41                    "resource buffer past end of file (off={off}, size={size}, len={})",
42                    data.len()
43                ));
44            }
45            let buf = &data[off..end];
46            off = end;
47            let id = r["id"].as_str().unwrap_or("").to_string();
48            match r["type"].as_str() {
49                Some("textureBuffer") => {
50                    textures.insert(id, buf.to_vec());
51                }
52                Some("geometryBuffer") => match r["format"].as_str() {
53                    Some("ctm") => geometries.push(Geometry {
54                        id,
55                        texture_id: r["texture"].as_str().map(|s| s.to_string()),
56                        ctm: buf.to_vec(),
57                    }),
58                    other => eprintln!("  skip geometry format {other:?} (id={id})"),
59                },
60                other => eprintln!("  skip resource type {other:?} (id={id})"),
61            }
62        }
63    }
64
65    Ok(Tmxb {
66        header,
67        textures,
68        geometries,
69    })
70}