mirror of
https://codeberg.org/icewind/vmdl.git
synced 2026-06-03 16:44:11 +02:00
bones
This commit is contained in:
parent
03470ab7f4
commit
f33101119c
10 changed files with 635 additions and 8 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,2 +1,3 @@
|
|||
/target
|
||||
Cargo.lock
|
||||
data
|
||||
|
|
@ -2,7 +2,11 @@
|
|||
name = "vmdl"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
exclude = ["data"]
|
||||
|
||||
[dependencies]
|
||||
arrayvec = "0.7.2"
|
||||
binrw = "0.8.0"
|
||||
thiserror = "1.0.30"
|
||||
static_assertions = "1.1.0"
|
||||
bitflags = "1.0.4"
|
||||
10
examples/parse.rs
Normal file
10
examples/parse.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
fn main() -> Result<(), vmdl::MdlError> {
|
||||
let mut args = std::env::args();
|
||||
let _ = args.next();
|
||||
let data = std::fs::read(args.next().expect("No demo file provided"))?;
|
||||
let mdl = vmdl::Mdl::read(&data)?;
|
||||
|
||||
dbg!(mdl.bones);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
58
src/data/bone.rs
Normal file
58
src/data/bone.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
use crate::{Quaternion, RadianEuler, Vector};
|
||||
use binrw::BinRead;
|
||||
use bitflags::bitflags;
|
||||
|
||||
#[derive(Debug, Clone, BinRead)]
|
||||
pub struct Bone {
|
||||
pub sz_name_index: i32,
|
||||
pub parent: i32, // parent bone
|
||||
pub bone_controller: [i32; 6], // bone controller index, -1 == none
|
||||
|
||||
pub pos: Vector,
|
||||
pub quaternion: Quaternion,
|
||||
pub rot: RadianEuler,
|
||||
pub pos_scale: Vector,
|
||||
pub rot_scale: Vector,
|
||||
|
||||
pub pose_to_bone: [[f32; 3]; 4], // 3x4 matrix
|
||||
pub q_alignment: Quaternion,
|
||||
pub flags: BoneFlags,
|
||||
pub proc_type: i32,
|
||||
pub proc_index: i32, // procedural rule
|
||||
pub physics_bone: i32, // index into physically simulated bone
|
||||
pub surface_prop_idx: i32, // index into string table for property name
|
||||
pub contents: i32, // See BSPFlags.h for the contents flags
|
||||
|
||||
#[allow(dead_code)]
|
||||
reserved: [i32; 8], // remove as appropriate
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(BinRead)]
|
||||
pub struct BoneFlags: u32 {
|
||||
const BONE_PHYSICALLY_SIMULATED = 0x00000001;
|
||||
const BONE_PHYSICS_PROCEDURAL = 0x00000002;
|
||||
const BONE_ALWAYS_PROCEDURAL = 0x00000004;
|
||||
const BONE_SCREEN_ALIGN_SPHERE = 0x00000008;
|
||||
const BONE_SCREEN_ALIGN_CYLINDER = 0x00000010;
|
||||
|
||||
const BONE_USED_BY_HITBOX = 0x00000100;
|
||||
const BONE_USED_BY_ATTACHMENT = 0x00000200;
|
||||
|
||||
const BONE_USED_BY_VERTEX_LOD0 = 0x00000400;
|
||||
const BONE_USED_BY_VERTEX_LOD1 = 0x00000800;
|
||||
const BONE_USED_BY_VERTEX_LOD2 = 0x00001000;
|
||||
const BONE_USED_BY_VERTEX_LOD3 = 0x00002000;
|
||||
const BONE_USED_BY_VERTEX_LOD4 = 0x00004000;
|
||||
const BONE_USED_BY_VERTEX_LOD5 = 0x00008000;
|
||||
const BONE_USED_BY_VERTEX_LOD6 = 0x00010000;
|
||||
const BONE_USED_BY_VERTEX_LOD7 = 0x00020000;
|
||||
const BONE_USED_BY_BONE_MERGE = 0x00040000;
|
||||
|
||||
const BONE_TYPE_MASK = 0x00F00000;
|
||||
const BONE_FIXED_ALIGNMENT = 0x00100000;
|
||||
|
||||
const BONE_HAS_SAVEFRAME_POS = 0x00200000;
|
||||
const BONE_HAS_SAVEFRAME_ROT = 0x00400000;
|
||||
}
|
||||
}
|
||||
295
src/data/header.rs
Normal file
295
src/data/header.rs
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
use crate::{Bone, FixedString, Vector};
|
||||
use binrw::BinRead;
|
||||
use std::mem::size_of;
|
||||
|
||||
pub const FILETYPE_ID: i32 = i32::from_be_bytes(*b"IDST");
|
||||
pub const MDL_VERSION: i32 = 48;
|
||||
|
||||
#[derive(Debug, Clone, BinRead)]
|
||||
pub struct StudioHeader {
|
||||
pub id: i32,
|
||||
pub version: i32,
|
||||
pub checksum: [u8; 4], // This has to be the same in the phy and vtx files to load!
|
||||
pub name: FixedString<64>,
|
||||
pub data_length: i32,
|
||||
|
||||
pub eye_position: Vector, // Position of player viewpoint relative to model origin
|
||||
pub illumination_position: Vector, // Position (relative to model origin) used to calculate ambient light contribution and cubemap reflections for the entire model.
|
||||
pub hull_min: Vector, // Corner of model hull box with the least X/Y/Z values
|
||||
pub hull_max: Vector, // Opposite corner of model hull box
|
||||
pub view_bb_min: Vector,
|
||||
pub view_bb_max: Vector,
|
||||
|
||||
pub flags: i32, // Binary flags in little-endian order.
|
||||
// ex (00000001,00000000,00000000,11000000) means flags for position 0, 30, and 31 are set.
|
||||
// Set model flags section for more information
|
||||
|
||||
/*
|
||||
* After this point, the header contains many references to offsets
|
||||
* within the MDL file and the number of items at those offsets.
|
||||
*
|
||||
* Offsets are from the very beginning of the file.
|
||||
*
|
||||
* Note that indexes/counts are not always paired and ordered consistently.
|
||||
*/
|
||||
// mstudiobone_t
|
||||
bone_count: i32, // Number of data sections (of type mstudiobone_t)
|
||||
bone_offset: i32, // Offset of first data section
|
||||
|
||||
// mstudiobonecontroller_t
|
||||
bone_controller_count: i32,
|
||||
bone_controller_offset: i32,
|
||||
|
||||
// mstudiohitboxset_t
|
||||
hitbox_count: i32,
|
||||
hitbox_offset: i32,
|
||||
|
||||
// mstudioanimdesc_t
|
||||
local_animation_count: i32,
|
||||
local_animation_offset: i32,
|
||||
|
||||
// mstudioseqdesc_t
|
||||
local_seq_count: i32,
|
||||
local_seq_offset: i32,
|
||||
|
||||
pub activity_list_version: i32, // ??
|
||||
pub events_indexed: i32, // ??
|
||||
|
||||
// VMT texture filenames
|
||||
// mstudiotexture_t
|
||||
texture_count: i32,
|
||||
texture_offset: i32,
|
||||
|
||||
// This offset points to a series of ints.
|
||||
// Each int value, in turn, is an offset relative to the start of this header/the-file,
|
||||
// At which there is a null-terminated string.
|
||||
texture_dir_count: i32,
|
||||
texture_dir_offset: i32,
|
||||
|
||||
// Each skin-family assigns a texture-id to a skin location
|
||||
pub skin_reference_count: i32,
|
||||
pub skin_r_family_count: i32,
|
||||
pub skin_reference_index: i32,
|
||||
|
||||
// mstudiobodyparts_t
|
||||
body_part_count: i32,
|
||||
body_part_offset: i32,
|
||||
|
||||
// Local attachment points
|
||||
// mstudioattachment_t
|
||||
attachment_count: i32,
|
||||
attachment_offset: i32,
|
||||
|
||||
// Node values appear to be single bytes, while their names are null-terminated strings.
|
||||
local_node_count: i32,
|
||||
local_node_index: i32,
|
||||
local_node_name_index: i32,
|
||||
|
||||
// mstudioflexdesc_t
|
||||
flex_desc_count: i32,
|
||||
flex_desc_index: i32,
|
||||
|
||||
// mstudioflexcontroller_t
|
||||
flex_controller_count: i32,
|
||||
flex_controller_index: i32,
|
||||
|
||||
// mstudioflexrule_t
|
||||
flex_rules_count: i32,
|
||||
flex_rules_index: i32,
|
||||
|
||||
// IK probably referse to inverse kinematics
|
||||
// mstudioikchain_t
|
||||
ik_chain_count: i32,
|
||||
ik_chain_index: i32,
|
||||
|
||||
// Information about any "mouth" on the model for speech animation
|
||||
// More than one sounds pretty creepy.
|
||||
// mstudiomouth_t
|
||||
mouths_count: i32,
|
||||
mouths_index: i32,
|
||||
|
||||
// mstudioposeparamdesc_t
|
||||
local_pose_param_count: i32,
|
||||
local_pose_param_index: i32,
|
||||
|
||||
/*
|
||||
* For anyone trying to follow along, as of this writing,
|
||||
* the next "surfaceprop_index" value is at position 0x0134 (308)
|
||||
* from the start of the file.
|
||||
*/
|
||||
// Surface property value (single null-terminated string)
|
||||
pub surface_prop_index: i32,
|
||||
|
||||
// Unusual: In this one index comes first, then count.
|
||||
// Key-value data is a series of strings. If you can't find
|
||||
// what you're interested in, check the associated PHY file as well.
|
||||
key_value_index: i32,
|
||||
key_value_count: i32,
|
||||
|
||||
// More inverse-kinematics
|
||||
// mstudioiklock_t
|
||||
ik_lock_count: i32,
|
||||
ik_lock_index: i32,
|
||||
|
||||
pub mass: f32, // Mass of object (4-bytes)
|
||||
pub contents: i32, // ??
|
||||
|
||||
// Other models can be referenced for re-used sequences and animations
|
||||
// (See also: The $includemodel QC option.)
|
||||
// mstudiomodelgroup_t
|
||||
include_model_count: i32,
|
||||
include_model_index: i32,
|
||||
|
||||
pub virtual_model: i32, // Placeholder for mutable-void*
|
||||
// Note that the SDK only compiles as 32-bit, so an int and a pointer are the same size (4 bytes)
|
||||
|
||||
// mstudioanimblock_t
|
||||
anim_blocks_name_index: i32,
|
||||
anim_blocks_count: i32,
|
||||
anim_blocks_index: i32,
|
||||
|
||||
pub anim_block_model: i32, // Placeholder for mutable-void*
|
||||
|
||||
// Points to a series of bytes?
|
||||
pub bone_table_name_index: i32,
|
||||
|
||||
pub vertex_base: i32, // Placeholder for void*
|
||||
pub offset_base: i32, // Placeholder for void*
|
||||
|
||||
// Used with $constantdirectionallight from the QC
|
||||
// Model should have flag #13 set if enabled
|
||||
pub directional_dot_product: u8,
|
||||
|
||||
pub root_lod: u8, // Preferred rather than clamped
|
||||
|
||||
// 0 means any allowed, N means Lod 0 -> (N-1)
|
||||
pub num_allowed_root_lods: u8,
|
||||
|
||||
#[allow(dead_code)]
|
||||
unused0: u8, // ??
|
||||
#[allow(dead_code)]
|
||||
unused1: i32, // ??
|
||||
|
||||
pub flex_controller_ui_count: i32,
|
||||
pub flex_controller_ui_index: i32,
|
||||
|
||||
pub vert_anim_fixed_point_scale: f32,
|
||||
pub unused2: i32,
|
||||
|
||||
pub studio_hdr2_index: i32,
|
||||
|
||||
#[allow(dead_code)]
|
||||
unused3: i32,
|
||||
}
|
||||
|
||||
impl StudioHeader {
|
||||
pub fn header2_index(&self) -> Option<usize> {
|
||||
(self.studio_hdr2_index > 0)
|
||||
.then(|| self.studio_hdr2_index)
|
||||
.and_then(|index| usize::try_from(index).ok())
|
||||
}
|
||||
|
||||
pub fn bone_indexes(&self) -> impl Iterator<Item = usize> {
|
||||
index_range(self.bone_offset, self.bone_count, size_of::<Bone>())
|
||||
}
|
||||
|
||||
pub fn bone_controller_indexes(&self) -> impl Iterator<Item = usize> {
|
||||
index_range(self.bone_controller_offset, self.bone_controller_count, 1)
|
||||
}
|
||||
|
||||
pub fn hitbox_indexes(&self) -> impl Iterator<Item = usize> {
|
||||
index_range(self.hitbox_offset, self.hitbox_count, 1)
|
||||
}
|
||||
|
||||
pub fn local_animation_indexes(&self) -> impl Iterator<Item = usize> {
|
||||
index_range(self.local_animation_offset, self.local_animation_count, 1)
|
||||
}
|
||||
|
||||
pub fn local_sequence_indexes(&self) -> impl Iterator<Item = usize> {
|
||||
index_range(self.local_seq_offset, self.local_seq_count, 1)
|
||||
}
|
||||
|
||||
pub fn texture_indexes(&self) -> impl Iterator<Item = usize> {
|
||||
index_range(self.texture_offset, self.texture_count, 1)
|
||||
}
|
||||
|
||||
pub fn texture_dir_indexes(&self) -> impl Iterator<Item = usize> {
|
||||
index_range(self.texture_dir_offset, self.texture_dir_count, 1)
|
||||
}
|
||||
|
||||
pub fn body_part_indexes(&self) -> impl Iterator<Item = usize> {
|
||||
index_range(self.body_part_offset, self.body_part_count, 1)
|
||||
}
|
||||
|
||||
pub fn attachment_indexes(&self) -> impl Iterator<Item = usize> {
|
||||
index_range(self.attachment_offset, self.attachment_count, 1)
|
||||
}
|
||||
|
||||
pub fn local_node_indexes(&self) -> impl Iterator<Item = usize> {
|
||||
index_range(self.local_node_index, self.local_node_count, 1)
|
||||
}
|
||||
|
||||
pub fn local_node_name_indexes(&self) -> impl Iterator<Item = usize> {
|
||||
index_range(self.local_node_name_index, self.local_node_count, 1)
|
||||
}
|
||||
|
||||
pub fn flex_descriptor_indexes(&self) -> impl Iterator<Item = usize> {
|
||||
index_range(self.flex_desc_index, self.flex_desc_count, 1)
|
||||
}
|
||||
|
||||
pub fn flex_controller_indexes(&self) -> impl Iterator<Item = usize> {
|
||||
index_range(self.flex_controller_index, self.flex_controller_count, 1)
|
||||
}
|
||||
|
||||
pub fn flex_rule_indexes(&self) -> impl Iterator<Item = usize> {
|
||||
index_range(self.flex_rules_index, self.flex_rules_count, 1)
|
||||
}
|
||||
|
||||
pub fn ik_chain_indexes(&self) -> impl Iterator<Item = usize> {
|
||||
index_range(self.ik_chain_index, self.ik_chain_count, 1)
|
||||
}
|
||||
|
||||
pub fn mouth_indexes(&self) -> impl Iterator<Item = usize> {
|
||||
index_range(self.mouths_index, self.mouths_count, 1)
|
||||
}
|
||||
|
||||
pub fn local_pose_param_indexes(&self) -> impl Iterator<Item = usize> {
|
||||
index_range(self.local_pose_param_index, self.local_pose_param_count, 1)
|
||||
}
|
||||
|
||||
pub fn key_value_indexes(&self) -> impl Iterator<Item = usize> {
|
||||
index_range(self.key_value_index, self.key_value_count, 1)
|
||||
}
|
||||
|
||||
pub fn ik_lock_indexes(&self) -> impl Iterator<Item = usize> {
|
||||
index_range(self.ik_lock_index, self.ik_lock_count, 1)
|
||||
}
|
||||
|
||||
pub fn include_model_indexes(&self) -> impl Iterator<Item = usize> {
|
||||
index_range(self.include_model_index, self.include_model_count, 1)
|
||||
}
|
||||
|
||||
pub fn animation_block_indexes(&self) -> impl Iterator<Item = usize> {
|
||||
index_range(self.anim_blocks_index, self.anim_blocks_count, 1)
|
||||
}
|
||||
|
||||
pub fn animation_block_name_indexes(&self) -> impl Iterator<Item = usize> {
|
||||
index_range(self.anim_blocks_name_index, self.anim_blocks_count, 1)
|
||||
}
|
||||
|
||||
pub fn flex_controller_ui_indexes(&self) -> impl Iterator<Item = usize> {
|
||||
index_range(
|
||||
self.flex_controller_ui_index,
|
||||
self.flex_controller_ui_count,
|
||||
1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
39
src/data/header2.rs
Normal file
39
src/data/header2.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
use std::ops::Range;
|
||||
|
||||
pub struct StudioHHeader2 {
|
||||
source_bone_transform_count: i32,
|
||||
source_bone_transform_index: i32,
|
||||
|
||||
pub illumination_position_attachment_index: i32,
|
||||
|
||||
fl_max_exe_deflection: f32,
|
||||
|
||||
pub linear_bone_index: i32,
|
||||
|
||||
pub sz_name_index: i32,
|
||||
|
||||
bone_flex_driver_count: i32,
|
||||
bone_flex_driver_index: i32,
|
||||
|
||||
#[allow(dead_code)]
|
||||
reserved: [i32; 56],
|
||||
}
|
||||
|
||||
impl StudioHHeader2 {
|
||||
pub fn source_bone_transforms(&self) -> Range<i32> {
|
||||
self.source_bone_transform_index
|
||||
..(self.source_bone_transform_index + self.source_bone_transform_count)
|
||||
}
|
||||
|
||||
pub fn bone_flex_drivers(&self) -> Range<i32> {
|
||||
self.bone_flex_driver_index..(self.bone_flex_driver_index + self.bone_flex_driver_count)
|
||||
}
|
||||
|
||||
pub fn max_eye_deflection(&self) -> f32 {
|
||||
if self.fl_max_exe_deflection == 0.0 {
|
||||
(30.0f32).cos()
|
||||
} else {
|
||||
self.fl_max_exe_deflection
|
||||
}
|
||||
}
|
||||
}
|
||||
114
src/data/mod.rs
Normal file
114
src/data/mod.rs
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
mod bone;
|
||||
mod header;
|
||||
mod header2;
|
||||
|
||||
pub use bone::*;
|
||||
pub use header::*;
|
||||
pub use header2::*;
|
||||
|
||||
use crate::error::StringError;
|
||||
use arrayvec::ArrayString;
|
||||
use binrw::{BinRead, BinResult, ReadOptions};
|
||||
use std::fmt;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug, Clone, Copy, BinRead)]
|
||||
pub struct Vector {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub z: f32,
|
||||
}
|
||||
|
||||
impl From<Vector> for [f32; 3] {
|
||||
fn from(vector: Vector) -> Self {
|
||||
[vector.x, vector.y, vector.z]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[f32; 3]> for Vector {
|
||||
fn from(vector: [f32; 3]) -> Self {
|
||||
Vector {
|
||||
x: vector[0],
|
||||
y: vector[1],
|
||||
z: vector[2],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Vector> for [f32; 3] {
|
||||
fn from(vector: &Vector) -> Self {
|
||||
[vector.x, vector.y, vector.z]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, BinRead)]
|
||||
pub struct Quaternion {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub z: f32,
|
||||
pub w: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, BinRead)]
|
||||
pub struct RadianEuler {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub z: f32,
|
||||
}
|
||||
|
||||
/// Fixed length, null-terminated string
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FixedString<const LEN: usize>(ArrayString<LEN>);
|
||||
|
||||
impl<const N: usize> AsRef<str> for FixedString<N> {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> FixedString<N> {
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const LEN: usize> Display for FixedString<LEN> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const LEN: usize> BinRead for FixedString<LEN> {
|
||||
type Args = ();
|
||||
|
||||
fn read_options<R: binrw::io::Read + binrw::io::Seek>(
|
||||
reader: &mut R,
|
||||
options: &ReadOptions,
|
||||
args: Self::Args,
|
||||
) -> BinResult<Self> {
|
||||
use std::str;
|
||||
|
||||
let name_buf = <[u8; LEN]>::read_options(reader, options, args)?;
|
||||
|
||||
let zero_pos =
|
||||
name_buf
|
||||
.iter()
|
||||
.position(|c| *c == 0)
|
||||
.ok_or_else(|| binrw::Error::Custom {
|
||||
pos: reader.stream_position().unwrap(),
|
||||
err: Box::new(StringError::NotNullTerminated),
|
||||
})?;
|
||||
let name = &name_buf[..zero_pos];
|
||||
Ok(FixedString(
|
||||
ArrayString::from(
|
||||
str::from_utf8(name)
|
||||
.map_err(StringError::NonUTF8)
|
||||
.map_err(|e| binrw::Error::Custom {
|
||||
pos: reader.stream_position().unwrap(),
|
||||
err: Box::new(e),
|
||||
})?,
|
||||
)
|
||||
.unwrap(),
|
||||
))
|
||||
}
|
||||
}
|
||||
41
src/error.rs
Normal file
41
src/error.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
use thiserror::Error;
|
||||
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum MdlError {
|
||||
#[error("io error while reading data: {0}")]
|
||||
IO(#[from] std::io::Error),
|
||||
#[error(transparent)]
|
||||
String(#[from] StringError),
|
||||
#[error("Malformed field found while parsing: {0:#}")]
|
||||
MalformedData(binrw::Error),
|
||||
#[error("referenced data is out of bounds")]
|
||||
OutOfBounds,
|
||||
}
|
||||
|
||||
impl From<binrw::Error> for MdlError {
|
||||
fn from(e: binrw::Error) -> Self {
|
||||
use binrw::Error;
|
||||
|
||||
// only a few error types should be generated by our code
|
||||
match e {
|
||||
Error::Io(e) => MdlError::IO(e),
|
||||
Error::Custom { err, .. } => {
|
||||
if err.is::<StringError>() {
|
||||
MdlError::String(*err.downcast::<StringError>().unwrap())
|
||||
} else {
|
||||
panic!("unexpected custom error")
|
||||
}
|
||||
}
|
||||
e => MdlError::MalformedData(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum StringError {
|
||||
#[error(transparent)]
|
||||
NonUTF8(#[from] std::str::Utf8Error),
|
||||
#[error("String is not null-terminated")]
|
||||
NotNullTerminated,
|
||||
}
|
||||
32
src/handle/mod.rs
Normal file
32
src/handle/mod.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
use crate::Mdl;
|
||||
use std::ops::Deref;
|
||||
|
||||
/// A handle represents a data 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
|
||||
/// reference parts from other structures in the mdl file
|
||||
#[derive(Debug)]
|
||||
pub struct Handle<'a, T> {
|
||||
mdl: &'a Mdl,
|
||||
data: &'a T,
|
||||
}
|
||||
|
||||
impl<T> Clone for Handle<'_, T> {
|
||||
fn clone(&self) -> Self {
|
||||
Handle { ..*self }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> AsRef<T> for Handle<'a, T> {
|
||||
fn as_ref(&self) -> &'a T {
|
||||
self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for Handle<'_, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.data
|
||||
}
|
||||
}
|
||||
45
src/lib.rs
45
src/lib.rs
|
|
@ -1,8 +1,41 @@
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = 2 + 2;
|
||||
assert_eq!(result, 4);
|
||||
mod data;
|
||||
mod error;
|
||||
mod handle;
|
||||
|
||||
use binrw::{BinRead, BinReaderExt};
|
||||
pub use data::*;
|
||||
pub use error::*;
|
||||
pub use handle::Handle;
|
||||
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>(
|
||||
indexes: I,
|
||||
data: &'a [u8],
|
||||
) -> impl Iterator<Item = Result<T, MdlError>> + 'a
|
||||
where
|
||||
T::Args: Default,
|
||||
{
|
||||
indexes
|
||||
.map(|index| data.get(index..).ok_or(MdlError::OutOfBounds))
|
||||
.map(|data| {
|
||||
data.and_then(|data| {
|
||||
let mut cursor = Cursor::new(data);
|
||||
cursor.read_le().map_err(MdlError::from)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue