mirror of
https://codeberg.org/icewind/vbsp.git
synced 2026-06-03 10:44:07 +02:00
game lump/static props
This commit is contained in:
parent
d628f898d0
commit
15c1c4e6eb
9 changed files with 423 additions and 50 deletions
|
|
@ -1,13 +1,20 @@
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
fn main() -> Result<(), vbsp::BspError> {
|
fn main() -> Result<(), vbsp::BspError> {
|
||||||
let mut args = std::env::args();
|
let mut args = std::env::args();
|
||||||
let _ = args.next();
|
let _ = args.next();
|
||||||
let data = std::fs::read(args.next().expect("No demo file provided"))?;
|
let data = std::fs::read(args.next().expect("No demo file provided"))?;
|
||||||
let props = vbsp::Bsp::read(&data)?.entities;
|
let bsp = vbsp::Bsp::read(&data)?;
|
||||||
for prop in props.iter() {
|
// for prop in bsp.entities.iter() {
|
||||||
match prop.parse() {
|
// match prop.parse() {
|
||||||
Ok(prop) => println!("{:#?}", prop),
|
// Ok(prop) => println!("{:#?}", prop),
|
||||||
Err(e) => println!("Failed parsing {:#?}: {:#}", prop, e),
|
// Err(e) => println!("Failed parsing {:#?}: {:#}", prop, e),
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
for prop in bsp.static_props() {
|
||||||
|
dbg!(prop.deref());
|
||||||
|
dbg!(prop.model());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use binrw::io::Cursor;
|
use binrw::io::Cursor;
|
||||||
use binrw::BinReaderExt;
|
use binrw::BinReaderExt;
|
||||||
use lzma_rs::decompress::{Options, UnpackedSize};
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
pub struct BspFile<'a> {
|
pub struct BspFile<'a> {
|
||||||
|
|
@ -57,32 +56,7 @@ impl<'a> BspFile<'a> {
|
||||||
Ok(match lump.ident {
|
Ok(match lump.ident {
|
||||||
0 => Cow::Borrowed(raw_data),
|
0 => Cow::Borrowed(raw_data),
|
||||||
_ => {
|
_ => {
|
||||||
let mut data: Vec<u8> = Vec::with_capacity(lump.ident as usize);
|
let data = lzma_decompress_with_header(raw_data, lump.ident as usize)?;
|
||||||
let mut cursor = Cursor::new(raw_data);
|
|
||||||
if b"LZMA" != &<[u8; 4]>::read(&mut cursor)? {
|
|
||||||
return Err(BspError::LumpDecompressError(
|
|
||||||
lzma_rs::error::Error::LzmaError("Invalid lzma header".into()),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let actual_size: u32 = cursor.read_le()?;
|
|
||||||
let _lzma_size: u32 = cursor.read_le()?;
|
|
||||||
lzma_rs::lzma_decompress_with_options(
|
|
||||||
&mut cursor,
|
|
||||||
&mut data,
|
|
||||||
&Options {
|
|
||||||
unpacked_size: UnpackedSize::UseProvided(Some(actual_size as u64)),
|
|
||||||
allow_incomplete: false,
|
|
||||||
memlimit: None,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.map_err(BspError::LumpDecompressError)?;
|
|
||||||
if data.len() != lump.ident as usize {
|
|
||||||
return Err(BspError::UnexpectedUncompressedLumpSize {
|
|
||||||
got: data.len() as u32,
|
|
||||||
expected: lump.ident,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Cow::Owned(data)
|
Cow::Owned(data)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
287
src/data/game.rs
Normal file
287
src/data/game.rs
Normal file
|
|
@ -0,0 +1,287 @@
|
||||||
|
use crate::error::UnsupportedLumpVersion;
|
||||||
|
use crate::{lzma_decompress_with_header, BspError, FixedString, Vector};
|
||||||
|
use binrw::{BinRead, BinReaderExt, BinResult, ReadOptions};
|
||||||
|
use bitflags::bitflags;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::io::{Cursor, Read, Seek};
|
||||||
|
use std::mem::size_of;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, BinRead)]
|
||||||
|
pub struct GameLumpHeader {
|
||||||
|
pub count: i32,
|
||||||
|
#[br(count = count)]
|
||||||
|
pub lumps: Vec<GameLump>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GameLumpHeader {
|
||||||
|
pub fn find<T: GameLumpType<Args = (u16,)>>(&self, data: &[u8]) -> Option<Result<T, BspError>> {
|
||||||
|
let (i, lump) = self
|
||||||
|
.lumps
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_, lump)| lump.id == T::ID)?;
|
||||||
|
|
||||||
|
let data = match self.get_game_lump_data(i, lump, data) {
|
||||||
|
Ok(data) => data,
|
||||||
|
Err(e) => return Some(Err(e)),
|
||||||
|
};
|
||||||
|
let mut reader = Cursor::new(data);
|
||||||
|
Some(reader.read_le_args((lump.version,)).map_err(BspError::from))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_game_lump_data<'a>(
|
||||||
|
&self,
|
||||||
|
i: usize,
|
||||||
|
lump: &GameLump,
|
||||||
|
data: &'a [u8],
|
||||||
|
) -> Result<Cow<'a, [u8]>, BspError> {
|
||||||
|
if lump.flags.contains(GameLumpFlags::COMPRESSED) {
|
||||||
|
let next_lump = self
|
||||||
|
.lumps
|
||||||
|
.get(i + 1)
|
||||||
|
.ok_or_else(|| BspError::GameLumpOutOfBounds(lump.clone()))?;
|
||||||
|
let compressed_size = next_lump.offset - lump.offset;
|
||||||
|
let raw_data = data
|
||||||
|
.get(lump.offset as usize..(lump.offset + compressed_size) as usize)
|
||||||
|
.ok_or_else(|| BspError::GameLumpOutOfBounds(lump.clone()))?;
|
||||||
|
let mut output = lzma_decompress_with_header(raw_data, lump.length as usize)?;
|
||||||
|
// some compressed lumps are a bit to small for some reason
|
||||||
|
output.extend_from_slice(&[0; 8]);
|
||||||
|
Ok(Cow::Owned(output))
|
||||||
|
} else {
|
||||||
|
let data = data
|
||||||
|
.get(lump.offset as usize..(lump.offset + lump.length) as usize)
|
||||||
|
.ok_or_else(|| BspError::GameLumpOutOfBounds(lump.clone()))?;
|
||||||
|
Ok(Cow::Borrowed(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, BinRead)]
|
||||||
|
pub struct GameLump {
|
||||||
|
pub id: i32,
|
||||||
|
pub flags: GameLumpFlags,
|
||||||
|
pub version: u16,
|
||||||
|
pub offset: i32,
|
||||||
|
pub length: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct GameLumpFlags: u16 {
|
||||||
|
const COMPRESSED = 0b0000_0000_0000_0000_0001;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait GameLumpType: BinRead {
|
||||||
|
const ID: i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, BinRead)]
|
||||||
|
#[br(import(version: u16))]
|
||||||
|
pub struct PropStaticGameLump {
|
||||||
|
pub dict: StaticPropDictLump,
|
||||||
|
pub leaf: StaticPropLeafLump,
|
||||||
|
#[br(args(version))]
|
||||||
|
pub props: StaticPropLumps,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GameLumpType for PropStaticGameLump {
|
||||||
|
const ID: i32 = i32::from_be_bytes(*b"sprp");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, BinRead)]
|
||||||
|
pub struct StaticPropDictLump {
|
||||||
|
pub entries: i32,
|
||||||
|
#[br(count = entries)]
|
||||||
|
pub name: Vec<FixedString<128>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, BinRead)]
|
||||||
|
pub struct StaticPropLeafLump {
|
||||||
|
pub entries: i32,
|
||||||
|
#[br(count = entries)]
|
||||||
|
pub leaves: Vec<u16>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, BinRead)]
|
||||||
|
#[br(import(version: u16))]
|
||||||
|
pub struct StaticPropLumps {
|
||||||
|
pub entries: i32,
|
||||||
|
#[br(args_raw = binrw::VecArgs{count: entries as usize, inner: (version,)})]
|
||||||
|
pub props: Vec<StaticPropLump>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct StaticPropLump {
|
||||||
|
pub origin: Vector,
|
||||||
|
pub angles: [f32; 3],
|
||||||
|
pub prop_type: u16,
|
||||||
|
pub first_leaf: u16,
|
||||||
|
pub leaf_count: u16,
|
||||||
|
pub solid: u8,
|
||||||
|
pub skin: i32,
|
||||||
|
pub fade_min_distance: f32,
|
||||||
|
pub fade_max_distance: f32,
|
||||||
|
pub lighting_origin: Vector,
|
||||||
|
pub forced_fade_scale: f32,
|
||||||
|
pub min_dx_level: u16,
|
||||||
|
pub max_dx_level: u16,
|
||||||
|
pub flags: StaticPropLumpFlags,
|
||||||
|
pub lightmap_resolution: [u16; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BinRead for StaticPropLump {
|
||||||
|
type Args = (u16,);
|
||||||
|
|
||||||
|
fn read_options<R: Read + Seek>(
|
||||||
|
reader: &mut R,
|
||||||
|
options: &ReadOptions,
|
||||||
|
args: Self::Args,
|
||||||
|
) -> BinResult<Self> {
|
||||||
|
match args.0 {
|
||||||
|
6 => StaticPropLumpV6::read_options(reader, options, ()).map(StaticPropLump::from),
|
||||||
|
7 | 10 => {
|
||||||
|
StaticPropLumpV10::read_options(reader, options, ()).map(StaticPropLump::from)
|
||||||
|
}
|
||||||
|
version => Err(binrw::Error::Custom {
|
||||||
|
err: Box::new(UnsupportedLumpVersion {
|
||||||
|
lump_type: "static props",
|
||||||
|
version,
|
||||||
|
}),
|
||||||
|
pos: reader.stream_position().unwrap(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct StaticPropLumpFlags: u32 {
|
||||||
|
const FLAG_FADES = 0x1;
|
||||||
|
const USE_LIGHTING_ORIGIN = 0x2;
|
||||||
|
const NO_DRAW = 0x4;
|
||||||
|
const IGNORE_NORMALS = 0x8;
|
||||||
|
const NO_SHADOW = 0x10;
|
||||||
|
const SCREEN_SPACE_FADE = 0x20;
|
||||||
|
const NO_PER_VERTEX_LIGHTING = 0x40;
|
||||||
|
const NO_SELF_SHADOWING = 0x80;
|
||||||
|
const NO_PER_TEXEL_LIGHTING = 0x100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<StaticPropLumpFlagsV6> for StaticPropLumpFlags {
|
||||||
|
fn from(v6: StaticPropLumpFlagsV6) -> Self {
|
||||||
|
StaticPropLumpFlags::from_bits_truncate(v6.bits().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
struct StaticPropLumpV6 {
|
||||||
|
pub origin: Vector,
|
||||||
|
pub angles: [f32; 3],
|
||||||
|
pub prop_type: u16,
|
||||||
|
pub first_leaf: u16,
|
||||||
|
pub leaf_count: u16,
|
||||||
|
pub solid: u8,
|
||||||
|
pub flags: StaticPropLumpFlagsV6,
|
||||||
|
pub skin: i32,
|
||||||
|
pub fade_min_distance: f32,
|
||||||
|
pub fade_max_distance: f32,
|
||||||
|
pub lighting_origin: Vector,
|
||||||
|
pub forced_fade_scale: f32,
|
||||||
|
pub min_dx_level: u16,
|
||||||
|
pub max_dx_level: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_static_prop_lump_v6_bytes() {
|
||||||
|
super::test_read_bytes::<StaticPropLumpV6>();
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
#[derive(BinRead)]
|
||||||
|
struct StaticPropLumpFlagsV6: u8 {
|
||||||
|
const FLAG_FADES = 0x1;
|
||||||
|
const USE_LIGHTING_ORIGIN = 0x2;
|
||||||
|
const NO_DRAW = 0x4;
|
||||||
|
const IGNORE_NORMALS = 0x8;
|
||||||
|
const NO_SHADOW = 0x10;
|
||||||
|
const SCREEN_SPACE_FADE = 0x20;
|
||||||
|
const NO_PER_VERTEX_LIGHTING = 0x40;
|
||||||
|
const NO_SELF_SHADOWING = 0x80;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// same as StaticPropLump but with derived BinRead
|
||||||
|
#[derive(BinRead)]
|
||||||
|
struct StaticPropLumpV10 {
|
||||||
|
pub origin: Vector,
|
||||||
|
pub angles: [f32; 3],
|
||||||
|
pub prop_type: u16,
|
||||||
|
pub first_leaf: u16,
|
||||||
|
pub leaf_count: u16,
|
||||||
|
// pad, not align
|
||||||
|
#[br(pad_after = 1)]
|
||||||
|
pub solid: u8,
|
||||||
|
pub skin: i32,
|
||||||
|
pub fade_min_distance: f32,
|
||||||
|
pub fade_max_distance: f32,
|
||||||
|
pub lighting_origin: Vector,
|
||||||
|
pub forced_fade_scale: f32,
|
||||||
|
pub min_dx_level: u16,
|
||||||
|
pub max_dx_level: u16,
|
||||||
|
pub flags: StaticPropLumpFlags,
|
||||||
|
pub lightmap_resolution: [u16; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_static_prop_lump_bytes() {
|
||||||
|
super::test_read_bytes::<StaticPropLumpV10>();
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assertions::const_assert_eq!(size_of::<StaticPropLumpV10>(), size_of::<StaticPropLump>());
|
||||||
|
|
||||||
|
impl From<StaticPropLumpV6> for StaticPropLump {
|
||||||
|
fn from(from: StaticPropLumpV6) -> Self {
|
||||||
|
StaticPropLump {
|
||||||
|
origin: from.origin,
|
||||||
|
angles: from.angles,
|
||||||
|
prop_type: from.prop_type,
|
||||||
|
first_leaf: from.first_leaf,
|
||||||
|
leaf_count: from.leaf_count,
|
||||||
|
solid: from.solid,
|
||||||
|
skin: from.skin,
|
||||||
|
fade_min_distance: from.fade_min_distance,
|
||||||
|
fade_max_distance: from.fade_max_distance,
|
||||||
|
lighting_origin: from.lighting_origin,
|
||||||
|
forced_fade_scale: from.forced_fade_scale,
|
||||||
|
min_dx_level: from.min_dx_level,
|
||||||
|
max_dx_level: from.max_dx_level,
|
||||||
|
flags: from.flags.into(),
|
||||||
|
lightmap_resolution: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<StaticPropLumpV10> for StaticPropLump {
|
||||||
|
fn from(from: StaticPropLumpV10) -> Self {
|
||||||
|
StaticPropLump {
|
||||||
|
origin: from.origin,
|
||||||
|
angles: from.angles,
|
||||||
|
prop_type: from.prop_type,
|
||||||
|
first_leaf: from.first_leaf,
|
||||||
|
leaf_count: from.leaf_count,
|
||||||
|
solid: from.solid,
|
||||||
|
skin: from.skin,
|
||||||
|
fade_min_distance: from.fade_min_distance,
|
||||||
|
fade_max_distance: from.fade_max_distance,
|
||||||
|
lighting_origin: from.lighting_origin,
|
||||||
|
forced_fade_scale: from.forced_fade_scale,
|
||||||
|
min_dx_level: from.min_dx_level,
|
||||||
|
max_dx_level: from.max_dx_level,
|
||||||
|
flags: from.flags,
|
||||||
|
lightmap_resolution: from.lightmap_resolution,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
mod displacement;
|
mod displacement;
|
||||||
mod entity;
|
mod entity;
|
||||||
|
mod game;
|
||||||
mod vector;
|
mod vector;
|
||||||
|
|
||||||
pub use self::displacement::*;
|
pub use self::displacement::*;
|
||||||
pub use self::entity::*;
|
pub use self::entity::*;
|
||||||
|
pub use self::game::*;
|
||||||
pub use self::vector::*;
|
pub use self::vector::*;
|
||||||
use crate::bspfile::LumpType;
|
use crate::bspfile::LumpType;
|
||||||
use crate::StringError;
|
use crate::StringError;
|
||||||
|
|
@ -101,6 +103,18 @@ bitflags! {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FixedString<const LEN: usize>(ArrayString<LEN>);
|
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> {
|
impl<const LEN: usize> Display for FixedString<LEN> {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
Display::fmt(&self.0, f)
|
Display::fmt(&self.0, f)
|
||||||
|
|
|
||||||
26
src/error.rs
26
src/error.rs
|
|
@ -2,12 +2,17 @@ use crate::data::*;
|
||||||
use std::num::{ParseFloatError, ParseIntError};
|
use std::num::{ParseFloatError, ParseIntError};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum BspError {
|
pub enum BspError {
|
||||||
#[error("unexpected magic numbers or version")]
|
#[error("unexpected magic numbers or version")]
|
||||||
UnexpectedHeader(Header),
|
UnexpectedHeader(Header),
|
||||||
#[error("bsp lump is out of bounds of the bsp file")]
|
#[error("bsp lump is out of bounds of the bsp file")]
|
||||||
LumpOutOfBounds(LumpEntry),
|
LumpOutOfBounds(LumpEntry),
|
||||||
|
#[error("bsp game lump is out of bounds of the bsp file")]
|
||||||
|
GameLumpOutOfBounds(GameLump),
|
||||||
|
#[error("compressed game lump is malformed")]
|
||||||
|
MalformedCompressedGameLump,
|
||||||
#[error("Invalid lump size, lump size {lump_size} is not a multiple of the element size {element_size}")]
|
#[error("Invalid lump size, lump size {lump_size} is not a multiple of the element size {element_size}")]
|
||||||
InvalidLumpSize {
|
InvalidLumpSize {
|
||||||
element_size: usize,
|
element_size: usize,
|
||||||
|
|
@ -15,6 +20,8 @@ pub enum BspError {
|
||||||
},
|
},
|
||||||
#[error("unexpected length of uncompressed lump, got {got} but expected {expected}")]
|
#[error("unexpected length of uncompressed lump, got {got} but expected {expected}")]
|
||||||
UnexpectedUncompressedLumpSize { got: u32, expected: u32 },
|
UnexpectedUncompressedLumpSize { got: u32, expected: u32 },
|
||||||
|
#[error("unexpected length of compressed lump, got {got} but expected {expected}")]
|
||||||
|
UnexpectedCompressedLumpSize { got: u32, expected: u32 },
|
||||||
#[error("error while decompressing lump")]
|
#[error("error while decompressing lump")]
|
||||||
LumpDecompressError(lzma_rs::error::Error),
|
LumpDecompressError(lzma_rs::error::Error),
|
||||||
#[error("io error while reading data: {0}")]
|
#[error("io error while reading data: {0}")]
|
||||||
|
|
@ -25,6 +32,8 @@ pub enum BspError {
|
||||||
MalformedData(binrw::Error),
|
MalformedData(binrw::Error),
|
||||||
#[error("bsp file is well-formed but contains invalid data")]
|
#[error("bsp file is well-formed but contains invalid data")]
|
||||||
Validation(#[from] ValidationError),
|
Validation(#[from] ValidationError),
|
||||||
|
#[error(transparent)]
|
||||||
|
LumpVersion(UnsupportedLumpVersion),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<binrw::Error> for BspError {
|
impl From<binrw::Error> for BspError {
|
||||||
|
|
@ -35,8 +44,10 @@ impl From<binrw::Error> for BspError {
|
||||||
match e {
|
match e {
|
||||||
Error::Io(e) => BspError::IO(e),
|
Error::Io(e) => BspError::IO(e),
|
||||||
Error::Custom { err, .. } => {
|
Error::Custom { err, .. } => {
|
||||||
if let Ok(string_error) = err.downcast::<StringError>() {
|
if err.is::<StringError>() {
|
||||||
BspError::String(*string_error)
|
BspError::String(*err.downcast::<StringError>().unwrap())
|
||||||
|
} else if err.is::<UnsupportedLumpVersion>() {
|
||||||
|
BspError::LumpVersion(*err.downcast::<UnsupportedLumpVersion>().unwrap())
|
||||||
} else {
|
} else {
|
||||||
panic!("unexpected custom error")
|
panic!("unexpected custom error")
|
||||||
}
|
}
|
||||||
|
|
@ -65,10 +76,17 @@ pub enum StringError {
|
||||||
NotNullTerminated,
|
NotNullTerminated,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
#[error("Unsupported lump version {version} for {lump_type} lump")]
|
||||||
|
pub struct UnsupportedLumpVersion {
|
||||||
|
pub lump_type: &'static str,
|
||||||
|
pub version: u16,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum ValidationError {
|
pub enum ValidationError {
|
||||||
#[error(
|
#[error(
|
||||||
"A {source_} indexes into {target} but the index {index} is out of range of the size {size}"
|
"A {source_} indexes into {target} but the index {index} is out of range of the size {size}"
|
||||||
)]
|
)]
|
||||||
ReferenceOutOfRange {
|
ReferenceOutOfRange {
|
||||||
source_: &'static str,
|
source_: &'static str,
|
||||||
|
|
@ -80,6 +98,8 @@ pub enum ValidationError {
|
||||||
NoRootNode,
|
NoRootNode,
|
||||||
#[error("displacement face with {0} edges")]
|
#[error("displacement face with {0} edges")]
|
||||||
NonSquareDisplacement(i16),
|
NonSquareDisplacement(i16),
|
||||||
|
#[error("No static prop lump found")]
|
||||||
|
NoStaticPropLump,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
|
|
||||||
8
src/handle/game.rs
Normal file
8
src/handle/game.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
use super::Handle;
|
||||||
|
use crate::data::*;
|
||||||
|
|
||||||
|
impl Handle<'_, StaticPropLump> {
|
||||||
|
pub fn model(&self) -> &str {
|
||||||
|
self.bsp.static_props.dict.name[self.prop_type as usize].as_str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
mod displacement;
|
mod displacement;
|
||||||
mod face;
|
mod face;
|
||||||
|
mod game;
|
||||||
|
|
||||||
use crate::data::*;
|
use crate::data::*;
|
||||||
use crate::Bsp;
|
use crate::Bsp;
|
||||||
|
|
|
||||||
67
src/lib.rs
67
src/lib.rs
|
|
@ -11,9 +11,10 @@ pub use crate::data::*;
|
||||||
use crate::error::ValidationError;
|
use crate::error::ValidationError;
|
||||||
pub use crate::handle::Handle;
|
pub use crate::handle::Handle;
|
||||||
use binrw::io::Cursor;
|
use binrw::io::Cursor;
|
||||||
use binrw::BinRead;
|
use binrw::{BinRead, BinReaderExt};
|
||||||
use bspfile::BspFile;
|
use bspfile::BspFile;
|
||||||
pub use error::{BspError, StringError};
|
pub use error::{BspError, StringError};
|
||||||
|
use lzma_rs::decompress::{Options, UnpackedSize};
|
||||||
use reader::LumpReader;
|
use reader::LumpReader;
|
||||||
use std::{io::Read, ops::Deref};
|
use std::{io::Read, ops::Deref};
|
||||||
|
|
||||||
|
|
@ -175,6 +176,7 @@ pub struct Bsp {
|
||||||
pub displacements: Vec<DisplacementInfo>,
|
pub displacements: Vec<DisplacementInfo>,
|
||||||
pub displacement_vertices: Vec<DisplacementVertex>,
|
pub displacement_vertices: Vec<DisplacementVertex>,
|
||||||
pub displacement_triangles: Vec<DisplacementTriangle>,
|
pub displacement_triangles: Vec<DisplacementTriangle>,
|
||||||
|
pub static_props: PropStaticGameLump,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bsp {
|
impl Bsp {
|
||||||
|
|
@ -238,6 +240,11 @@ impl Bsp {
|
||||||
let displacement_triangles = bsp_file
|
let displacement_triangles = bsp_file
|
||||||
.lump_reader(LumpType::DisplacementTris)?
|
.lump_reader(LumpType::DisplacementTris)?
|
||||||
.read_vec(|r| r.read())?;
|
.read_vec(|r| r.read())?;
|
||||||
|
let game_lumps: GameLumpHeader = bsp_file.lump_reader(LumpType::GameLump)?.read()?;
|
||||||
|
|
||||||
|
let static_props = game_lumps
|
||||||
|
.find(data)
|
||||||
|
.ok_or(ValidationError::NoStaticPropLump)??;
|
||||||
|
|
||||||
let bsp = Bsp {
|
let bsp = Bsp {
|
||||||
header: bsp_file.header().clone(),
|
header: bsp_file.header().clone(),
|
||||||
|
|
@ -261,6 +268,7 @@ impl Bsp {
|
||||||
displacements,
|
displacements,
|
||||||
displacement_vertices,
|
displacement_vertices,
|
||||||
displacement_triangles,
|
displacement_triangles,
|
||||||
|
static_props,
|
||||||
};
|
};
|
||||||
bsp.validate()?;
|
bsp.validate()?;
|
||||||
Ok(bsp)
|
Ok(bsp)
|
||||||
|
|
@ -328,6 +336,14 @@ impl Bsp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn static_props(&self) -> impl Iterator<Item = Handle<'_, StaticPropLump>> {
|
||||||
|
self.static_props
|
||||||
|
.props
|
||||||
|
.props
|
||||||
|
.iter()
|
||||||
|
.map(|lump| Handle::new(self, lump))
|
||||||
|
}
|
||||||
|
|
||||||
/// Get all faces stored in the bsp
|
/// Get all faces stored in the bsp
|
||||||
pub fn original_faces(&self) -> impl Iterator<Item = Handle<Face>> {
|
pub fn original_faces(&self) -> impl Iterator<Item = Handle<Face>> {
|
||||||
self.faces.iter().map(move |face| Handle::new(self, face))
|
self.faces.iter().map(move |face| Handle::new(self, face))
|
||||||
|
|
@ -429,6 +445,12 @@ impl Bsp {
|
||||||
"node",
|
"node",
|
||||||
"leaf",
|
"leaf",
|
||||||
)?;
|
)?;
|
||||||
|
self.validate_indexes(
|
||||||
|
self.static_props().map(|prop| prop.prop_type),
|
||||||
|
&self.static_props.dict.name,
|
||||||
|
"static props",
|
||||||
|
"static prop models",
|
||||||
|
)?;
|
||||||
|
|
||||||
if self.nodes.is_empty() {
|
if self.nodes.is_empty() {
|
||||||
return Err(ValidationError::NoRootNode.into());
|
return Err(ValidationError::NoRootNode.into());
|
||||||
|
|
@ -444,12 +466,12 @@ impl Bsp {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_indexes<
|
fn validate_indexes<
|
||||||
'a,
|
'b,
|
||||||
Index: TryInto<usize> + Into<i64> + Copy + Ord + Default,
|
Index: TryInto<usize> + Into<i64> + Copy + Ord + Default,
|
||||||
Indexes: Iterator<Item = Index>,
|
Indexes: Iterator<Item = Index>,
|
||||||
T: 'a,
|
T: 'b,
|
||||||
>(
|
>(
|
||||||
&'a self,
|
&'b self,
|
||||||
indexes: Indexes,
|
indexes: Indexes,
|
||||||
list: &[T],
|
list: &[T],
|
||||||
source: &'static str,
|
source: &'static str,
|
||||||
|
|
@ -482,3 +504,40 @@ mod tests {
|
||||||
Bsp::read(&data).unwrap();
|
Bsp::read(&data).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// LZMA decompression with the header used by source
|
||||||
|
fn lzma_decompress_with_header(data: &[u8], expected_length: usize) -> Result<Vec<u8>, BspError> {
|
||||||
|
// extra 8 byte because game lumps need some padding for reasons
|
||||||
|
let mut output: Vec<u8> = Vec::with_capacity(expected_length + 8);
|
||||||
|
let mut cursor = Cursor::new(data);
|
||||||
|
if b"LZMA" != &<[u8; 4]>::read(&mut cursor)? {
|
||||||
|
return Err(BspError::LumpDecompressError(
|
||||||
|
lzma_rs::error::Error::LzmaError("Invalid lzma header".into()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let actual_size: u32 = cursor.read_le()?;
|
||||||
|
let lzma_size: u32 = cursor.read_le()?;
|
||||||
|
if data.len() < lzma_size as usize + 12 {
|
||||||
|
return Err(BspError::UnexpectedCompressedLumpSize {
|
||||||
|
got: data.len() as u32,
|
||||||
|
expected: lzma_size,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
lzma_rs::lzma_decompress_with_options(
|
||||||
|
&mut cursor,
|
||||||
|
&mut output,
|
||||||
|
&Options {
|
||||||
|
unpacked_size: UnpackedSize::UseProvided(Some(actual_size as u64)),
|
||||||
|
allow_incomplete: false,
|
||||||
|
memlimit: None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.map_err(BspError::LumpDecompressError)?;
|
||||||
|
if output.len() != expected_length {
|
||||||
|
return Err(BspError::UnexpectedUncompressedLumpSize {
|
||||||
|
got: output.len() as u32,
|
||||||
|
expected: expected_length as u32,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use binrw::BinReaderExt;
|
use binrw::BinReaderExt;
|
||||||
use std::any::type_name;
|
// use std::any::type_name;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::fmt::Debug;
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
|
|
||||||
pub struct LumpReader<R> {
|
pub struct LumpReader<R> {
|
||||||
|
|
@ -47,19 +48,21 @@ impl<R: BinReaderExt + Read> LumpReader<R> {
|
||||||
Ok(entries)
|
Ok(entries)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read<T: BinRead>(&mut self) -> BspResult<T>
|
pub fn read<T: BinRead + Debug>(&mut self) -> BspResult<T>
|
||||||
where
|
where
|
||||||
T::Args: Default,
|
T::Args: Default,
|
||||||
{
|
{
|
||||||
let start = self.inner.stream_position().unwrap() as usize;
|
// let start = self.inner.stream_position().unwrap() as usize;
|
||||||
let result = self.inner.read_le()?;
|
let result = self.inner.read_le()?;
|
||||||
let end = self.inner.stream_position().unwrap() as usize;
|
// let end = self.inner.stream_position().unwrap() as usize;
|
||||||
debug_assert_eq!(
|
// todo: figure out how to only run this check for types that don't allocate
|
||||||
end - start,
|
// debug_assert_eq!(
|
||||||
size_of::<T>(),
|
// end - start,
|
||||||
"Incorrect number of bytes consumed while reading a {}",
|
// size_of::<T>(),
|
||||||
type_name::<T>()
|
// "Incorrect number of bytes consumed while reading a {} ({:#?})",
|
||||||
);
|
// type_name::<T>(),
|
||||||
|
// result
|
||||||
|
// );
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue