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