first pass
parent
d41dbd36a0
commit
a0fb4d60bd
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
**swp
|
|
@ -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"
|
|
@ -0,0 +1,7 @@
|
||||||
|
[workspace]
|
||||||
|
|
||||||
|
members = [
|
||||||
|
"gmflib",
|
||||||
|
"gmftool",
|
||||||
|
"gmfmacros",
|
||||||
|
]
|
|
@ -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"
|
|
@ -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" }
|
|
@ -0,0 +1,4 @@
|
||||||
|
mod machinery;
|
||||||
|
pub mod types;
|
||||||
|
|
||||||
|
pub use types::*;
|
|
@ -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(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
}
|
|
@ -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"
|
|
@ -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)
|
||||||
|
}
|
|
@ -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" }
|
|
@ -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(())
|
||||||
|
}
|
Loading…
Reference in New Issue