use serde_json::Value;
use std::collections::HashMap;
pub struct Geometry {
pub id: String,
pub texture_id: Option<String>,
pub ctm: Vec<u8>,
}
pub struct Tmxb {
pub header: Value,
pub textures: HashMap<String, Vec<u8>>,
pub geometries: Vec<Geometry>,
}
pub fn parse(data: &[u8]) -> Result<Tmxb, String> {
if data.len() < 9 || &data[0..5] != b"3MXBO" {
return Err("bad magic (not a 3mxb)".into());
}
let header_len = u32::from_le_bytes(data[5..9].try_into().unwrap()) as usize;
let header_end = 9 + header_len;
if data.len() < header_end {
return Err("truncated header".into());
}
let header: Value =
serde_json::from_slice(&data[9..header_end]).map_err(|e| format!("header json: {e}"))?;
let mut textures = HashMap::new();
let mut geometries = Vec::new();
let mut off = header_end;
if let Some(resources) = header["resources"].as_array() {
for r in resources {
let size = r["size"].as_u64().unwrap_or(0) as usize;
let end = off.checked_add(size).ok_or("resource size overflow")?;
if end > data.len() {
return Err(format!(
"resource buffer past end of file (off={off}, size={size}, len={})",
data.len()
));
}
let buf = &data[off..end];
off = end;
let id = r["id"].as_str().unwrap_or("").to_string();
match r["type"].as_str() {
Some("textureBuffer") => {
textures.insert(id, buf.to_vec());
}
Some("geometryBuffer") => match r["format"].as_str() {
Some("ctm") => geometries.push(Geometry {
id,
texture_id: r["texture"].as_str().map(|s| s.to_string()),
ctm: buf.to_vec(),
}),
other => eprintln!(" skip geometry format {other:?} (id={id})"),
},
other => eprintln!(" skip resource type {other:?} (id={id})"),
}
}
}
Ok(Tmxb {
header,
textures,
geometries,
})
}