From a0fb4d60bdd4a64e9db4c6fd601cfd6ba30a028b Mon Sep 17 00:00:00 2001 From: Serge Bazanski Date: Thu, 14 Apr 2022 11:08:01 +0000 Subject: [PATCH] first pass --- .gitignore | 2 + Cargo.lock | 61 ++++++ Cargo.toml | 7 + gmflib/Cargo.lock | 79 ++++++++ gmflib/Cargo.toml | 9 + gmflib/src/lib.rs | 4 + gmflib/src/machinery.rs | 195 ++++++++++++++++++ gmflib/src/types.rs | 435 ++++++++++++++++++++++++++++++++++++++++ gmfmacros/Cargo.toml | 14 ++ gmfmacros/src/lib.rs | 99 +++++++++ gmftool/Cargo.toml | 9 + gmftool/src/main.rs | 8 + 12 files changed, 922 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 gmflib/Cargo.lock create mode 100644 gmflib/Cargo.toml create mode 100644 gmflib/src/lib.rs create mode 100644 gmflib/src/machinery.rs create mode 100644 gmflib/src/types.rs create mode 100644 gmfmacros/Cargo.toml create mode 100644 gmfmacros/src/lib.rs create mode 100644 gmftool/Cargo.toml create mode 100644 gmftool/src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bef918e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +**swp diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..8c319f7 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,61 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "gmflib" +version = "0.1.0" +dependencies = [ + "gmfmacros", +] + +[[package]] +name = "gmfmacros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "gmftool" +version = "0.1.0" +dependencies = [ + "gmflib", +] + +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704df27628939572cd88d33f171cd6f896f4eaca85252c6e0a72d8d8287ee86f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9afb596 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[workspace] + +members = [ + "gmflib", + "gmftool", + "gmfmacros", +] diff --git a/gmflib/Cargo.lock b/gmflib/Cargo.lock new file mode 100644 index 0000000..136296b --- /dev/null +++ b/gmflib/Cargo.lock @@ -0,0 +1,79 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "binread" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16598dfc8e6578e9b597d9910ba2e73618385dc9f4b1d43dd92c349d6be6418f" +dependencies = [ + "binread_derive", + "rustversion", +] + +[[package]] +name = "binread_derive" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9672209df1714ee804b1f4d4f68c8eb2a90b1f7a07acf472f88ce198ef1fed" +dependencies = [ + "either", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "gmftools" +version = "0.1.0" +dependencies = [ + "binread", +] + +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" + +[[package]] +name = "syn" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704df27628939572cd88d33f171cd6f896f4eaca85252c6e0a72d8d8287ee86f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" diff --git a/gmflib/Cargo.toml b/gmflib/Cargo.toml new file mode 100644 index 0000000..19f5ba2 --- /dev/null +++ b/gmflib/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "gmflib" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +gmfmacros = { path = "../gmfmacros" } diff --git a/gmflib/src/lib.rs b/gmflib/src/lib.rs new file mode 100644 index 0000000..1c507b5 --- /dev/null +++ b/gmflib/src/lib.rs @@ -0,0 +1,4 @@ +mod machinery; +pub mod types; + +pub use types::*; diff --git a/gmflib/src/machinery.rs b/gmflib/src/machinery.rs new file mode 100644 index 0000000..7a1733a --- /dev/null +++ b/gmflib/src/machinery.rs @@ -0,0 +1,195 @@ +use std::fmt; + +pub struct ReadError { + pub msg: String, + pub stack: Vec, +} + +impl fmt::Debug for ReadError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> 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) + .field("stack", &stack) + .finish() + } +} + +#[derive(Debug, Clone)] +pub struct ParseFrame { + pub name: String, + pub loc: usize, +} + +pub type ReadResult = Result; + +pub struct GMFElementDescriptor { + fields: Vec, +} + +pub struct GMFElementField { + rust_name: &'static str, + gma_repr: GMARepr, +} + +pub enum GMARepr { + Named(&'static str), +} + +pub trait GMFElement: Sized { + fn read(r: &mut ReadStream) -> ReadResult; + //fn descriptor() -> GMFElementDescriptor; +} + +impl GMFElement for Vec { + fn read(r: &mut ReadStream) -> ReadResult { + let count: u32 = r.read("count")?; + (0..count).map(|_| r.read("data")).collect() + } +} + +impl GMFElement for [T; N] { + fn read(r: &mut ReadStream) -> ReadResult { + (0..N).map(|_| r.read("data")).collect::>>()?.try_into().map_err(|_| r.error("eof")) + } +} + + +impl GMFElement 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); + } +} + +impl GMFElement for u8 { + fn read(r: &mut ReadStream) -> ReadResult { + Ok(r.bytes(1)?[0]) + } +} + +impl GMFElement 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 GMFElement 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 GMFElement 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)?; + let bytes: Vec = bytes.into_iter().filter(|&b| b != 0).collect(); + let res = std::str::from_utf8(&bytes).map_err(|_| r.error("invalid string")).map(String::from); + r.pop(); + res + } +} + +pub struct ReadStream { + backing: R, + stack: Vec, + pos: usize, +} + +impl ReadStream { + pub fn new(r: R) -> Self { + Self { + backing: r, + pos: 0, + stack: vec![ + ParseFrame{name: "root".into(), loc: 0}, + ], + } + } + + //fn context(&mut self, s: String) { + // self.stack.last_mut().unwrap().name = s; + // self.stack.last_mut().unwrap().loc = self.pos; + //} + + fn push(&mut self, s: String) { + self.stack.push(ParseFrame { + name: s, + loc: self.pos, + }); + } + + fn pop(&mut self) { + self.stack.pop(); + } +} + +impl ReadStream { + pub fn bytes(&mut self, n: usize) -> ReadResult> { + let mut buf: Vec = vec![0; n]; + self.pos += n; + match self.backing.read(&mut buf) { + Ok(_) => return Ok(buf), + Err(_) => return Err(self.error("eof")), + } + } + + pub fn tlv(&mut self) -> ReadResult { + let start_pos = self.pos; + let tag: u32 = self.read("tag")?; + let flags: u32 = self.read("flags")?; + let length: u32 = self.read("length")?; + Ok(TLV { + tag, flags, length, start_pos, + }) + } + + pub fn read(&mut self, ctx: &str) -> ReadResult { + self.push(ctx.to_string()); + let res = T::read(self); + self.pop(); + res + } + + pub fn error(&self, msg: S) -> ReadError { + ReadError { + msg: msg.to_string(), + stack: self.stack.clone(), + } + } +} + +pub struct TLV { + pub tag: u32, + pub flags: u32, + pub length: u32, + + start_pos: usize, +} + +impl TLV { + pub fn error(&self, msg: &str) -> ReadError { + ReadError { + msg: msg.to_string(), + stack: vec![], + } + } + + 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 { + msg: format!("tlv length mismatch: expected end at {}, got at {}", expected, r.pos), + stack: vec![], + }); + } + Ok(()) + } +} diff --git a/gmflib/src/types.rs b/gmflib/src/types.rs new file mode 100644 index 0000000..477112a --- /dev/null +++ b/gmflib/src/types.rs @@ -0,0 +1,435 @@ +use gmfmacros::GMFElement; + +use crate::machinery::{ReadResult, ReadStream, GMFElement}; + +#[derive(Debug)] +pub struct GMI { + pub version: u32, + pub model_type: ModelType, + pub unk1: u32, + pub unk2: u32, + + pub scene: Scene, + pub materials: MaterialList, + pub objects: ObjectList, +} + +impl GMI { + pub fn parse(r: R) -> ReadResult { + let mut r = ReadStream::new(r); + r.read("gmi") + } +} + +impl GMFElement for GMI { + fn read(r: &mut ReadStream) -> ReadResult { + if r.bytes(3)? != b"GMI".to_vec() { + return Err(r.error("invaid magic")); + } + let version: u32 = r.read("version")?; + let model_type = r.read("model_type")?; + let unk1: u32 = r.read("unk1")?; + if unk1 != 0 { + return Err(r.error("invalid unk1")); + } + let unk2: u32 = r.read("unk2")?; + if unk2 != 15 { + return Err(r.error("invalid unk2")); + } + Ok(Self { + version, model_type, unk1, unk2, + scene: r.read("scene")?, + materials: r.read("material_list")?, + objects: r.read("object_list")?, + }) + } +} + +#[derive(Debug,GMFElement)] +pub struct Color { + pub r: u8, + pub g: u8, + pub b: u8, + pub a: u8, +} + +#[derive(Debug,GMFElement)] +pub enum MapKind { + Diffuse = 1, + SelfIllum = 5, + Opacity = 6, +} + +#[derive(Debug,GMFElement)] +pub enum MapType { + Screen = 4, +} + +#[derive(Debug,GMFElement)] +pub enum BitmapFilter { + Pyramidal = 0, + SAT = 1, +} + +#[derive(Debug, GMFElement)] +pub enum ModelType { + BasicModel = 1, +} + +#[derive(Debug,GMFElement)] +#[gmf_tagged(1,2)] +pub struct Scene { + pub filename: String, + pub first_frame: u32, + pub last_frame: u32, + pub frame_speed: u32, + pub ticks_per_frame: u32, + pub background: Color, + pub ambient: Color, +} + +#[derive(Debug,GMFElement)] +#[gmf_tagged(7, 2)] +pub struct MaterialList { + pub materials: Vec, +} + +#[derive(Debug,GMFElement)] +#[gmf_tagged(8, 2)] +pub struct Material { + pub ref_no: u32, + pub name: String, + pub class: String, + pub ambient: Color, + pub diffuse: Color, + pub specular: Color, + pub shine: f32, + pub shine_strength: f32, + pub wiresize: f32, + pub transparency: f32, + pub shading: Shading, + pub xp_falloff: f32, + pub selfillum: f32, + pub falloff: Falloff, + pub xp_type: XPType, + pub textures: Option, + pub sub: Option, +} + +#[derive(Debug,GMFElement)] +pub enum Shading { + Other = 0, + Blinn = 0xc, +} + +#[derive(Debug, GMFElement)] +pub enum Falloff { + In = 0, +} + +#[derive(Debug,GMFElement)] +pub enum XPType { + Other = 0, + Filter = 1, +} + +#[derive(Debug,GMFElement)] +#[gmf_tagged(14, 2)] +pub struct TextureList { + pub textures: Vec, +} + +#[derive(Debug,GMFElement)] +#[gmf_tagged(15, 4)] +pub struct Texture { + pub name: String, + pub class: String, + pub bitmap: String, + pub amount: f32, + pub kind: MapKind, + pub map_type: MapType, + pub u_offset: f32, + pub v_offset: f32, + pub u_tiling: f32, + pub v_tiling: f32, + pub angle: f32, + pub blur: f32, + pub blur_offset: f32, + pub noise_amt: f32, + pub noise_size: f32, + pub noise_level: u32, + pub noise_phase: f32, + pub invert: u32, + pub unknown: u32, + pub filter: BitmapFilter, + pub channel: u32, + pub sub: Option, +} + +#[derive(Debug,GMFElement)] +#[gmf_tagged(18,2)] +pub struct ObjectList { + pub objects: Vec, +} + +#[derive(Debug)] +pub enum Object { + Geometry(GeometryObject), + Light(LightObject), + AttachmentPoint(AttachmentPointObject), + ConstraintSolver(ConstraintSolverObject), + Simulation(SimulationObject), + RBCollection(RBCollectionObject), +} + +impl GMFElement for Object { + fn read(r: &mut ReadStream) -> ReadResult { + let tlv = r.tlv()?; + let (res, check) = match (tlv.tag, tlv.flags) { + (2, 4) => (Object::Geometry(r.read("geometry")?), false), + (5, 3) => (Object::Light(r.read("light")?), false), + (21, 2) => (Object::AttachmentPoint(r.read("attachmentpoint")?), true), + (42, 3) => (Object::ConstraintSolver(r.read("constraintsolver")?), false), + (30, 2) => (Object::Simulation(r.read("simulation")?), false), + _ => return Err(r.error(format!("unknown object type ({}, {})", tlv.tag, tlv.flags))), + }; + println!("{:#?}", res); + if check { + tlv.check(r)?; + } + Ok(res) + } +} + +#[derive(Debug,GMFElement)] +pub struct GeometryObject { + pub name: String, + pub parent: String, + pub shade_verts: u8, + pub tm: TransformMatrix, + pub mesh: Mesh, +} + +#[derive(Debug,GMFElement)] +#[gmf_tagged(17, 2)] +pub struct TransformMatrix { + pub name: String, + pub data: [f32; 12], +} + +#[derive(Debug)] +pub struct Mesh { + pub time: u32, + pub vertex_count: u32, + pub face_count: u32, + pub tvertex_count: u32, + pub cvertex_count: u32, + pub material_ref: u32, + + pub vertices: Vec, + pub faces: Vec, + + pub tvertices: Vec, + pub tfaces: Vec, + + pub channels: Option>, + + pub cvertices: Vec, + pub cfaces: Vec, + + pub normals: Vec, + + pub backface_cull: u32, +} + +impl GMFElement for Mesh { + fn read(r: &mut ReadStream) -> ReadResult { + let tlv = r.tlv()?; + if tlv.tag != 16 || tlv.flags != 4 { + return Err(r.error("unexpected tag/flags (wanted 16/4)")); + } + + let time = r.read("time")?; + let vertex_count = r.read("vertex_count")?; + let face_count = r.read("face_count")?; + let tvertex_count = r.read("tvertex_count")?; + let cvertex_count = r.read("cvertex_count")?; + let material_ref = r.read("material_ref")?; + + let res = Self { + time, vertex_count, face_count, tvertex_count, + cvertex_count, material_ref, + + vertices: (0..vertex_count).map(|_| r.read("vertices")).collect::>>()?, + faces: (0..face_count).map(|_| r.read("faces")).collect::>>()?, + tvertices: (0..tvertex_count).map(|_| r.read("tvertices")).collect::>>()?, + tfaces: if tvertex_count > 0 { + (0..face_count).map(|_| r.read("tfaces")).collect::>>()? + } else { + vec![] + }, + channels: r.read("channels")?, + cvertices: (0..cvertex_count).map(|_| r.read("cvertices")).collect::>>()?, + cfaces: if cvertex_count > 0 { + (0..face_count).map(|_| r.read("cfaces")).collect::>>()? + } else { + vec![] + }, + normals: (0..face_count).map(|_| r.read("normals")).collect::>>()?, + + backface_cull: r.read("backface_cull")?, + }; + // Broken for stock files. + //tlv.check(r)?; + Ok(res) + } +} + +#[derive(Debug,GMFElement)] +pub struct Point { + pub x: f32, + pub y: f32, + pub z: f32, +} + +#[derive(Debug,GMFElement)] +pub struct Face { + pub a: u32, + pub b: u32, + pub c: u32, + pub mtlid: u32, +} + +#[derive(Debug,GMFElement)] +pub struct TFace { + pub a: u32, + pub b: u32, + pub c: u32, +} + +#[derive(Debug,GMFElement)] +pub struct FaceNormal { + pub face: [f32; 3], + pub vertex: [[f32; 3]; 3], +} + +#[derive(Debug)] +pub struct TextureChannel { + pub unk1: u32, + pub tvertex_count: u32, + pub unk2: u32, + pub face_count: u32, + pub unk3: u32, + pub unk4: u32, + pub tvertex_count2: u32, + pub face_count2: u32, + + pub tvertices: Vec, + pub tfaces: Vec, +} + +impl GMFElement for TextureChannel { + fn read(r: &mut ReadStream) -> ReadResult { + let unk1 = r.read("unk1")?; + let tvertex_count = r.read("tvertex_count")?; + let unk2 = r.read("unk2")?; + let face_count = r.read("face_count")?; + let unk3 = r.read("unk3")?; + let unk4 = r.read("unk4")?; + let tvertex_count2 = r.read("tvertex_count2")?; + let face_count2 = r.read("face_count2")?; + + let res = Self { + unk1, tvertex_count, + unk2, face_count, + unk3, unk4, + tvertex_count2, + face_count2, + + tvertices: (0..tvertex_count).map(|_| r.read("tvertices")).collect::>>()?, + tfaces: if tvertex_count > 0 { + (0..face_count).map(|_| r.read("tfaces")).collect::>>()? + } else { + vec![] + }, + }; + Ok(res) + } +} + +#[derive(Debug,GMFElement)] +pub struct LightObject { + pub name: String, + pub tm: TransformMatrix, + pub target: String, + pub light_type: LightType, + pub shadows: LightShadows, + pub uselight: u32, + pub color: Color, + pub intensity: f32, + pub aspect: f32, + pub unk1: [u8; 8], + pub attn_start: f32, + pub attn_end: f32, + pub tdist: f32, + pub use_for_attn: u32, +} + +#[derive(Debug,GMFElement)] +pub enum LightType { + Omni = 0, +} + +#[derive(Debug,GMFElement)] +pub enum LightShadows { + Off = 0, +} + +#[derive(Debug,GMFElement)] +pub struct AttachmentPointObject { + pub name: String, + pub tm: TransformMatrix, + pub user_data: String, +} + +#[derive(Debug,GMFElement)] +pub struct ConstraintSolverObject { + pub name: String, + pub threshold: f32, + pub rb_collection_name: String, + pub constraints: Option, +} + +#[derive(Debug,GMFElement)] +#[gmf_tagged(44, 2)] +pub struct ConstraintList { + pub constraints: Vec, +} + +#[derive(Debug,GMFElement)] +pub enum Constraint { + Foo = 1, +} + +#[derive(Debug,GMFElement)] +pub struct SimulationObject { + pub name: String, + pub gravity: Point, + pub worldscale: f32, + pub simtolerance: f32, + pub resolver: u32, + pub incl_drag: u8, + pub linear_drag: f32, + pub angular_drag: f32, + pub incl_deactivator: u8, + pub shortfreq: f32, + pub longfreq: f32, + pub last_subspace: u8, + pub updates_per_timestamp: f32, + pub collision_pairs: u32, +} + +#[derive(Debug,GMFElement)] +pub struct RBCollectionObject { + pub name: String, +} diff --git a/gmfmacros/Cargo.toml b/gmfmacros/Cargo.toml new file mode 100644 index 0000000..7a5bacd --- /dev/null +++ b/gmfmacros/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "gmfmacros" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +proc-macro = true + +[dependencies] +syn = "1" +quote = "1" +proc-macro2 = "1" diff --git a/gmfmacros/src/lib.rs b/gmfmacros/src/lib.rs new file mode 100644 index 0000000..4d11855 --- /dev/null +++ b/gmfmacros/src/lib.rs @@ -0,0 +1,99 @@ +use proc_macro::TokenStream; +use syn::{parse_macro_input, DeriveInput, parenthesized}; +use quote::quote; + +struct GMFTagged { + paren_token: syn::token::Paren, + fields: syn::punctuated::Punctuated, +} + +impl syn::parse::Parse for GMFTagged { + fn parse(input: syn::parse::ParseStream) -> syn::parse::Result { + let content; + Ok(Self { + paren_token: parenthesized!(content in input), + fields: content.parse_terminated(syn::LitInt::parse)?, + }) + } +} + +#[proc_macro_derive(GMFElement, attributes(gmf_tagged))] +pub fn gmf_element_macro(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let ident = input.ident; + + let mut tlv_header = quote! { }; + let mut tlv_footer = quote! { }; + + if let Some(attr) = input.attrs.iter().find(|a| a.path.is_ident("gmf_tagged")) { + let tagged = syn::parse2::(attr.tokens.clone()).expect("invalid gmf_tagged attribute"); + if tagged.fields.len() != 2 { + panic!("gmf_tagged takes two arguments"); + } + let a: u32 = tagged.fields[0].base10_parse().expect("gmf_tagged arg 1 not a number"); + let b: u32 = tagged.fields[1].base10_parse().expect("gmf_tagged arg 2 not a number"); + + tlv_header = quote! { + let tlv = r.tlv()?; + if tlv.tag != #a || tlv.flags != #b { + return Err(tlv.error("unsupported tag/flags (wanted ...)")); + } + }; + tlv_footer = quote! { + tlv.check(r)?; + }; + } + + let expanded = match input.data { + syn::Data::Enum(en) => { + let branches: Vec = en.variants.iter().map(|variant| { + let vident = &variant.ident; + let (_, expr) = variant.discriminant.as_ref().expect("enum variants must have values"); + quote! { + #expr => #ident::#vident, + } + }).collect(); + + quote! { + impl GMFElement for #ident { + fn read(r: &mut ReadStream) -> ReadResult { + #tlv_header + let v: u32 = r.read("value")?; + let res = match v { + #(#branches)* + _ => return Err(r.error("unknown ")), + }; + #tlv_footer + Ok(res) + } + } + } + }, + syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Named(fields), .. }) => { + let fields: Vec = fields.named.iter().map(|field| { + let field_ident = &field.ident; + 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()); + quote! { + #field_ident: r.read(#field_ident_quoted)?, + } + }).collect(); + quote! { + impl GMFElement for #ident { + fn read(r: &mut ReadStream) -> ReadResult { + #tlv_header + let res = Self { + #(#fields)* + }; + #tlv_footer + Ok(res) + } + } + } + }, + _ => panic!("unimplemented"), + }; + + TokenStream::from(expanded) +} diff --git a/gmftool/Cargo.toml b/gmftool/Cargo.toml new file mode 100644 index 0000000..4e848e7 --- /dev/null +++ b/gmftool/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "gmftool" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +gmflib = { path = "../gmflib" } diff --git a/gmftool/src/main.rs b/gmftool/src/main.rs new file mode 100644 index 0000000..ce610c8 --- /dev/null +++ b/gmftool/src/main.rs @@ -0,0 +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(); + + println!("{:#?}", gmi); + + Ok(()) +}