This commit is contained in:
Robin Appelman 2022-03-10 23:32:51 +01:00
commit b0d3245e07
13 changed files with 510 additions and 55 deletions

View file

@ -1,10 +1,14 @@
fn main() -> Result<(), vmdl::MdlError> { use std::env::args;
let mut args = std::env::args(); use std::fs;
let _ = args.next(); use vmdl::mdl::Mdl;
let data = std::fs::read(args.next().expect("No demo file provided"))?;
let mdl = vmdl::Mdl::read(&data)?;
dbg!(mdl.bones); fn main() -> Result<(), vmdl::ModelError> {
let mut args = args();
let _ = args.next();
let data = fs::read(args.next().expect("No demo file provided"))?;
let mdl = Mdl::read(&data)?;
dbg!(mdl.header);
Ok(()) Ok(())
} }

View file

@ -2,32 +2,32 @@ use thiserror::Error;
#[non_exhaustive] #[non_exhaustive]
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum MdlError { pub enum ModelError {
#[error("io error while reading data: {0}")] #[error("io error while reading data: {0}")]
IO(#[from] std::io::Error), IO(#[from] std::io::Error),
#[error(transparent)] #[error(transparent)]
String(#[from] StringError), String(#[from] StringError),
#[error("Malformed field found while parsing: {0:#}")] #[error("Malformed field found while parsing: {0:#}")]
MalformedData(binrw::Error), MalformedData(binrw::Error),
#[error("referenced data is out of bounds")] #[error("referenced data to {data} is out of bounds at {offset}")]
OutOfBounds, OutOfBounds { data: &'static str, offset: usize },
} }
impl From<binrw::Error> for MdlError { impl From<binrw::Error> for ModelError {
fn from(e: binrw::Error) -> Self { fn from(e: binrw::Error) -> Self {
use binrw::Error; use binrw::Error;
// only a few error types should be generated by our code // only a few error types should be generated by our code
match e { match e {
Error::Io(e) => MdlError::IO(e), Error::Io(e) => ModelError::IO(e),
Error::Custom { err, .. } => { Error::Custom { err, .. } => {
if err.is::<StringError>() { if err.is::<StringError>() {
MdlError::String(*err.downcast::<StringError>().unwrap()) ModelError::String(*err.downcast::<StringError>().unwrap())
} else { } else {
panic!("unexpected custom error") panic!("unexpected custom error")
} }
} }
e => MdlError::MalformedData(e), e => ModelError::MalformedData(e),
} }
} }
} }

View file

@ -1,9 +1,9 @@
use crate::Mdl; use crate::mdl::Mdl;
use std::ops::Deref; use std::ops::Deref;
/// A handle represents a data structure in the mdl file and the mdl file containing it. /// A handle represents a mdl structure in the mdl file and the mdl file containing it.
/// ///
/// Keeping a reference of the mdl file with the data is required since a lot of data types /// Keeping a reference of the mdl file with the mdl is required since a lot of mdl types
/// reference parts from other structures in the mdl file /// reference parts from other structures in the mdl file
#[derive(Debug)] #[derive(Debug)]
pub struct Handle<'a, T> { pub struct Handle<'a, T> {

View file

@ -1,41 +1,40 @@
mod data;
mod error; mod error;
mod handle; mod handle;
pub mod mdl;
mod shared;
pub mod vtx;
pub mod vvd;
use binrw::{BinRead, BinReaderExt}; use binrw::{BinRead, BinReaderExt};
pub use data::*;
pub use error::*; pub use error::*;
pub use handle::Handle; pub use handle::Handle;
pub use shared::*;
use std::io::Cursor; use std::io::Cursor;
#[derive(Debug, Clone)]
pub struct Mdl {
pub header: StudioHeader,
pub bones: Vec<Bone>,
}
impl Mdl {
pub fn read(data: &[u8]) -> Result<Self, MdlError> {
let mut reader = Cursor::new(data);
let header: StudioHeader = reader.read_le()?;
let bones = read_indexes(header.bone_indexes(), data).collect::<Result<_, _>>()?;
Ok(Mdl { header, bones })
}
}
fn read_indexes<'a, I: Iterator<Item = usize> + 'static, T: BinRead>( fn read_indexes<'a, I: Iterator<Item = usize> + 'static, T: BinRead>(
indexes: I, indexes: I,
data: &'a [u8], data: &'a [u8],
) -> impl Iterator<Item = Result<T, MdlError>> + 'a ) -> impl Iterator<Item = Result<T, ModelError>> + 'a
where where
T::Args: Default, T::Args: Default,
{ {
indexes indexes
.map(|index| data.get(index..).ok_or(MdlError::OutOfBounds)) .map(|index| {
data.get(index..).ok_or_else(|| ModelError::OutOfBounds {
data: "Bone",
offset: index,
})
})
.map(|data| { .map(|data| {
data.and_then(|data| { data.and_then(|data| {
let mut cursor = Cursor::new(data); let mut cursor = Cursor::new(data);
cursor.read_le().map_err(MdlError::from) cursor.read_le().map_err(ModelError::from)
}) })
}) })
} }
fn index_range(index: i32, count: i32, size: usize) -> impl Iterator<Item = usize> {
(0..count as usize)
.map(move |i| i * size)
.map(move |i| index as usize + i)
}

View file

@ -1,4 +1,5 @@
use crate::{Bone, FixedString, Vector}; use crate::mdl::Bone;
use crate::{index_range, FixedString, Vector};
use binrw::BinRead; use binrw::BinRead;
use std::mem::size_of; use std::mem::size_of;
@ -33,8 +34,8 @@ pub struct StudioHeader {
* Note that indexes/counts are not always paired and ordered consistently. * Note that indexes/counts are not always paired and ordered consistently.
*/ */
// mstudiobone_t // mstudiobone_t
bone_count: i32, // Number of data sections (of type mstudiobone_t) bone_count: i32, // Number of mdl sections (of type mstudiobone_t)
bone_offset: i32, // Offset of first data section bone_offset: i32, // Offset of first mdl section
// mstudiobonecontroller_t // mstudiobonecontroller_t
bone_controller_count: i32, bone_controller_count: i32,
@ -121,7 +122,7 @@ pub struct StudioHeader {
pub surface_prop_index: i32, pub surface_prop_index: i32,
// Unusual: In this one index comes first, then count. // Unusual: In this one index comes first, then count.
// Key-value data is a series of strings. If you can't find // Key-value mdl is a series of strings. If you can't find
// what you're interested in, check the associated PHY file as well. // what you're interested in, check the associated PHY file as well.
key_value_index: i32, key_value_index: i32,
key_value_count: i32, key_value_count: i32,
@ -286,10 +287,4 @@ impl StudioHeader {
} }
} }
fn index_range(index: i32, count: i32, size: usize) -> impl Iterator<Item = usize> {
(0..count as usize)
.map(move |i| i * size)
.map(move |i| index as usize + i)
}
static_assertions::const_assert_eq!(size_of::<StudioHeader>() - size_of::<FixedString<0>>(), 408); static_assertions::const_assert_eq!(size_of::<StudioHeader>() - size_of::<FixedString<0>>(), 408);

26
src/mdl/mod.rs Normal file
View file

@ -0,0 +1,26 @@
mod bone;
mod header;
mod header2;
pub use bone::*;
pub use header::*;
pub use header2::*;
use crate::{read_indexes, ModelError};
use binrw::BinReaderExt;
use std::io::Cursor;
#[derive(Debug, Clone)]
pub struct Mdl {
pub header: StudioHeader,
pub bones: Vec<Bone>,
}
impl Mdl {
pub fn read(data: &[u8]) -> Result<Self, ModelError> {
let mut reader = Cursor::new(data);
let header: StudioHeader = reader.read_le()?;
let bones = read_indexes(header.bone_indexes(), data).collect::<Result<_, _>>()?;
Ok(Mdl { header, bones })
}
}

View file

@ -1,14 +1,6 @@
mod bone; use crate::{BinRead, StringError};
mod header;
mod header2;
pub use bone::*;
pub use header::*;
pub use header2::*;
use crate::error::StringError;
use arrayvec::ArrayString; use arrayvec::ArrayString;
use binrw::{BinRead, BinResult, ReadOptions}; use binrw::{BinResult, ReadOptions};
use std::fmt; use std::fmt;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};

206
src/vtx/mod.rs Normal file
View file

@ -0,0 +1,206 @@
mod raw;
use crate::ModelError;
use binrw::BinReaderExt;
use raw::*;
pub use raw::{MeshFlags, StripFlags, StripGroupFlags, Vertex};
use std::io::Cursor;
pub const MDL_VERSION: i32 = 7;
type Result<T> = std::result::Result<T, ModelError>;
#[derive(Debug, Clone)]
pub struct Vtx {
pub header: VtxHeader,
pub body_parts: Vec<BodyPart>,
}
impl Vtx {
pub fn read(data: &[u8]) -> Result<Self> {
let mut reader = Cursor::new(data);
let header: VtxHeader = reader.read_le()?;
Ok(Vtx {
body_parts: header
.body_indexes()
.map(|index| {
let data = data.get(index..).ok_or_else(|| ModelError::OutOfBounds {
data: "BodyPart",
offset: index,
})?;
let mut reader = Cursor::new(data);
let header = reader.read_le()?;
BodyPart::read(data, header)
})
.collect::<Result<_>>()?,
header,
})
}
}
#[derive(Debug, Clone)]
pub struct BodyPart {
pub models: Vec<Model>,
}
impl BodyPart {
fn read(data: &[u8], header: BodyPartHeader) -> Result<Self> {
Ok(BodyPart {
models: header
.model_indexes()
.map(|index| {
let data = data.get(index..).ok_or_else(|| ModelError::OutOfBounds {
data: "Model",
offset: index,
})?;
let mut reader = Cursor::new(data);
let header = reader.read_le()?;
Model::read(data, header)
})
.collect::<Result<_>>()?,
})
}
}
#[derive(Debug, Clone)]
pub struct Model {
pub lods: Vec<ModelLod>,
}
impl Model {
fn read(data: &[u8], header: ModelHeader) -> Result<Self> {
Ok(Model {
lods: header
.lod_indexes()
.map(|index| {
let data = data.get(index..).ok_or_else(|| ModelError::OutOfBounds {
data: "ModelLod",
offset: index,
})?;
let mut reader = Cursor::new(data);
let header = reader.read_le()?;
ModelLod::read(data, header)
})
.collect::<Result<_>>()?,
})
}
}
#[derive(Debug, Clone)]
pub struct ModelLod {
pub meshes: Vec<Mesh>,
pub switch_point: f32,
}
impl ModelLod {
fn read(data: &[u8], header: ModelLodHeader) -> Result<Self> {
Ok(ModelLod {
meshes: header
.mesh_indexes()
.map(|index| {
let data = data.get(index..).ok_or_else(|| ModelError::OutOfBounds {
data: "Mesh",
offset: index,
})?;
let mut reader = Cursor::new(data);
let header = reader.read_le()?;
Mesh::read(data, header)
})
.collect::<Result<_>>()?,
switch_point: header.switch_point,
})
}
}
#[derive(Debug, Clone)]
pub struct Mesh {
pub strip_groups: Vec<StripGroup>,
pub flags: MeshFlags,
}
impl Mesh {
fn read(data: &[u8], header: MeshHeader) -> Result<Self> {
Ok(Mesh {
strip_groups: header
.strip_group_indexes()
.map(|index| {
let data = data.get(index..).ok_or_else(|| ModelError::OutOfBounds {
data: "StripGroup",
offset: index,
})?;
let mut reader = Cursor::new(data);
let header = reader.read_le()?;
StripGroup::read(data, header)
})
.collect::<Result<_>>()?,
flags: header.flags,
})
}
}
#[derive(Debug, Clone)]
pub struct StripGroup {
// todo vertex indexes
// todo topologies
pub vertices: Vec<Vertex>,
pub strips: Vec<Strip>,
pub flags: StripGroupFlags,
}
impl StripGroup {
fn read(data: &[u8], header: StripGroupHeader) -> Result<Self> {
Ok(StripGroup {
vertices: header
.vertex_indexes()
.map(|index| {
let data = data.get(index..).ok_or_else(|| ModelError::OutOfBounds {
data: "Vertex",
offset: index,
})?;
let mut reader = Cursor::new(data);
reader.read_le().map_err(ModelError::from)
})
.collect::<Result<_>>()?,
strips: header
.strip_indexes()
.map(|index| {
let data = data.get(index..).ok_or_else(|| ModelError::OutOfBounds {
data: "Strip",
offset: index,
})?;
let mut reader = Cursor::new(data);
let header = reader.read_le()?;
Strip::read(data, header)
})
.collect::<Result<_>>()?,
flags: header.flags,
})
}
}
#[derive(Debug, Clone)]
pub struct Strip {
// todo vertex indexes
// todo bone state changes
pub vertices: Vec<Vertex>,
pub flags: StripFlags,
}
impl Strip {
fn read(data: &[u8], header: StripHeader) -> Result<Self> {
Ok(Strip {
vertices: header
.vertex_indexes()
.map(|index| {
let data = data.get(index..).ok_or_else(|| ModelError::OutOfBounds {
data: "Vertex",
offset: index,
})?;
let mut reader = Cursor::new(data);
reader.read_le().map_err(ModelError::from)
})
.collect::<Result<_>>()?,
flags: header.flags,
})
}
}

202
src/vtx/raw.rs Normal file
View file

@ -0,0 +1,202 @@
use crate::index_range;
use binrw::BinRead;
use bitflags::bitflags;
use std::mem::size_of;
#[derive(Debug, Clone, BinRead)]
pub struct VtxHeader {
pub version: i32,
pub vertex_cache_size: i32,
pub max_bones_per_strip: u16,
pub max_bones_per_triangle: u16,
pub max_bones_per_vertex: i32,
pub checksum: [u8; 4],
pub lod_count: i32,
pub material_replacement_list: i32,
body_part_count: i32,
body_part_offset: i32,
}
static_assertions::const_assert_eq!(size_of::<VtxHeader>(), 36);
impl VtxHeader {
pub fn body_indexes(&self) -> impl Iterator<Item = usize> {
index_range(
self.body_part_offset,
self.body_part_count,
size_of::<BodyPartHeader>(),
)
}
}
#[derive(Debug, Clone, BinRead)]
pub struct BodyPartHeader {
model_count: i32,
model_offset: i32,
}
impl BodyPartHeader {
pub fn model_indexes(&self) -> impl Iterator<Item = usize> {
index_range(
self.model_offset,
self.model_count,
size_of::<ModelHeader>(),
)
}
}
#[derive(Debug, Clone, BinRead)]
pub struct ModelHeader {
lod_count: i32,
lod_offset: i32,
}
impl ModelHeader {
pub fn lod_indexes(&self) -> impl Iterator<Item = usize> {
index_range(self.lod_offset, self.lod_count, size_of::<ModelLodHeader>())
}
}
#[derive(Debug, Clone, BinRead)]
pub struct ModelLodHeader {
mesh_count: i32,
mesh_offset: i32,
pub switch_point: f32,
}
static_assertions::const_assert_eq!(size_of::<ModelLodHeader>(), 12);
impl ModelLodHeader {
pub fn mesh_indexes(&self) -> impl Iterator<Item = usize> {
index_range(self.mesh_offset, self.mesh_count, size_of::<MeshHeader>())
}
}
#[derive(Debug, Clone, Copy, BinRead)]
#[repr(packed)]
pub struct MeshHeader {
strip_group_count: i32,
strip_group_offset: i32,
pub flags: MeshFlags,
}
impl MeshHeader {
pub fn strip_group_indexes(&self) -> impl Iterator<Item = usize> {
index_range(
self.strip_group_offset,
self.strip_group_count,
size_of::<StripGroupHeader>(),
)
}
}
bitflags! {
#[derive(BinRead)]
pub struct MeshFlags: u8 {
const IS_TEETH = 0x01;
const IS_EYES = 0x02;
}
}
#[derive(Debug, Clone, Copy, BinRead)]
#[repr(packed)]
pub struct StripGroupHeader {
vertex_count: i32,
vertex_offset: i32,
index_count: i32,
index_offset: i32,
strip_count: i32,
strip_offset: i32,
pub flags: StripGroupFlags,
}
static_assertions::const_assert_eq!(size_of::<StripGroupHeader>(), 25);
impl StripGroupHeader {
/// Index into the VVD file vertexes
pub fn vertex_indexes(&self) -> impl Iterator<Item = usize> {
index_range(
self.vertex_offset,
self.vertex_count,
size_of::<u16>(), // Vertex index from .VVD's vertex array
)
}
pub fn index_indexes(&self) -> impl Iterator<Item = usize> {
index_range(self.index_offset, self.index_count, size_of::<Vertex>())
}
pub fn strip_indexes(&self) -> impl Iterator<Item = usize> {
index_range(
self.strip_offset,
self.strip_count,
size_of::<StripHeader>(),
)
}
}
bitflags! {
#[derive(BinRead)]
pub struct StripGroupFlags: u8 {
const IS_FLEXED = 0x01;
const IS_HWSKINNED = 0x02;
const IS_DELTA_FLEXED = 0x04;
const SUPPRESS_HW_MORPH = 0x08;
}
}
#[derive(Debug, Clone, Copy, BinRead)]
#[repr(packed)]
pub struct StripHeader {
index_count: i32,
index_offset: i32,
vertex_count: i32,
vertex_offset: i32,
pub flags: StripFlags,
bone_state_change_count: i32,
bone_state_change_offset: i32,
}
static_assertions::const_assert_eq!(size_of::<StripHeader>(), 25);
bitflags! {
#[derive(BinRead)]
pub struct StripFlags: u8 {
const IS_TRI_LIST = 0x01;
const IS_TRI_STRIP = 0x02;
}
}
impl StripHeader {
/// Index into the VVD file vertexes
pub fn vertex_indexes(&self) -> impl Iterator<Item = usize> {
index_range(
self.vertex_offset,
self.vertex_count,
size_of::<u16>(), // Vertex index from .VVD's vertex array
)
}
pub fn index_indexes(&self) -> impl Iterator<Item = usize> {
index_range(self.index_offset, self.index_count, size_of::<Vertex>())
}
pub fn bone_state_change_indexes(&self) -> impl Iterator<Item = usize> {
index_range(
self.bone_state_change_offset,
self.bone_state_change_count,
todo!(),
)
}
}
#[derive(Debug, Clone, Copy, BinRead)]
#[repr(packed)]
pub struct Vertex {
pub bone_weight_indexes: [u8; 3],
pub bone_count: u8,
pub original_mesh_vertex_id: u16,
pub bone_id: [u8; 3],
}
static_assertions::const_assert_eq!(size_of::<Vertex>(), 9);

16
src/vvd.rs Normal file
View file

@ -0,0 +1,16 @@
use binrw::BinRead;
pub const MDL_VERSION: i32 = 7;
#[derive(Debug, Clone, BinRead)]
pub struct Header {
pub id: i32,
pub version: i32,
pub checksum: [u8; 4],
pub lod_count: i32,
pub lod_vertex_count: [i32; 8],
pub fixup_count: i32,
pub fixup_index: i32,
pub vertex_index: i32,
pub tangent_index: i32,
}

15
tests/parse.rs Normal file
View file

@ -0,0 +1,15 @@
use std::fs::read;
use vmdl::mdl::Mdl;
use vmdl::vtx::Vtx;
#[test]
fn parse_mdl() {
let data = read("data/barrel01.mdl").unwrap();
Mdl::read(&data).unwrap();
}
#[test]
fn parse_vtx() {
let data = read("data/barrel01.dx90.vtx").unwrap();
Vtx::read(&data).unwrap();
}