From 13c9fe6678c982fc402d00471fb09838b2c2affc Mon Sep 17 00:00:00 2001 From: Serge Bazanski Date: Thu, 14 Apr 2022 22:06:10 +0000 Subject: [PATCH] boxarena.gmf now decompiles exactly --- Cargo.toml | 4 + gmflib/src/gma.rs | 149 ++++++++ gmflib/src/{machinery.rs => gmi.rs} | 74 ++-- gmflib/src/lib.rs | 3 +- gmflib/src/types.rs | 553 ++++++++++++++++++++++++---- gmfmacros/src/lib.rs | 213 +++++++++-- gmftool/src/main.rs | 4 +- 7 files changed, 861 insertions(+), 139 deletions(-) create mode 100644 gmflib/src/gma.rs rename gmflib/src/{machinery.rs => gmi.rs} (66%) diff --git a/Cargo.toml b/Cargo.toml index 9afb596..e8c5689 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,3 +5,7 @@ members = [ "gmftool", "gmfmacros", ] + +[profile.release] +strip = true +lto = true diff --git a/gmflib/src/gma.rs b/gmflib/src/gma.rs new file mode 100644 index 0000000..2534ec7 --- /dev/null +++ b/gmflib/src/gma.rs @@ -0,0 +1,149 @@ +#[derive(Debug, Clone)] +pub struct WriteError { + pub msg: String, +} + +pub type WriteResult = Result; + +pub trait Serializable: Sized { + fn write(&self, name: S, r: &mut WriteStream) -> WriteResult<()>; +} + +pub struct WriteStream { + backing: W, + depth: usize, +} + +pub trait Atom { + fn atom(&self) -> String; +} + +impl Atom for str { + fn atom(&self) -> String { + self.to_string() + } +} + +impl Atom for String { + fn atom(&self) -> String { + self.to_string() + } +} + +impl Atom for f32 { + fn atom(&self) -> String { + format!("{:.6}", self) + } +} + +impl Serializable for [f32; 3] { + fn write(&self, name: S, w: &mut WriteStream) -> WriteResult<()> { + w.emit(&format!("{} {:.6}\t{:.6}\t{:.6}", name.to_string(), self[0], self[1], self[2])) + } +} + +impl Atom for i32 { + fn atom(&self) -> String { + format!("{}", self) + } +} + +impl Atom for u32 { + fn atom(&self) -> String { + format!("{}", self) + } +} + +impl Atom for u8 { + fn atom(&self) -> String { + format!("{}", self) + } +} + +impl Atom for usize { + fn atom(&self) -> String { + format!("{}", self) + } +} + +impl Serializable for Option { + fn write(&self, name: S, w: &mut WriteStream) -> WriteResult<()> { + if let Some(s) = self { + s.write(name, w)?; + } + Ok(()) + } +} + +impl Serializable for Vec { + fn write(&self, name: S, w: &mut WriteStream) -> WriteResult<()> { + if name.to_string() == "*" { + w.emit_pair("*COUNT", &self.len())?; + } else { + w.emit_pair(&(name.to_string() + "_COUNT"), &self.len())?; + } + for elem in self.iter() { + elem.write(name.to_string(), w)?; + } + Ok(()) + } +} + +impl Serializable for A { + fn write(&self, name: S, w: &mut WriteStream) -> WriteResult<()> { + if self.atom().len() > 0 { + w.emit_pair(&name.to_string(), self)?; + } + Ok(()) + } +} + +impl WriteStream { + pub fn new(w: W) -> Self { + Self { + backing: w, + depth: 0, + } + } + + pub fn error(&self, msg: S) -> WriteError { + WriteError { + msg: msg.to_string(), + } + } + + pub fn emit(&mut self, elem: &S) -> WriteResult<()> { + match write!(self.backing, "{}{}\n", "\t".repeat(self.depth), elem.atom()) { + Ok(_) => return Ok(()), + Err(_) => return Err(self.error("write failed")), + } + } + + pub fn emit_pair(&mut self, a: &S, b: &T) -> WriteResult<()> { + let a = a.atom(); + if a == "" || a == "*" { + return Ok(()); + } + let b = b.atom(); + match write!(self.backing, "{}{}\t{}\n", "\t".repeat(self.depth), a, b) { + Ok(_) => return Ok(()), + Err(_) => return Err(self.error("write failed")), + } + } + + pub fn push(&mut self, el: S) -> WriteResult<()> { + self.emit(&el.to_string())?; + self.emit("{")?; + self.depth += 1; + Ok(()) + } + + pub fn pop(&mut self) -> WriteResult<()> { + if self.depth == 0 { + return Err(self.error("stack underflow")); + } + self.depth -= 1; + self.emit("}")?; + Ok(()) + } +} diff --git a/gmflib/src/machinery.rs b/gmflib/src/gmi.rs similarity index 66% rename from gmflib/src/machinery.rs rename to gmflib/src/gmi.rs index ea96b3c..39d9c6a 100644 --- a/gmflib/src/machinery.rs +++ b/gmflib/src/gmi.rs @@ -1,5 +1,3 @@ -use std::fmt; - pub struct ReadError { pub msg: String, pub stack: Vec, @@ -11,8 +9,8 @@ pub struct ParseFrame { pub loc: usize, } -impl fmt::Debug for ReadError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +impl std::fmt::Debug for ReadError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let stack = self.stack.iter().map(|pf| format!("{}@{:x}", pf.name, pf.loc)).collect::>().join("->"); f.debug_struct("ReadError") .field("msg", &self.msg) @@ -23,22 +21,15 @@ impl fmt::Debug for ReadError { pub type ReadResult = Result; -#[derive(Debug, Clone)] -pub struct WriteError { - pub msg: String, -} - -pub type WriteResult = Result; - -pub trait GMISerializable: Sized { - fn read(r: &mut GMIReadStream) -> ReadResult; - fn read_parametrized(r: &mut GMIReadStream, params: &[u32]) -> ReadResult { +pub trait Serializable: Sized { + fn read(r: &mut ReadStream) -> ReadResult; + fn read_parametrized(r: &mut ReadStream, _params: &[u32]) -> ReadResult { Err(r.error("called with unexpected parameters")) } } -impl GMISerializable for Vec { - fn read(r: &mut GMIReadStream) -> ReadResult { +impl Serializable for Vec { + fn read(r: &mut ReadStream) -> ReadResult { let count: u32 = r.read("count")?; // Safety check. A reasonable value of ~32MiB of smallest possible // element (u32). @@ -47,28 +38,27 @@ impl GMISerializable for Vec { } (0..count).map(|i| r.read(format!("elem[{}]", i))).collect() } - fn read_parametrized(r: &mut GMIReadStream, params: &[u32]) -> ReadResult { + fn read_parametrized(r: &mut ReadStream, params: &[u32]) -> ReadResult { let count = params[0]; (0..count).map(|i| r.read(format!("elem[{}]", i))).collect() } } -impl GMISerializable for [T; N] { - fn read(r: &mut GMIReadStream) -> ReadResult { +impl Serializable for [T; N] { + fn read(r: &mut ReadStream) -> ReadResult { (0..N).map(|_| r.read("data")).collect::>>()?.try_into().map_err(|_| r.error("eof")) } } - -impl GMISerializable for Option { - fn read(r: &mut GMIReadStream) -> ReadResult { +impl Serializable for Option { + fn read(r: &mut ReadStream) -> ReadResult { let count: u32 = r.read("count")?; if count != 0 { return Ok(Some(r.read("data")?)); } return Ok(None); } - fn read_parametrized(r: &mut GMIReadStream, params: &[u32]) -> ReadResult { + fn read_parametrized(r: &mut ReadStream, params: &[u32]) -> ReadResult { let count = params[0]; if count != 0 { return Ok(Some(r.read("data")?)); @@ -77,28 +67,35 @@ impl GMISerializable for Option { } } -impl GMISerializable for u8 { - fn read(r: &mut GMIReadStream) -> ReadResult { +impl Serializable for u8 { + fn read(r: &mut ReadStream) -> ReadResult { Ok(r.bytes(1)?[0]) } } -impl GMISerializable for u32 { - fn read(r: &mut GMIReadStream) -> ReadResult { +impl Serializable for u32 { + fn read(r: &mut ReadStream) -> ReadResult { let buf: [u8; 4] = r.bytes(4)?.try_into().unwrap(); Ok(u32::from_le_bytes(buf)) } } -impl GMISerializable for f32 { - fn read(r: &mut GMIReadStream) -> ReadResult { +impl Serializable for i32 { + fn read(r: &mut ReadStream) -> ReadResult { + let buf: [u8; 4] = r.bytes(4)?.try_into().unwrap(); + Ok(i32::from_le_bytes(buf)) + } +} + +impl Serializable for f32 { + fn read(r: &mut ReadStream) -> ReadResult { let buf: [u8; 4] = r.bytes(4)?.try_into().unwrap(); Ok(f32::from_le_bytes(buf)) } } -impl GMISerializable for String { - fn read(r: &mut GMIReadStream) -> ReadResult { +impl Serializable for String { + fn read(r: &mut ReadStream) -> ReadResult { let len: u32 = r.read("len")?; r.push("bytes".to_string()); let bytes = r.bytes(len as usize)?; @@ -109,13 +106,13 @@ impl GMISerializable for String { } } -pub struct GMIReadStream { +pub struct ReadStream { backing: R, stack: Vec, pos: usize, } -impl GMIReadStream { +impl ReadStream { pub fn new(r: R) -> Self { Self { backing: r, @@ -138,7 +135,7 @@ impl GMIReadStream { } } -impl GMIReadStream { +impl ReadStream { pub fn bytes(&mut self, n: usize) -> ReadResult> { let mut buf: Vec = vec![0; n]; self.pos += n; @@ -158,14 +155,14 @@ impl GMIReadStream { }) } - pub fn read(&mut self, ctx: S) -> ReadResult { + pub fn read(&mut self, ctx: S) -> ReadResult { self.push(ctx.to_string()); let res = T::read(self); self.pop(); res } - pub fn read_parametrized(&mut self, ctx: S, params: &[u32]) -> ReadResult { + pub fn read_parametrized(&mut self, ctx: S, params: &[u32]) -> ReadResult { self.push(ctx.to_string()); let res = T::read_parametrized(self, params); self.pop(); @@ -189,14 +186,14 @@ pub struct TLV { } impl TLV { - pub fn error(&self, msg: &str) -> ReadError { + pub fn error(&self, msg: S) -> ReadError { ReadError { msg: msg.to_string(), stack: vec![], } } - pub fn check(&self, r: &mut GMIReadStream) -> ReadResult<()> { + pub fn check(&self, r: &mut ReadStream) -> ReadResult<()> { let expected = self.start_pos + self.length as usize + 12usize; if expected != r.pos { return Err(ReadError { @@ -207,3 +204,4 @@ impl TLV { Ok(()) } } + diff --git a/gmflib/src/lib.rs b/gmflib/src/lib.rs index 1c507b5..2e2fe1d 100644 --- a/gmflib/src/lib.rs +++ b/gmflib/src/lib.rs @@ -1,4 +1,5 @@ -mod machinery; +mod gmi; +mod gma; pub mod types; pub use types::*; diff --git a/gmflib/src/types.rs b/gmflib/src/types.rs index 85df972..866df40 100644 --- a/gmflib/src/types.rs +++ b/gmflib/src/types.rs @@ -1,9 +1,15 @@ -use gmfmacros::GMISerializable; +use std::{io, string}; -use crate::machinery::{ReadResult, GMIReadStream, GMISerializable}; +use gmfmacros::{GMISerializable, GMASerializable}; + +use crate::{ + gmi, gma, + gmi::Serializable as GMISerializable, + gma::Serializable as GMASerializable, +}; #[derive(Debug)] -pub struct GMI { +pub struct GMF { pub version: u32, pub model_type: ModelType, pub unk1: u32, @@ -14,19 +20,27 @@ pub struct GMI { pub objects: ObjectList, } -impl GMI { - pub fn parse(r: R) -> ReadResult { - let mut r = GMIReadStream::new(r); - r.read("gmi") +impl GMF { + pub fn read_gmi(r: R) -> gmi::ReadResult { + let mut r = gmi::ReadStream::new(r); + GMF::read(&mut r) + } + + pub fn write_gma(&self, w: W) -> gma::WriteResult<()> { + let mut w = gma::WriteStream::new(w); + self.write("", &mut w) } } -impl GMISerializable for GMI { - fn read(r: &mut GMIReadStream) -> ReadResult { +impl gmi::Serializable for GMF { + fn read(r: &mut gmi::ReadStream) -> gmi::ReadResult { if r.bytes(3)? != b"GMI".to_vec() { return Err(r.error("invaid magic")); } let version: u32 = r.read("version")?; + if version != 3 { + return Err(r.error(format!("unsupported version {}", version))); + } let model_type = r.read("model_type")?; let unk1: u32 = r.read("unk1")?; if unk1 != 0 { @@ -45,6 +59,18 @@ impl GMISerializable for GMI { } } +impl gma::Serializable for GMF { + fn write(&self, _name: S, w: &mut gma::WriteStream) -> gma::WriteResult<()> { + w.emit("GMA")?; + w.emit_pair("*GABRIEL_ASCIIEXPORT", "3")?; + self.model_type.write("*MODEL_TYPE", w)?; + self.scene.write("", w)?; + self.materials.write("", w)?; + self.objects.write("", w)?; + Ok(()) + } +} + #[derive(Debug,GMISerializable)] pub struct Color { pub r: u8, @@ -53,6 +79,18 @@ pub struct Color { pub a: u8, } +impl gma::Atom for Color { + fn atom(&self) -> String { + let mut val: u32 = 0; + val |= (self.b as u32) << 16; + val |= (self.g as u32) << 8; + val |= (self.r as u32) << 0; + // TODO: figure out alpha support, if at all. + format!("0x{:x}", val) + } +} + + #[derive(Debug,GMISerializable)] pub enum MapKind { Diffuse = 1, @@ -60,23 +98,34 @@ pub enum MapKind { Opacity = 6, } -#[derive(Debug,GMISerializable)] +impl gma::Serializable for MapKind { + fn write(&self, _name: S, w: &mut gma::WriteStream) -> gma::WriteResult<()> { + match self { + MapKind::Diffuse => w.emit("*MAP_DIFFUSE"), + MapKind::SelfIllum => w.emit("*MAP_SELFILLUM"), + MapKind::Opacity => w.emit("*MAP_OPACITY"), + } + } +} + +#[derive(Debug,GMISerializable,GMASerializable)] pub enum MapType { Screen = 4, } -#[derive(Debug,GMISerializable)] +#[derive(Debug,GMISerializable,GMASerializable)] pub enum BitmapFilter { Pyramidal = 0, SAT = 1, } -#[derive(Debug, GMISerializable)] +#[derive(Debug,GMISerializable,GMASerializable)] pub enum ModelType { + #[gma_value("Basic Model")] BasicModel = 1, } -#[derive(Debug,GMISerializable)] +#[derive(Debug,GMISerializable,GMASerializable)] #[gmi_tagged(1,2)] pub struct Scene { pub filename: String, @@ -84,19 +133,24 @@ pub struct Scene { pub last_frame: u32, pub frame_speed: u32, pub ticks_per_frame: u32, + #[gma_name("SCENE_BACKGROUND_STATIC")] pub background: Color, + #[gma_name("SCENE_AMBIENT_STATIC")] pub ambient: Color, } -#[derive(Debug,GMISerializable)] +#[derive(Debug,GMISerializable,GMASerializable)] #[gmi_tagged(7, 2)] +#[gma_name("MATERIAL_LIST")] pub struct MaterialList { + #[gma_name("MATERIAL")] pub materials: Vec, } -#[derive(Debug,GMISerializable)] +#[derive(Debug,GMISerializable,GMASerializable)] #[gmi_tagged(8, 2)] pub struct Material { + #[gma_name("MATERIAL_REF_NO")] pub ref_no: u32, pub name: String, pub class: String, @@ -105,70 +159,115 @@ pub struct Material { pub specular: Color, pub shine: f32, pub shine_strength: f32, - pub wiresize: f32, pub transparency: f32, + pub wiresize: f32, pub shading: Shading, + #[gma_name("MATERIAL_XP_FALLOFF")] pub xp_falloff: f32, pub selfillum: f32, pub falloff: Falloff, + #[gma_name("MATERIAL_XP_TYPE")] pub xp_type: XPType, pub textures: Option, pub sub: Option, } -#[derive(Debug,GMISerializable)] +#[derive(Debug,GMISerializable,GMASerializable)] pub enum Shading { Other = 0, Blinn = 0xc, } -#[derive(Debug, GMISerializable)] +#[derive(Debug, GMISerializable,GMASerializable)] pub enum Falloff { In = 0, } -#[derive(Debug,GMISerializable)] +#[derive(Debug,GMISerializable,GMASerializable)] pub enum XPType { Other = 0, Filter = 1, } -#[derive(Debug,GMISerializable)] +#[derive(Debug,GMISerializable,GMASerializable)] #[gmi_tagged(14, 2)] +#[gma_name("TEXTURE_LIST")] pub struct TextureList { + #[gma_name("TEXTURE")] pub textures: Vec, } -#[derive(Debug,GMISerializable)] +#[derive(Debug,GMISerializable,GMASerializable)] #[gmi_tagged(15, 4)] pub struct Texture { + #[gma_name("MAP_NAME")] pub name: String, + #[gma_name("MAP_CLASS")] pub class: String, + #[gma_name("BITMAP")] pub bitmap: String, + #[gma_name("MAP_AMOUNT")] pub amount: f32, pub kind: MapKind, + #[gma_name("MAP_TYPE")] pub map_type: MapType, + #[gma_name("UVW_U_OFFSET")] pub u_offset: f32, + #[gma_name("UVW_V_OFFSET")] pub v_offset: f32, + #[gma_name("UVW_U_TILING")] pub u_tiling: f32, + #[gma_name("UVW_V_TILING")] pub v_tiling: f32, + #[gma_name("UVW_ANGLE")] pub angle: f32, + #[gma_name("UVW_BLUR")] pub blur: f32, + #[gma_name("UVW_BLUR_OFFSET")] pub blur_offset: f32, - pub noise_amt: f32, + #[gma_name("UVW_NOUSE_AMT")] + pub noise_amount: f32, + #[gma_name("UVW_NOISE_SIZE")] pub noise_size: f32, + #[gma_name("UVW_NOISE_LEVEL")] pub noise_level: u32, + #[gma_name("UVW_NOISE_PHASE")] pub noise_phase: f32, + #[gma_name("")] pub invert: u32, + #[gma_name("")] pub unknown: u32, + #[gma_name("BITMAP_FILTER")] pub filter: BitmapFilter, + #[gma_name("BITMAP_MAP_CHANNEL")] pub channel: u32, pub sub: Option, } -#[derive(Debug,GMISerializable)] +#[derive(Debug)] +pub struct ObjectName(String); + +impl gmi::Serializable for ObjectName { + fn read(r: &mut gmi::ReadStream) -> gmi::ReadResult { + String::read(r).map(|e| ObjectName(e)) + } +} + +impl gma::Serializable for ObjectName { + fn write(&self, name: S, w: &mut gma::WriteStream) -> gma::WriteResult<()> { + let val = match self.0.len() { + 0 => "(null)".to_string(), + _ => self.0.clone(), + }; + val.write(name, w) + } +} + +#[derive(Debug,GMISerializable,GMASerializable)] #[gmi_tagged_nolen(18,2)] +#[gma_name("OBJECT_LIST")] pub struct ObjectList { + #[gma_name("OBJECT")] pub objects: Vec, } @@ -182,8 +281,8 @@ pub enum Object { RBCollection(RBCollectionObject), } -impl GMISerializable for Object { - fn read(r: &mut GMIReadStream) -> ReadResult { +impl gmi::Serializable for Object { + fn read(r: &mut gmi::ReadStream) -> gmi::ReadResult { let tlv = r.tlv()?; let (res, check) = match (tlv.tag, tlv.flags) { (2, 4) => (Object::Geometry(r.read("geometry")?), false), @@ -194,7 +293,6 @@ impl GMISerializable for Object { (31, 4) => (Object::RBCollection(r.read("rigidbodycollection")?), false), _ => return Err(r.error(format!("unknown object type ({}, {})", tlv.tag, tlv.flags))), }; - println!("{:#?}", res); if check { tlv.check(r)?; } @@ -202,20 +300,47 @@ impl GMISerializable for Object { } } -#[derive(Debug,GMISerializable)] +impl gma::Serializable for Object { + fn write(&self, _name: S, w: &mut gma::WriteStream) -> gma::WriteResult<()> { + match self { + Object::Geometry(o) => o.write("", w), + Object::Light(o) => o.write("", w), + Object::AttachmentPoint(o) => o.write("", w), + Object::ConstraintSolver(o) => o.write("", w), + Object::Simulation(o) => o.write("", w), + Object::RBCollection(o) => o.write("", w), + } + } +} + +#[derive(Debug,GMISerializable,GMASerializable)] +#[gma_name("GEOMOBJECT")] pub struct GeometryObject { + #[gma_name("NODE_NAME")] pub name: String, + #[gma_name("NODE_PARENT")] pub parent: String, + #[gma_name("NODE_SHADEVERTS")] pub shade_verts: u8, + #[gma_name("NODE_TM")] pub tm: TransformMatrix, pub mesh: Mesh, } -#[derive(Debug,GMISerializable)] +#[derive(Debug,GMISerializable,GMASerializable)] #[gmi_tagged(17, 2)] +#[gma_name("NODE_TM")] pub struct TransformMatrix { + #[gma_name("NODE_NAME")] pub name: String, - pub data: [f32; 12], + #[gma_name("TM_ROW0")] + pub row0: [f32; 3], + #[gma_name("TM_ROW1")] + pub row1: [f32; 3], + #[gma_name("TM_ROW2")] + pub row2: [f32; 3], + #[gma_name("TM_ROW3")] + pub row3: [f32; 3], } #[derive(Debug, GMISerializable)] @@ -226,39 +351,190 @@ pub struct Mesh { pub face_count: u32, pub tvertex_count: u32, pub cvertex_count: u32, - pub material_ref: u32, + pub material_ref: i32, #[gmi_read_parameters(vertex_count)] - pub vertices: Vec, + pub vertices: MeshVertexList, #[gmi_read_parameters(face_count)] - pub faces: Vec, + pub faces: MeshFaceList, #[gmi_read_parameters(tvertex_count)] - pub tvertices: Vec, + pub tvertices: MeshTVertexList, #[gmi_read_parameters(if tvertex_count > 0 { face_count } else { 0 })] - pub tfaces: Vec, + pub tfaces: MeshTFaceList, pub channels: Option>, #[gmi_read_parameters(cvertex_count)] - pub cvertices: Vec, + pub cvertices: MeshCVertexList, #[gmi_read_parameters(if cvertex_count > 0 { face_count } else { 0 })] - pub cfaces: Vec, + pub cfaces: MeshCFaceList, #[gmi_read_parameters(face_count)] - pub normals: Vec, + pub normals: MeshNormalList, pub backface_cull: u32, } -#[derive(Debug,GMISerializable)] +impl gma::Serializable for Mesh { + fn write(&self, _name: S, w: &mut gma::WriteStream) -> gma::WriteResult<()> { + w.push("*MESH")?; + self.time.write("*TIMEVALUE", w)?; + self.vertex_count.write("*MESH_NUMVERTEX", w)?; + self.face_count.write("*MESH_NUMFACES", w)?; + self.vertices.write("", w)?; + self.faces.write("", w)?; + if self.tvertex_count > 0 { + self.tvertex_count.write("*MESH_NUMTVERTEX", w)?; + self.tvertices.write("", w)?; + self.face_count.write("*MESH_NUMTVFACES", w)?; + self.tfaces.write("", w)?; + } + if let Some(channels) = &self.channels { + for (i, channel) in channels.iter().enumerate() { + w.push(format!("*MESH_MAPPINGCHANNEL\t{}", i+2))?; + channel.tvertex_count.write("*MESH_NUMTVERTEX", w)?; + channel.tvertices.write("", w)?; + channel.face_count.write("*MESH_NUMTVFACES", w)?; + channel.tfaces.write("", w)?; + w.pop()?; + } + } + if self.cvertices.0.len() > 0 { + self.cvertices.0.len().write("*MESH_NUMCVERTEX", w)?; + self.cvertices.write("", w)?; + } + if self.cfaces.0.len() > 0 { + self.cfaces.0.len().write("*MESH_NUMCVFACES", w)?; + self.cfaces.write("", w)?; + } + if self.normals.0.len() > 0 { + self.normals.write(w, &self.faces)?; + } + self.backface_cull.write("*BACKFACE_CULL", w)?; + self.material_ref.write("*MATERIAL_REF", w)?; + w.pop()?; + Ok(()) + } +} + +macro_rules! specialized_vec { + ($newname:ident, $elemname:ident, $header:expr, $format:expr) => { + #[derive(Debug)] + pub struct $newname(Vec<$elemname>); + impl gmi::Serializable for $newname { + fn read(r: &mut gmi::ReadStream) -> gmi::ReadResult { + as gmi::Serializable>::read(r).map(|r| $newname(r)) + } + fn read_parametrized(r: &mut gmi::ReadStream, params: &[u32]) -> gmi::ReadResult { + as gmi::Serializable>::read_parametrized(r, params).map(|r| $newname(r)) + } + } + impl gma::Serializable for $newname { + fn write(&self, _name: S, w: &mut gma::WriteStream) -> gma::WriteResult<()> { + w.push($header)?; + for (i, v) in self.0.iter().enumerate() { + w.emit(&$format(i, v))?; + } + w.pop()?; + Ok(()) + } + } + + } +} + +specialized_vec!( + MeshVertexList, Point, + "*MESH_VERTEX_LIST", + (|i, v: &Point| { + format!("*MESH_VERTEX\t{}\t{:.6}\t{:.6}\t{:.6}", i, v.x, v.y, v.z) + }) +); + +specialized_vec!( + MeshFaceList, Face, + "*MESH_FACE_LIST", + (|i, f: &Face| { + format!( + "*MESH_FACE\t{:>4}\tA:{:>5}\tB:{:>5}\tC:{:>5}\t*MESH_MTLID {}", + i, f.a, f.b, f.c, f.mtlid) + }) +); + +specialized_vec!( + MeshTVertexList, Point, + "*MESH_TVERTLIST", + (|i, v: &Point| { + format!("*MESH_TVERT\t{}\t{:.6}\t{:.6}\t{:.6}", i, v.x, v.y, v.z) + }) +); + +specialized_vec!( + MeshTFaceList, TFace, + "*MESH_TFACELIST", + (|i, f: &TFace| { + format!("*MESH_TFACE\t{}\t{}\t{}\t{}", i, f.a, f.b, f.c) + }) +); + +specialized_vec!( + MeshCVertexList, Point, + "*MESH_CVERTLIST", + (|i, v: &Point| { + format!("*MESH_VERTCOL\t{}\t{:.6}\t{:.6}\t{:.6}", i, v.x, v.y, v.z) + }) +); + +specialized_vec!( + MeshCFaceList, TFace, + "*MESH_CFACELIST", + (|i, f: &TFace| { + format!("*MESH_CFACE\t{}\t{}\t{}\t{}", i, f.a, f.b, f.c) + }) +); + + +specialized_vec!( + MeshNormalList, FaceNormal, + "*MESH_NORMALS", + (|_, _: &FaceNormal| -> String { + // This implementation is never used. + unreachable!() + }) +); + +impl MeshNormalList { + fn write(&self, w: &mut gma::WriteStream, faces: &MeshFaceList) -> gma::WriteResult<()> { + w.push("*MESH_NORMALS")?; + for (i, (normal, face)) in self.0.iter().zip(faces.0.iter()).enumerate() { + w.emit(&format!("*MESH_FACENORMAL\t{}\t{:.6}\t{:.6}\t{:.6}", + i, normal.face[0], normal.face[1], normal.face[2]))?; + for j in 0..3 { + let vn = normal.vertex[j]; + let vno = match j { + 0 => face.a, + 1 => face.b, + 2 => face.c, + _ => unreachable!(), + }; + w.emit(&format!("\t*MESH_VERTEXNORMAL\t{}\t{:.6}\t{:.6}\t{:.6}", + vno, vn[0], vn[1], vn[2]))?; + } + } + w.pop()?; + Ok(()) + } +} + +#[derive(Debug,GMISerializable,GMASerializable)] pub struct Point { pub x: f32, pub y: f32, pub z: f32, } -#[derive(Debug,GMISerializable)] +#[derive(Debug,GMISerializable,GMASerializable)] pub struct Face { pub a: u32, pub b: u32, @@ -266,7 +542,7 @@ pub struct Face { pub mtlid: u32, } -#[derive(Debug,GMISerializable)] +#[derive(Debug,GMISerializable,GMASerializable)] pub struct TFace { pub a: u32, pub b: u32, @@ -279,7 +555,13 @@ pub struct FaceNormal { pub vertex: [[f32; 3]; 3], } -#[derive(Debug, GMISerializable)] +impl gma::Serializable for FaceNormal { + fn write(&self, _name: S, _w: &mut gma::WriteStream) -> gma::WriteResult<()> { + Ok(()) + } +} + +#[derive(Debug, GMISerializable,GMASerializable)] pub struct TextureChannel { pub unk1: u32, pub tvertex_count: u32, @@ -291,87 +573,149 @@ pub struct TextureChannel { pub face_count2: u32, #[gmi_read_parameters(tvertex_count)] - pub tvertices: Vec, + pub tvertices: MeshTVertexList, #[gmi_read_parameters(face_count)] - pub tfaces: Vec, + pub tfaces: MeshTFaceList, } -#[derive(Debug,GMISerializable)] +#[derive(Debug,GMISerializable,GMASerializable)] +#[gma_name("LIGHT")] pub struct LightObject { + #[gma_name("NODE_NAME")] pub name: String, pub tm: TransformMatrix, + #[gma_skip] pub target: String, + #[gma_name("LIGHT_TYPE")] pub light_type: LightType, pub shadows: LightShadows, pub uselight: u32, + pub spotshape: LightSpotShape, pub color: Color, + #[gma_name("LIGHT_INTENS")] pub intensity: f32, pub aspect: f32, + #[gma_skip] pub unk1: [u8; 8], pub attn_start: f32, pub attn_end: f32, pub tdist: f32, - pub use_for_attn: u32, + #[gma_name("USE FAR ATTENUATION = ")] + pub use_far_attenuation: u32, } -#[derive(Debug,GMISerializable)] +#[derive(Debug,GMISerializable,GMASerializable)] pub enum LightType { Omni = 0, } -#[derive(Debug,GMISerializable)] +#[derive(Debug,GMISerializable,GMASerializable)] pub enum LightShadows { Off = 0, } -#[derive(Debug,GMISerializable)] +#[derive(Debug,GMASerializable)] +pub enum LightSpotShape { + Circle = 0, +} + +impl gmi::Serializable for LightSpotShape { + fn read(_: &mut gmi::ReadStream) -> gmi::ReadResult { + Ok(LightSpotShape::Circle) + } +} + +#[derive(Debug,GMISerializable,GMASerializable)] +#[gma_name("GMID_ATTACHMENTPT")] pub struct AttachmentPointObject { + #[gma_name("NODE_NAME")] pub name: String, pub tm: TransformMatrix, + #[gma_name_bare("USER DATA")] pub user_data: String, } -#[derive(Debug,GMISerializable)] +#[derive(Debug,GMISerializable,GMASerializable)] +#[gma_name("GMID_HAVOK_CONSTRAINTSOLVER")] pub struct ConstraintSolverObject { + #[gma_name("NODE_NAME")] pub name: String, + #[gma_name("THRESHOLD")] pub threshold: f32, + #[gma_name("RB_COLLECTION_NAME")] pub rb_collection_name: String, pub constraints: Option, } -#[derive(Debug,GMISerializable)] +#[derive(Debug,GMISerializable,GMASerializable)] #[gmi_tagged(44, 2)] pub struct ConstraintList { pub constraints: Vec, } -#[derive(Debug,GMISerializable)] +#[derive(Debug,GMISerializable,GMASerializable)] pub enum Constraint { Foo = 1, } -#[derive(Debug,GMISerializable)] +#[derive(Debug,GMISerializable,GMASerializable)] +#[gma_name("GMID_HAVOK_SIMOBJECT")] pub struct SimulationObject { - pub name: String, - pub gravity: Point, + #[gma_name("NODE_NAME")] + pub name: ObjectName, + #[gma_name("GRAVITY")] + pub gravity: Gravity, + #[gma_name("WORLDSCALE")] pub worldscale: f32, + #[gma_name("SIMTOLERANCE")] pub simtolerance: f32, + #[gma_name("RESOLVER")] pub resolver: u32, + #[gma_name("INCLUDE_DRAG")] pub incl_drag: u8, + #[gma_name("LINEAR_DRAG")] pub linear_drag: f32, + #[gma_name("ANGULAR_DRAG")] pub angular_drag: f32, + #[gma_name("INCLUDE_DEACTIVATOR")] pub incl_deactivator: u8, + #[gma_name("SHORTFREQ")] pub shortfreq: f32, + #[gma_name("LONGFREQ")] pub longfreq: f32, - pub last_subspace: u8, + #[gma_name("USE_FAST_SUBSPACE")] + pub use_fast_subspace: u8, + #[gma_name("UPDATES_PER_TIMESTEP")] pub updates_per_timestamp: f32, + #[gma_name("NUM_COLLISION_PAIRS")] pub collision_pairs: u32, } -#[derive(Debug,GMISerializable)] +#[derive(Debug)] +pub struct Gravity(Point); + +impl gmi::Serializable for Gravity { + fn read(r: &mut gmi::ReadStream) -> gmi::ReadResult { + Point::read(r).map(|e| Gravity(e)) + } +} + +impl gma::Serializable for Gravity { + fn write(&self, name: S, w: &mut gma::WriteStream) -> gma::WriteResult<()> { + w.emit(&format!("{}\t{:.6} {:.6} {:.6}", + name.to_string(), self.0.x, self.0.y, self.0.z)) + } +} + + +#[derive(Debug,GMISerializable,GMASerializable)] +#[gma_name("GMID_HAVOK_RBCOLLECTION")] pub struct RBCollectionObject { + #[gma_name("NODE_NAME")] pub name: String, + #[gma_name("NUM_DISABLED_PAIRS")] pub disabled_pairs: u32, + #[gma_name("SOLVER_TYPE")] pub solver_type: u32, pub rigidbody_list: RigidBodyList, #[gmi_read_parameters(disabled_pairs)] @@ -384,29 +728,81 @@ pub struct RigidBodyList { pub rigidbodies: Vec, } -#[derive(Debug,GMISerializable)] -#[gmi_tagged_nolen(32, 4)] -pub struct RigidBody { - pub name: String, - pub mass: f32, - pub elasticity: f32, - pub friction: f32, - pub optimization_level: f32, - pub unyielding: u32, - pub simulation_geometry: u32, - pub geometry_proxy_name: String, - pub use_display_proxy: u8, - pub disable_collisions: u8, - pub inactive: u8, - pub display_proxy_name: String, - pub tm: TransformMatrix, - pub geo_type: u32, - pub children: Option, +impl gma::Serializable for RigidBodyList { + fn write(&self, _name: S, w: &mut gma::WriteStream) -> gma::WriteResult<()> { + self.rigidbodies.len().write("*COUNT", w)?; + w.push("*GMID_HAVOK_RIGIDBODY_LIST")?; + self.rigidbodies.len().write("*COUNT", w)?; + for rigidbody in self.rigidbodies.iter() { + rigidbody.write("", w)?; + } + w.pop()?; + Ok(()) + } } -#[derive(Debug,GMISerializable)] +#[derive(Debug,GMISerializable,GMASerializable)] +#[gmi_tagged_nolen(32, 4)] +#[gma_name("GMID_HAVOK_RIGIDBODY")] +pub struct RigidBody { + #[gma_name("NODE_NAME")] + pub name: String, + #[gma_name("MASS")] + pub mass: f32, + #[gma_name("ELASTICITY")] + pub elasticity: f32, + #[gma_name("FRICTION")] + pub friction: f32, + #[gma_name("OPTIMIZATION_LEVEL")] + pub optimization_level: f32, + #[gma_name("UNYIELDING")] + pub unyielding: u32, + #[gma_name("SIMULATION_GEOMETRY")] + pub simulation_geometry: u32, + #[gma_name("GEOMETRY_PROXY_NAME")] + pub geometry_proxy_name: ObjectName, + #[gma_name("USE_DISPLAY_PROXY")] + pub use_display_proxy: u8, + #[gma_name("DISABLE_COLLISIONS")] + pub disable_collisions: u8, + #[gma_name("INACTIVE")] + pub inactive: u8, + #[gma_name("DISPLAY_PROXY_NAME")] + pub display_proxy_name: ObjectName, + pub tm: TransformMatrix, + #[gma_name("HAVOK_GEO_TYPE")] + pub geo_type: GeoType, + pub children: NestedRigidBodyList, +} + +#[derive(Debug)] +pub struct NestedRigidBodyList(Option); + +impl gmi::Serializable for NestedRigidBodyList { + fn read(r: &mut gmi::ReadStream) -> gmi::ReadResult { + Option::::read(r).map(|el| NestedRigidBodyList(el)) + } +} + +impl gma::Serializable for NestedRigidBodyList { + fn write(&self, name: S, w: &mut gma::WriteStream) -> gma::WriteResult<()> { + let count = self.0.as_ref().map(|l| l.rigidbodies.len()).unwrap_or(0); + count.write("*NUMBER_OF_CHILDREN", w)?; + self.0.write(name, w)?; + Ok(()) + } +} + +#[derive(Debug,GMISerializable,GMASerializable)] +pub enum GeoType { + Standard = 0, +} + +#[derive(Debug,GMISerializable,GMASerializable)] #[gmi_tagged_nolen(51, 2)] +#[gma_name("GMID_HAVOK_DIS_COLLISION_PAIRS")] pub struct DisabledCollisionPairList { + #[gma_name("")] pub pairs: Vec, } @@ -415,3 +811,10 @@ pub struct DisabledCollisionPair { pub a: String, pub b: String, } + +impl gma::Serializable for DisabledCollisionPair { + fn write(&self, _name: S, w: &mut gma::WriteStream) -> gma::WriteResult<()> { + w.emit(&format!("{{ {}\t{} }}", + self.a, self.b)) + } +} diff --git a/gmfmacros/src/lib.rs b/gmfmacros/src/lib.rs index 98233f9..742ab83 100644 --- a/gmfmacros/src/lib.rs +++ b/gmfmacros/src/lib.rs @@ -3,8 +3,13 @@ use syn::{parse_macro_input, DeriveInput, parenthesized}; use quote::quote; struct GMITagged { + #[allow(dead_code)] paren_token: syn::token::Paren, fields: syn::punctuated::Punctuated, + + nolen: bool, + ty: u32, + flag: u32, } impl syn::parse::Parse for GMITagged { @@ -13,11 +18,31 @@ impl syn::parse::Parse for GMITagged { Ok(Self { paren_token: parenthesized!(content in input), fields: content.parse_terminated(syn::LitInt::parse)?, + + nolen: false, + ty: 0, + flag: 0, }) } } +impl GMITagged { + fn from_attrs(attrs: &Vec) -> Option { + let attr = attrs.iter().find(|a| a.path.is_ident("gmi_tagged") || a.path.is_ident("gmi_tagged_nolen"))?; + let mut res = syn::parse2::(attr.tokens.clone()).expect("invalid gmi_tagged attribute"); + if res.fields.len() != 2 { + panic!("gmi_tagged(_len) takes two arguments"); + } + + res.nolen = attr.path.is_ident("gmi_tagged_nolen"); + res.ty = res.fields[0].base10_parse().expect("gmi_tagged arg 1 not a number"); + res.flag = res.fields[1].base10_parse().expect("gmi_tagged arg 2 not a number"); + Some(res) + } +} + struct GMIReadParameters { + #[allow(dead_code)] paren_token: syn::token::Paren, fields: syn::punctuated::Punctuated, } @@ -32,6 +57,16 @@ impl syn::parse::Parse for GMIReadParameters { } } +impl GMIReadParameters { + fn from_attrs(attrs: &Vec) -> Option> { + if let Some(attr) = attrs.iter().find(|a| a.path.is_ident("gmi_read_parameters")) { + let parameters = syn::parse2::(attr.tokens.clone()).expect("invalid gmi_read_parameters attribute"); + return Some(parameters.fields.iter().cloned().collect()); + } + None + } +} + #[proc_macro_derive(GMISerializable, attributes(gmi_tagged, gmi_tagged_nolen, gmi_read_parameters))] pub fn gmi_serializable_macro(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); @@ -41,22 +76,16 @@ pub fn gmi_serializable_macro(input: TokenStream) -> TokenStream { let mut tlv_header = quote! { }; let mut tlv_footer = quote! { }; - if let Some(attr) = input.attrs.iter().find(|a| a.path.is_ident("gmi_tagged") || a.path.is_ident("gmi_tagged_nolen")) { - let nolen = attr.path.is_ident("gmi_tagged_nolen"); - let tagged = syn::parse2::(attr.tokens.clone()).expect("invalid gmi_tagged attribute"); - if tagged.fields.len() != 2 { - panic!("gmi_tagged takes two arguments"); - } - let a: u32 = tagged.fields[0].base10_parse().expect("gmi_tagged arg 1 not a number"); - let b: u32 = tagged.fields[1].base10_parse().expect("gmi_tagged arg 2 not a number"); + if let Some(tagged) = GMITagged::from_attrs(&input.attrs) { + let (ty, flags) = (tagged.ty, tagged.flag); tlv_header = quote! { let tlv = _r.tlv()?; - if tlv.tag != #a || tlv.flags != #b { - return Err(tlv.error("unsupported tag/flags (wanted ...)")); + if tlv.tag != #ty || tlv.flags != #flags { + return Err(tlv.error(format!("unsupported tag/flags (wanted {}/{}, got {}/{})", #ty, #flags, tlv.tag, tlv.flags))); } }; - if !nolen { + if !tagged.nolen { tlv_footer = quote! { tlv.check(_r)?; }; @@ -74,8 +103,8 @@ pub fn gmi_serializable_macro(input: TokenStream) -> TokenStream { }).collect(); quote! { - impl GMISerializable for #ident { - fn read(_r: &mut GMIReadStream) -> ReadResult { + impl gmi::Serializable for #ident { + fn read(_r: &mut gmi::ReadStream) -> gmi::ReadResult { #tlv_header let v: u32 = _r.read("value")?; let res = match v { @@ -94,8 +123,7 @@ pub fn gmi_serializable_macro(input: TokenStream) -> TokenStream { let field_type = &field.ty; let field_ident_str = field_ident.as_ref().unwrap().to_string(); let field_ident_quoted = syn::LitStr::new(&field_ident_str, proc_macro2::Span::call_site()); - let parameters = field_parameters(&field.attrs); - if let Some(p) = parameters { + if let Some(p) = GMIReadParameters::from_attrs(&field.attrs) { quote! { let #field_ident: #field_type = _r.read_parametrized(#field_ident_quoted, &[#(#p),*])?; } @@ -112,8 +140,8 @@ pub fn gmi_serializable_macro(input: TokenStream) -> TokenStream { } }).collect(); quote! { - impl GMISerializable for #ident { - fn read(_r: &mut GMIReadStream) -> ReadResult { + impl gmi::Serializable for #ident { + fn read(_r: &mut gmi::ReadStream) -> gmi::ReadResult { #tlv_header #(#temporaries)* let res = Self { @@ -131,10 +159,149 @@ pub fn gmi_serializable_macro(input: TokenStream) -> TokenStream { TokenStream::from(expanded) } -fn field_parameters(attrs: &Vec) -> Option> { - if let Some(attr) = attrs.iter().find(|a| a.path.is_ident("gmi_read_parameters")) { - let parameters = syn::parse2::(attr.tokens.clone()).expect("invalid gmi_read_parameters attribute"); - return Some(parameters.fields.iter().cloned().collect()); - } - None +struct GMAName { + #[allow(dead_code)] + paren_token: syn::token::Paren, + fields: syn::punctuated::Punctuated, + + name: String, + bare: bool, +} + +impl syn::parse::Parse for GMAName { + fn parse(input: syn::parse::ParseStream) -> syn::parse::Result { + let content; + Ok(Self { + paren_token: parenthesized!(content in input), + fields: content.parse_terminated(::parse)?, + name: String::new(), + bare: false, + }) + } +} + +impl GMAName { + fn from_attrs(attrs: &Vec) -> Option { + let attr = attrs.iter() + .find(|a| a.path.is_ident("gma_name") + || a.path.is_ident("gma_name_bare"))?; + let mut res = syn::parse2::(attr.tokens.clone()).expect("invalid gma_name attribute"); + if res.fields.len() != 1 { + panic!("gma_name takes one argument"); + } + + res.name = res.fields[0].value(); + res.bare = attr.path.is_ident("gma_name_bare"); + Some(res) + } +} + + +struct GMAValue { + #[allow(dead_code)] + paren_token: syn::token::Paren, + fields: syn::punctuated::Punctuated, + + name: String, +} + +impl syn::parse::Parse for GMAValue { + fn parse(input: syn::parse::ParseStream) -> syn::parse::Result { + let content; + Ok(Self { + paren_token: parenthesized!(content in input), + fields: content.parse_terminated(::parse)?, + name: String::new(), + }) + } +} + +impl GMAValue { + fn from_attrs(attrs: &Vec) -> Option { + let attr = attrs.iter().find(|a| a.path.is_ident("gma_value"))?; + let mut res = syn::parse2::(attr.tokens.clone()).expect("invalid gma_value attribute"); + if res.fields.len() != 1 { + panic!("gma_value takes one argument"); + } + + res.name = res.fields[0].value(); + Some(res) + } +} + +#[proc_macro_derive(GMASerializable, attributes(gma_name, gma_name_bare, gma_value, gma_skip))] +pub fn gma_serializable_macro(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let ident = input.ident; + + let expanded = match input.data { + syn::Data::Enum(en) => { + let branches: Vec = en.variants.iter().map(|variant| { + let vident = &variant.ident; + let value: String = GMAValue::from_attrs(&variant.attrs) + .map(|s| s.name.to_string()) + .or_else(|| { + Some(vident.to_string()) + }).unwrap(); + quote! { + #ident::#vident => #value, + } + }).collect(); + + quote! { + impl gma::Serializable for #ident { + fn write(&self, _name: S, _w: &mut gma::WriteStream) -> gma::WriteResult<()> { + _w.emit_pair(&_name.to_string(), match self { + #(#branches)* + }) + } + } + } + }, + syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Named(fields), .. }) => { + let struct_name = GMAName::from_attrs(&input.attrs) + .map(|s| "*".to_string() + &s.name) + .or_else(|| { + Some("*".to_string() + &gma_name_from_ident(&ident)) + }).unwrap(); + let writes: Vec = fields.named.iter().map(|field| { + let field_ident = &field.ident; + let field_skip = field.attrs.iter().any(|a| a.path.is_ident("gma_skip")); + let field_name = GMAName::from_attrs(&field.attrs) + .map(|s| + if s.bare { + s.name + } else { + "*".to_string() + &s.name + }) + .or_else(|| { + Some(struct_name.clone() + "_" + &gma_name_from_ident(field_ident.as_ref().unwrap())) + }).unwrap(); + if field_skip { + quote! { } + } else { + quote! { + self.#field_ident.write(#field_name, _w)?; + } + } + }).collect(); + quote! { + impl gma::Serializable for #ident { + fn write(&self, _name: S, _w: &mut gma::WriteStream) -> gma::WriteResult<()> { + _w.push(#struct_name)?; + #(#writes)* + _w.pop()?; + Ok(()) + } + } + } + }, + _ => panic!("unimplemented"), + }; + + TokenStream::from(expanded) +} + +fn gma_name_from_ident(ident: &syn::Ident) -> String { + ident.to_string().to_uppercase().replace("_", "") } diff --git a/gmftool/src/main.rs b/gmftool/src/main.rs index ce610c8..f900823 100644 --- a/gmftool/src/main.rs +++ b/gmftool/src/main.rs @@ -1,8 +1,8 @@ fn main() -> std::io::Result<()> { let f = std::fs::File::open("/home/q3k/Games/RA2/Robot Arena 2 v1.4/Arenas/box/boxarena.gmf")?; - let gmi = gmflib::GMI::parse(f).unwrap(); + let gmf = gmflib::GMF::read_gmi(f).unwrap(); - println!("{:#?}", gmi); + gmf.write_gma(std::io::stdout()).unwrap(); Ok(()) }