vbsp/
lib.rs

1mod bspfile;
2pub mod data;
3pub mod error;
4mod handle;
5mod reader;
6
7use crate::bspfile::LumpType;
8pub use crate::data::TextureFlags;
9pub use crate::data::*;
10use crate::error::ValidationError;
11pub use crate::handle::Handle;
12use binrw::io::Cursor;
13use binrw::{BinRead, BinReaderExt};
14use bspfile::BspFile;
15pub use error::{BspError, StringError};
16use lzma_rs::decompress::{Options, UnpackedSize};
17use reader::LumpReader;
18use std::cmp::min;
19use std::io::Read;
20pub use vbsp_common::{deserialize_bool, AsPropPlacement};
21
22pub type BspResult<T> = Result<T, BspError>;
23
24// TODO: Store all the allocated objects inline to improve cache usage
25/// A parsed bsp file
26#[derive(Debug)]
27#[non_exhaustive]
28pub struct Bsp {
29    pub header: Header,
30    pub entities: Entities,
31    pub textures_data: Vec<TextureData>,
32    pub textures_info: Vec<TextureInfo>,
33    pub texture_string_tables: Vec<i32>,
34    pub texture_string_data: String,
35    pub planes: Vec<Plane>,
36    pub nodes: Vec<Node>,
37    pub leaves: Leaves,
38    pub leaf_faces: Vec<LeafFace>,
39    pub leaf_brushes: Vec<LeafBrush>,
40    pub models: Vec<Model>,
41    pub brushes: Vec<Brush>,
42    pub brush_sides: Vec<BrushSide>,
43    pub vertices: Vec<Vertex>,
44    pub edges: Vec<Edge>,
45    pub surface_edges: Vec<SurfaceEdge>,
46    pub faces: Vec<Face>,
47    pub original_faces: Vec<Face>,
48    pub vis_data: VisData,
49    pub displacements: Vec<DisplacementInfo>,
50    pub displacement_vertices: Vec<DisplacementVertex>,
51    pub displacement_triangles: Vec<DisplacementTriangle>,
52    vertex_normals: Vec<VertNormal>,
53    vertex_normal_indices: Vec<VertNormalIndex>,
54    pub static_props: PropStaticGameLump,
55    pub pack: Packfile,
56}
57
58impl Bsp {
59    pub fn read(data: &[u8]) -> BspResult<Self> {
60        let bsp_file = BspFile::new(data)?;
61
62        let entities = bsp_file.lump_reader(LumpType::Entities)?.read_entities()?;
63        let textures_data = bsp_file
64            .lump_reader(LumpType::TextureData)?
65            .read_vec(|r| r.read())?;
66        let textures_info = bsp_file
67            .lump_reader(LumpType::TextureInfo)?
68            .read_vec(|r| r.read())?;
69        let texture_string_tables = bsp_file
70            .lump_reader(LumpType::TextureDataStringTable)?
71            .read_vec(|r| r.read())?;
72        let texture_string_data = String::from_utf8(
73            bsp_file
74                .get_lump(bsp_file.get_lump_entry(LumpType::TextureDataStringData))?
75                .into_owned(),
76        )
77        .map_err(|e| BspError::String(StringError::NonUTF8(e.utf8_error())))?;
78        let planes = bsp_file
79            .lump_reader(LumpType::Planes)?
80            .read_vec(|r| r.read())?;
81        let nodes = bsp_file
82            .lump_reader(LumpType::Nodes)?
83            .read_vec(|r| r.read())?;
84        let leaves = bsp_file.lump_reader(LumpType::Leaves)?.read_args()?;
85        let leaf_faces = bsp_file
86            .lump_reader(LumpType::LeafFaces)?
87            .read_vec(|r| r.read())?;
88        let leaf_brushes = bsp_file
89            .lump_reader(LumpType::LeafBrushes)?
90            .read_vec(|r| r.read())?;
91        let models = bsp_file
92            .lump_reader(LumpType::Models)?
93            .read_vec(|r| r.read())?;
94        let brushes = bsp_file
95            .lump_reader(LumpType::Brushes)?
96            .read_vec(|r| r.read())?;
97        let brush_sides = bsp_file
98            .lump_reader(LumpType::BrushSides)?
99            .read_vec(|r| r.read())?;
100        let vertices = bsp_file
101            .lump_reader(LumpType::Vertices)?
102            .read_vec(|r| r.read())?;
103        let edges = bsp_file
104            .lump_reader(LumpType::Edges)?
105            .read_vec(|r| r.read())?;
106        let surface_edges = bsp_file
107            .lump_reader(LumpType::SurfaceEdges)?
108            .read_vec(|r| r.read())?;
109        let faces = bsp_file
110            .lump_reader(LumpType::Faces)?
111            .read_vec(|r| r.read())?;
112        let original_faces = bsp_file
113            .lump_reader(LumpType::OriginalFaces)?
114            .read_vec(|r| r.read())?;
115        let vis_data = bsp_file.lump_reader(LumpType::Visibility)?.read_visdata()?;
116        let displacements = bsp_file
117            .lump_reader(LumpType::DisplacementInfo)?
118            .read_vec(|r| r.read())?;
119        let displacement_vertices = bsp_file
120            .lump_reader(LumpType::DisplacementVertices)?
121            .read_vec(|r| r.read())?;
122        let displacement_triangles = bsp_file
123            .lump_reader(LumpType::DisplacementTris)?
124            .read_vec(|r| r.read())?;
125        let vertex_normals = bsp_file
126            .lump_reader(LumpType::VertNormals)?
127            .read_vec(|r| r.read())?;
128        let vertex_normal_indices = bsp_file
129            .lump_reader(LumpType::VertNormalIndices)?
130            .read_vec(|r| r.read())?;
131        let game_lumps: GameLumpHeader = bsp_file.lump_reader(LumpType::GameLump)?.read()?;
132        let pack = Packfile::read(bsp_file.lump_reader(LumpType::PakFile)?.into_data())?;
133
134        let static_props = game_lumps
135            .find(data)
136            .ok_or(ValidationError::NoStaticPropLump)??;
137
138        let bsp = Bsp {
139            header: bsp_file.header().clone(),
140            entities,
141            textures_data,
142            textures_info,
143            texture_string_tables,
144            texture_string_data,
145            planes,
146            nodes,
147            leaves,
148            leaf_faces,
149            leaf_brushes,
150            models,
151            brushes,
152            brush_sides,
153            vertices,
154            edges,
155            surface_edges,
156            faces,
157            original_faces,
158            vis_data,
159            displacements,
160            displacement_vertices,
161            displacement_triangles,
162            vertex_normals,
163            vertex_normal_indices,
164            static_props,
165            pack,
166        };
167        bsp.validate()?;
168        Ok(bsp)
169    }
170
171    pub fn leaf(&self, n: usize) -> Option<Handle<'_, Leaf>> {
172        self.leaves.get(n).map(|leaf| Handle::new(self, leaf))
173    }
174
175    pub fn plane(&self, n: usize) -> Option<Handle<'_, Plane>> {
176        self.planes.get(n).map(|plane| Handle::new(self, plane))
177    }
178
179    pub fn face(&self, n: usize) -> Option<Handle<'_, Face>> {
180        self.faces.get(n).map(|face| Handle::new(self, face))
181    }
182
183    pub fn node(&self, n: usize) -> Option<Handle<'_, Node>> {
184        self.nodes.get(n).map(|node| Handle::new(self, node))
185    }
186
187    pub fn texture_info(&self, n: usize) -> Option<Handle<'_, TextureInfo>> {
188        self.textures_info
189            .get(n)
190            .map(|texture_info| Handle::new(self, texture_info))
191    }
192
193    pub fn displacement(&self, n: usize) -> Option<Handle<'_, DisplacementInfo>> {
194        self.displacements
195            .get(n)
196            .map(|displacement| Handle::new(self, displacement))
197    }
198
199    fn displacement_vertex(&self, n: usize) -> Option<Handle<'_, DisplacementVertex>> {
200        self.displacement_vertices
201            .get(n)
202            .map(|vert| Handle::new(self, vert))
203    }
204
205    /// Get the root node of the bsp
206    pub fn root_node(&self) -> Handle<'_, Node> {
207        self.node(0).unwrap()
208    }
209
210    /// Get all models stored in the bsp
211    pub fn models(&self) -> impl Iterator<Item = Handle<'_, Model>> {
212        self.models.iter().map(move |m| Handle::new(self, m))
213    }
214
215    /// Get all models stored in the bsp
216    pub fn textures(&self) -> impl Iterator<Item = Handle<'_, TextureInfo>> {
217        self.textures_info.iter().map(move |m| Handle::new(self, m))
218    }
219
220    /// Find a leaf for a specific position
221    pub fn leaf_at(&self, point: Vector) -> Handle<'_, Leaf> {
222        let mut current = self.root_node();
223
224        loop {
225            let plane = current.plane();
226            let dot: f32 = point
227                .iter()
228                .zip(plane.normal.iter())
229                .map(|(a, b)| a * b)
230                .sum();
231
232            let [front, back] = current.children;
233
234            let next = if dot < plane.dist { back } else { front };
235
236            if next < 0 {
237                return self.leaf((!next) as usize).unwrap();
238            } else {
239                current = self.node(next as usize).unwrap();
240            }
241        }
242    }
243
244    pub fn static_props(&self) -> impl Iterator<Item = Handle<'_, StaticPropLump>> {
245        self.static_props
246            .props
247            .props
248            .iter()
249            .map(|lump| Handle::new(self, lump))
250    }
251
252    /// Get all faces stored in the bsp
253    pub fn original_faces(&self) -> impl Iterator<Item = Handle<Face>> {
254        self.faces.iter().map(move |face| Handle::new(self, face))
255    }
256
257    fn validate(&self) -> BspResult<()> {
258        self.validate_indexes(
259            self.faces
260                .iter()
261                .filter_map(|face| face.displacement_index()),
262            &self.displacements,
263            "face",
264            "displacement",
265        )?;
266        self.validate_indexes(
267            self.displacements
268                .iter()
269                .map(|displacement| displacement.map_face),
270            &self.faces,
271            "displacement",
272            "face",
273        )?;
274        self.validate_indexes(
275            self.faces
276                .iter()
277                .map(|face| face.first_edge + face.num_edges as i32 - 1),
278            &self.surface_edges,
279            "face",
280            "surface_edge",
281        )?;
282        self.validate_indexes(
283            self.surface_edges.iter().map(|edge| edge.edge_index()),
284            &self.edges,
285            "surface_edge",
286            "edge",
287        )?;
288        self.validate_indexes(
289            self.edges
290                .iter()
291                .flat_map(|edge| [edge.start_index, edge.end_index]),
292            &self.vertices,
293            "edge",
294            "vertex",
295        )?;
296        self.validate_indexes(
297            self.displacements
298                .iter()
299                .flat_map(|displacement| &displacement.corner_neighbours)
300                .flat_map(|corner| corner.neighbours()),
301            &self.displacements,
302            "displacement",
303            "displacement",
304        )?;
305        self.validate_indexes(
306            self.displacements
307                .iter()
308                .flat_map(|displacement| &displacement.edge_neighbours)
309                .flat_map(|edge| edge.iter())
310                .map(|sub| sub.neighbour_index),
311            &self.displacements,
312            "displacement",
313            "displacement",
314        )?;
315        self.validate_indexes(
316            self.faces.iter().map(|face| face.texture_info),
317            &self.textures_info,
318            "face",
319            "texture_info",
320        )?;
321        self.validate_indexes(
322            self.textures_info
323                .iter()
324                .map(|texture| texture.texture_data_index),
325            &self.textures_data,
326            "texture_info",
327            "texture_data",
328        )?;
329        self.validate_indexes(
330            self.textures_data
331                .iter()
332                .map(|texture| texture.name_string_table_id),
333            &self.texture_string_tables,
334            "textures_data",
335            "texture_string_tables",
336        )?;
337        self.validate_indexes(
338            self.texture_string_tables.iter().copied(),
339            self.texture_string_data.as_bytes(),
340            "texture_string_tables",
341            "texture_string_data",
342        )?;
343        self.validate_indexes(
344            self.nodes.iter().map(|node| node.plane_index),
345            &self.planes,
346            "node",
347            "plane",
348        )?;
349        self.validate_indexes(
350            self.nodes
351                .iter()
352                .flat_map(|node| node.children)
353                .filter(|index| *index >= 0),
354            &self.nodes,
355            "node",
356            "node",
357        )?;
358        self.validate_indexes(
359            self.nodes
360                .iter()
361                .flat_map(|node| node.children)
362                .filter_map(|index| (index < 0).then_some(!index)),
363            &self.leaves,
364            "node",
365            "leaf",
366        )?;
367        self.validate_indexes(
368            self.static_props().map(|prop| prop.prop_type),
369            &self.static_props.dict.name,
370            "static props",
371            "static prop models",
372        )?;
373        self.validate_indexes(
374            self.vertex_normal_indices.iter().map(|i| i.index),
375            &self.vertex_normals,
376            "vertex normal indices",
377            "vertex normals",
378        )?;
379
380        if self.nodes.is_empty() {
381            return Err(ValidationError::NoRootNode.into());
382        }
383
384        for face in &self.faces {
385            if face.displacement_index().is_some() && face.num_edges != 4 {
386                return Err(ValidationError::NonSquareDisplacement(face.num_edges).into());
387            }
388        }
389
390        Ok(())
391    }
392
393    fn validate_indexes<
394        'b,
395        Index: TryInto<usize> + Into<i64> + Copy + Ord + Default,
396        Indexes: Iterator<Item = Index>,
397        T: 'b,
398    >(
399        &'b self,
400        indexes: Indexes,
401        list: &[T],
402        source: &'static str,
403        target: &'static str,
404    ) -> BspResult<()> {
405        let max = match indexes.max() {
406            Some(max) => max,
407            None => return Ok(()),
408        };
409        max.try_into()
410            .ok()
411            .and_then(|index| list.get(index))
412            .ok_or_else(|| ValidationError::ReferenceOutOfRange {
413                source_: source,
414                target,
415                index: max.into(),
416                size: list.len(),
417            })?;
418        Ok(())
419    }
420}
421
422/// LZMA decompression with the header used by source
423fn lzma_decompress_with_header(data: &[u8], expected_length: usize) -> Result<Vec<u8>, BspError> {
424    // extra 8 byte because game lumps need some padding for reasons
425    let mut output: Vec<u8> = Vec::with_capacity(min(expected_length + 8, 8 * 1024 * 1024));
426    let mut cursor = Cursor::new(data);
427    if b"LZMA" != &<[u8; 4]>::read(&mut cursor)? {
428        return Err(BspError::LumpDecompressError(
429            lzma_rs::error::Error::LzmaError("Invalid lzma header".into()),
430        ));
431    }
432    let actual_size: u32 = cursor.read_le()?;
433    let lzma_size: u32 = cursor.read_le()?;
434    if data.len() < lzma_size as usize + 12 {
435        return Err(BspError::UnexpectedCompressedLumpSize {
436            got: data.len() as u32,
437            expected: lzma_size,
438        });
439    }
440    lzma_rs::lzma_decompress_with_options(
441        &mut cursor,
442        &mut output,
443        &Options {
444            unpacked_size: UnpackedSize::UseProvided(Some(actual_size as u64)),
445            allow_incomplete: false,
446            memlimit: None,
447        },
448    )
449    .map_err(BspError::LumpDecompressError)?;
450    if output.len() != expected_length {
451        return Err(BspError::UnexpectedUncompressedLumpSize {
452            got: output.len() as u32,
453            expected: expected_length as u32,
454        });
455    }
456    Ok(output)
457}
458
459#[cfg(test)]
460mod tests {
461    use super::Bsp;
462
463    #[test]
464    fn tf2_file() {
465        use std::fs::read;
466
467        let data = read("koth_bagel_rc2a.bsp").unwrap();
468
469        Bsp::read(&data).unwrap();
470    }
471}