first pass

main
q3k 2022-04-14 11:08:01 +00:00
parent d41dbd36a0
commit a0fb4d60bd
12 changed files with 922 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
**swp

61
Cargo.lock generated Normal file
View File

@ -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"

7
Cargo.toml Normal file
View File

@ -0,0 +1,7 @@
[workspace]
members = [
"gmflib",
"gmftool",
"gmfmacros",
]

79
gmflib/Cargo.lock generated Normal file
View File

@ -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"

9
gmflib/Cargo.toml Normal file
View File

@ -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" }

4
gmflib/src/lib.rs Normal file
View File

@ -0,0 +1,4 @@
mod machinery;
pub mod types;
pub use types::*;

195
gmflib/src/machinery.rs Normal file
View File

@ -0,0 +1,195 @@
use std::fmt;
pub struct ReadError {
pub msg: String,
pub stack: Vec<ParseFrame>,
}
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::<Vec<String>>().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<T> = Result<T, ReadError>;
pub struct GMFElementDescriptor {
fields: Vec<GMFElementField>,
}
pub struct GMFElementField {
rust_name: &'static str,
gma_repr: GMARepr,
}
pub enum GMARepr {
Named(&'static str),
}
pub trait GMFElement: Sized {
fn read<R: std::io::Read>(r: &mut ReadStream<R>) -> ReadResult<Self>;
//fn descriptor() -> GMFElementDescriptor;
}
impl<T: GMFElement> GMFElement for Vec<T> {
fn read<R: std::io::Read>(r: &mut ReadStream<R>) -> ReadResult<Self> {
let count: u32 = r.read("count")?;
(0..count).map(|_| r.read("data")).collect()
}
}
impl<T: GMFElement, const N: usize> GMFElement for [T; N] {
fn read<R: std::io::Read>(r: &mut ReadStream<R>) -> ReadResult<Self> {
(0..N).map(|_| r.read("data")).collect::<ReadResult<Vec<T>>>()?.try_into().map_err(|_| r.error("eof"))
}
}
impl<T: GMFElement> GMFElement for Option<T> {
fn read<R: std::io::Read>(r: &mut ReadStream<R>) -> ReadResult<Self> {
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: std::io::Read>(r: &mut ReadStream<R>) -> ReadResult<Self> {
Ok(r.bytes(1)?[0])
}
}
impl GMFElement for u32 {
fn read<R: std::io::Read>(r: &mut ReadStream<R>) -> ReadResult<Self> {
let buf: [u8; 4] = r.bytes(4)?.try_into().unwrap();
Ok(u32::from_le_bytes(buf))
}
}
impl GMFElement for f32 {
fn read<R: std::io::Read>(r: &mut ReadStream<R>) -> ReadResult<Self> {
let buf: [u8; 4] = r.bytes(4)?.try_into().unwrap();
Ok(f32::from_le_bytes(buf))
}
}
impl GMFElement for String {
fn read<R: std::io::Read>(r: &mut ReadStream<R>) -> ReadResult<Self> {
let len: u32 = r.read("len")?;
r.push("bytes".to_string());
let bytes = r.bytes(len as usize)?;
let bytes: Vec<u8> = 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<R: std::io::Read> {
backing: R,
stack: Vec<ParseFrame>,
pos: usize,
}
impl<R: std::io::Read> ReadStream<R> {
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 <R: std::io::Read> ReadStream<R> {
pub fn bytes(&mut self, n: usize) -> ReadResult<Vec<u8>> {
let mut buf: Vec<u8> = 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<TLV> {
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<T: GMFElement>(&mut self, ctx: &str) -> ReadResult<T> {
self.push(ctx.to_string());
let res = T::read(self);
self.pop();
res
}
pub fn error<S: std::string::ToString>(&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<R: std::io::Read>(&self, r: &mut ReadStream<R>) -> 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(())
}
}

435
gmflib/src/types.rs Normal file
View File

@ -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: std::io::Read>(r: R) -> ReadResult<Self> {
let mut r = ReadStream::new(r);
r.read("gmi")
}
}
impl GMFElement for GMI {
fn read<R: std::io::Read>(r: &mut ReadStream<R>) -> ReadResult<Self> {
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<Material>,
}
#[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<TextureList>,
pub sub: Option<MaterialList>,
}
#[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<Texture>,
}
#[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<TextureList>,
}
#[derive(Debug,GMFElement)]
#[gmf_tagged(18,2)]
pub struct ObjectList {
pub objects: Vec<Object>,
}
#[derive(Debug)]
pub enum Object {
Geometry(GeometryObject),
Light(LightObject),
AttachmentPoint(AttachmentPointObject),
ConstraintSolver(ConstraintSolverObject),
Simulation(SimulationObject),
RBCollection(RBCollectionObject),
}
impl GMFElement for Object {
fn read<R: std::io::Read>(r: &mut ReadStream<R>) -> ReadResult<Self> {
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<Point>,
pub faces: Vec<Face>,
pub tvertices: Vec<Point>,
pub tfaces: Vec<TFace>,
pub channels: Option<Vec<TextureChannel>>,
pub cvertices: Vec<Point>,
pub cfaces: Vec<TFace>,
pub normals: Vec<FaceNormal>,
pub backface_cull: u32,
}
impl GMFElement for Mesh {
fn read<R: std::io::Read>(r: &mut ReadStream<R>) -> ReadResult<Self> {
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::<ReadResult<Vec<Point>>>()?,
faces: (0..face_count).map(|_| r.read("faces")).collect::<ReadResult<Vec<Face>>>()?,
tvertices: (0..tvertex_count).map(|_| r.read("tvertices")).collect::<ReadResult<Vec<Point>>>()?,
tfaces: if tvertex_count > 0 {
(0..face_count).map(|_| r.read("tfaces")).collect::<ReadResult<Vec<TFace>>>()?
} else {
vec![]
},
channels: r.read("channels")?,
cvertices: (0..cvertex_count).map(|_| r.read("cvertices")).collect::<ReadResult<Vec<Point>>>()?,
cfaces: if cvertex_count > 0 {
(0..face_count).map(|_| r.read("cfaces")).collect::<ReadResult<Vec<TFace>>>()?
} else {
vec![]
},
normals: (0..face_count).map(|_| r.read("normals")).collect::<ReadResult<Vec<FaceNormal>>>()?,
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<Point>,
pub tfaces: Vec<TFace>,
}
impl GMFElement for TextureChannel {
fn read<R: std::io::Read>(r: &mut ReadStream<R>) -> ReadResult<Self> {
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::<ReadResult<Vec<_>>>()?,
tfaces: if tvertex_count > 0 {
(0..face_count).map(|_| r.read("tfaces")).collect::<ReadResult<Vec<_>>>()?
} 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<ConstraintList>,
}
#[derive(Debug,GMFElement)]
#[gmf_tagged(44, 2)]
pub struct ConstraintList {
pub constraints: Vec<Constraint>,
}
#[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,
}

14
gmfmacros/Cargo.toml Normal file
View File

@ -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"

99
gmfmacros/src/lib.rs Normal file
View File

@ -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<syn::LitInt, syn::Token![,]>,
}
impl syn::parse::Parse for GMFTagged {
fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
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::<GMFTagged>(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<proc_macro2::TokenStream> = 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: std::io::Read>(r: &mut ReadStream<R>) -> ReadResult<Self> {
#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<proc_macro2::TokenStream> = 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: std::io::Read>(r: &mut ReadStream<R>) -> ReadResult<Self> {
#tlv_header
let res = Self {
#(#fields)*
};
#tlv_footer
Ok(res)
}
}
}
},
_ => panic!("unimplemented"),
};
TokenStream::from(expanded)
}

9
gmftool/Cargo.toml Normal file
View File

@ -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" }

8
gmftool/src/main.rs Normal file
View File

@ -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(())
}