summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJakob L. Kreuze <jakob@memeware.net>2018-06-12 17:45:19 -0400
committerJakob L. Kreuze <jakob@memeware.net>2018-06-12 17:45:19 -0400
commit527c2f8e341d87a94031644697dec354e9b67910 (patch)
treea61b775b35f0042c5856051d388431109db594ba /src
parentc1fcfc3a58fc0061fb9262c95d728063029e1c2f (diff)
Cleaned up and began to implement tests.
Diffstat (limited to 'src')
-rw-r--r--src/bitmap.rs191
-rw-r--r--src/grp.rs45
-rw-r--r--src/main.rs13
-rw-r--r--src/renderer.rs47
-rw-r--r--src/timer.rs49
-rw-r--r--src/world.rs187
6 files changed, 302 insertions, 230 deletions
diff --git a/src/bitmap.rs b/src/bitmap.rs
index 8773a66..d9ec391 100644
--- a/src/bitmap.rs
+++ b/src/bitmap.rs
@@ -23,7 +23,29 @@ use std::io::{Cursor, Read, Seek, SeekFrom};
use self::byteorder::{LE, ReadBytesExt};
use grp::GroupManager;
-/// Rectangular chunk of ARGB data.
+/// A rectangular chunk of raw ARGB8888 data. That is, each byte carries 8 bits
+/// of information about the intensity of a certain color. The following is a
+/// list of bitmasks and which color ("channel," in the vernacular) they
+/// represent.
+///
+/// 0x000000ff - Blue
+/// 0x0000ff00 - Green
+/// 0x00ff0000 - Red
+/// 0xff000000 - Alpha (transparency)
+///
+/// # Examples
+///
+/// Colors are extracted from each individual integer via bitwise operations.
+///
+/// ```
+/// let bitmap = bitmaps.get(277); // Arbitrary choice of tile number.
+/// let corner = bitmap.data[0];
+///
+/// let b = corner & 0xff;
+/// let g = (corner >> 8) & 0xff;
+/// let r = (corner >> 16) & 0xff;
+/// let a = (corner >> 24) & 0xff;
+/// ```
#[derive(Clone)]
pub struct Bitmap {
pub width: u16,
@@ -31,14 +53,13 @@ pub struct Bitmap {
pub data: Vec<u32>,
}
-/// Manages access to bitmap tiles in a GRP archive.
+/// Implementation of a bitmap cache, which is used for obtaining a bitmap
+/// conversion of the individual tiles in a group file.
pub struct BitmapManager {
bitmaps: Vec<Bitmap>,
}
impl BitmapManager {
- // FIXME: Should we have a hardcoded palette to fall back on if there is no
- // PALETTE.DAT? That might make sense for Blood.
/// Create a new BitmapManager, loading all of the bitmap tiles in the given
/// GRP archive.
///
@@ -98,22 +119,26 @@ impl BitmapManager {
// - 'paletteLoadFromDisk' in EDuke's 'build/src/palette.cpp'
// - 'loadpalette' in Build's 'ENGINE.C'
// - 'LoadPalette' in Transfusion's 'arttools/art2tga.c'
+
let palette = if let Some(data) = grp.get("PALETTE.DAT") {
let mut palette = Vec::new();
- // FIXME: As of right now it's RGB, not ARGB.
for i in 0..256 {
- let r = data[(i * 3)] << 2;
- let g = data[(i * 3) + 1] << 2;
- let b = data[(i * 3) + 2] << 2;
-
- // FIXME: Also, I'm not a fan of this manual shifting shit.
- palette.push(((r as u32) << 16) | ((g as u32) << 8) | (b as u32));
+ // The VGA 262,144 color palette appears darker than intended
+ // when interpreted ARGB8888 output, so the intensity of the
+ // red, green, and blue channels are scaled by a uniform factor.
+ let a = (0xff) as u32;
+ let r = (data[(i * 3)] << 2) as u32;
+ let g = (data[(i * 3) + 1] << 2) as u32;
+ let b = (data[(i * 3) + 2] << 2) as u32;
+ palette.push(a << 24 | r << 16 | g << 8 | b);
}
palette
} else {
- bail!("No PALETTE.DAT");
+ // FIXME: There should be a default palette to fall back on. Update
+ // the documentation for this method when that's implemented.
+ bail!("No PALETTE.DAT in GRP archive.");
};
// From BUILDINF.TXT
@@ -121,20 +146,20 @@ impl BitmapManager {
// All art files must have xxxxx###.ART. When loading an art file you
// should keep trying to open new xxxxx###'s, incrementing the number,
// until an art file is not found.
+
for i in 0.. {
if let Some(data) = grp.get(&format!("TILES{:03}.ART", i)) {
- // FIXME: Hoo, boy. Passing that Error back up the stack is
- // probably a pretty bad way of handling an invalid header.
- bitmaps.extend_from_slice(&BitmapManager::load_art(data, &palette)?);
+ // FIXME: Passing the Error back up the stack is suboptimal (?)
+ let parsed = BitmapManager::load_art(data, &palette)?;
+ bitmaps.extend_from_slice(&parsed);
} else {
- // Indicates that there was no TILES000.ART, implying that
- // literally NO bitmaps were loaded. That probably isn't right.
+ // Indicates that we didn't even load TILES000.ART, meaning that
+ // literally NO bitmaps were loaded. That's a pretty big issue.
if i == 0 {
- bail!("No TILES000.ART");
+ bail!("No TILES000.ART in GRP archive.");
}
- // But if we get here, it means that we just hit the last
- // tilesheet and it's not worth our time to continue checking.
+ // But if i > 0, we simply hit the last tilesheet. No problem.
break;
}
}
@@ -144,9 +169,11 @@ impl BitmapManager {
/// Load the tile given by the specified index, or None if no tile with the
/// specified index exists.
- pub fn get_tile(&self, index: i32) -> Option<&Bitmap> {
- if (index as usize) < self.bitmaps.len() {
- Some(&self.bitmaps[(index as usize)])
+ pub fn get(&self, index: i32) -> Option<&Bitmap> {
+ let index = index as usize;
+
+ if index < self.bitmaps.len() {
+ Some(&self.bitmaps[index])
} else {
None
}
@@ -154,6 +181,12 @@ impl BitmapManager {
/// Loads the tiles in an TILES###.ART file.
fn load_art(data: &[u8], palette: &[u32]) -> Result<Vec<Bitmap>, Box<Error>> {
+ let len = data.len() as u32;
+
+ if len < 16 {
+ bail!("ART does not contain a valid header.");
+ }
+
let mut data = Cursor::new(data);
let mut bitmaps = Vec::new();
@@ -164,6 +197,7 @@ impl BitmapManager {
// The first 4 bytes in the art format are the version number. The
// current current art version is now 1. If artversion is not 1 then
// either it's the wrong art version or something is wrong.
+
let version = data.read_u32::<LE>()?;
if version != 1 {
@@ -177,7 +211,8 @@ impl BitmapManager {
// would need this variable, but it turned it is was unnecessary. To get
// the number of tiles, you should search all art files, and check the
// localtilestart and localtileend values for each file.
- let _ = data.read_u32::<LE>()?;
+
+ let _count = data.read_u32::<LE>()?;
// 3. long localtilestart;
//
@@ -194,10 +229,15 @@ impl BitmapManager {
// TILES001.ART -> localtilestart = 256, localtileend = 511
// TILES002.ART -> localtilestart = 512, localtileend = 767
// TILES003.ART -> localtilestart = 768, localtileend = 1023
+
let first_tile_index = data.read_u32::<LE>()?;
let last_tile_index = data.read_u32::<LE>()?;
let count = last_tile_index - first_tile_index + 1;
+ if len < 8 * count + 16 {
+ bail!(format!("Invalid number of tiles (given: {})", count));
+ }
+
// 5. short tilesizx[localtileend-localtilestart+1];
//
// This is an array of shorts of all the x dimensions of the tiles in this art
@@ -238,49 +278,106 @@ impl BitmapManager {
// memory is stored. Example on a 4*4 file:
//
// Offsets:
- // -----------------
- // | 0 | 4 | 8 |12 |
- // -----------------
- // | 1 | 5 | 9 |13 |
- // -----------------
- // | 2 | 6 |10 |14 |
- // -----------------
- // | 3 | 7 |11 |15 |
- // -----------------
- let mut data_off = (16 + 2 * count + 2 * count + 4 * count) as u64;
-
- for i in 0..count {
- // The header is 16 bytes, the width and height arrays are both 2
- // bytes per value, and the attribute array is 4 bytes per value.
- // The raw bitmap data follows immediately and the bounds of each
- // bitmap are inferred from the width and height.
- let width_array_off = (2 * i + 16) as u64;
- let height_array_off = (2 * i + 2 * count + 16) as u64;
+ // ---------------------
+ // | 0 | 4 | 8 | 12 |
+ // ---------------------
+ // | 1 | 5 | 9 | 13 |
+ // ---------------------
+ // | 2 | 6 | 10 | 14 |
+ // ---------------------
+ // | 3 | 7 | 11 | 15 |
+ // ---------------------
+
+ // 16 bytes for the header, 2 bytes per entry for the array of bitmap
+ // widths, 2 bytes per entry for the array of bitmap heights, and 4
+ // bytes per entry for the array of bitmap attributes.
+
+ let data_off = 16 + 2 * count + 2 * count + 4 * count;
+ let mut data_off = data_off as u64;
+
+ if (len as u64) < data_off {
+ bail!(format!("Invalid number of tiles (given: {})", count));
+ }
+ for i in 0..count {
+ let width_array_off = 2 * i + 16;
+ let width_array_off = width_array_off as u64;
data.seek(SeekFrom::Start(width_array_off))?;
- let width = data.read_u16::<LE>()?;
+ let width = data.read_u16::<LE>()? as usize;
+ let height_array_off = 2 * i + 2 * count + 16;
+ let height_array_off = height_array_off as u64;
data.seek(SeekFrom::Start(height_array_off))?;
- let height = data.read_u16::<LE>()?;
+ let height = data.read_u16::<LE>()? as usize;
- let mut indices = vec![0; (width as usize) * (height as usize)];
+ let mut indices = vec![0; width * height];
data.seek(SeekFrom::Start(data_off))?;
data.read(&mut indices)?;
data_off += indices.len() as u64;
let mut data = Vec::new();
- // FIXME: Casting this as usize is just nasty, man.
for column in 0..width {
for row in 0..height {
- let index = indices[(row as usize) * (width as usize) + (column as usize)];
+ let index = indices[row * width + column];
data.push(palette[index as usize]);
}
}
+ let width = width as u16;
+ let height = height as u16;
+
bitmaps.push(Bitmap { width, height, data });
}
Ok(bitmaps)
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_load_slice() {
+ // Generated palette blob. All colors in the palette are white (HTML hex
+ // ffffffff).
+ let palette = vec![0xffffffff; 256];
+
+ // Binary blob containing an ART test vector, made by me. Contains one
+ // tile, a single black pixel.
+ let data = vec![
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00,
+ ];
+
+ let parsed = BitmapManager::load_art(&data, &palette).unwrap();
+
+ assert_eq!(parsed.len(), 1);
+
+ let tile = &parsed[0];
+
+ assert_eq!(tile.width, 1);
+ assert_eq!(tile.height, 1);
+ assert_eq!(tile.data[0], 0xffffffff);
+ }
+
+ #[test]
+ fn test_incomplete_header() {
+ let palette = vec![0xffffffff; 256];
+
+ // Binary blob similar to the ART test vector above, but without the
+ // actual bitmap data.
+ let data = vec![
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ ];
+
+ if let Ok(_) = BitmapManager::load_art(&data, &palette) {
+ panic!("Parser accepted invalid ART file.");
+ }
+ }
+}
diff --git a/src/grp.rs b/src/grp.rs
index 3bed2fc..82fa507 100644
--- a/src/grp.rs
+++ b/src/grp.rs
@@ -22,7 +22,7 @@ use std::error::Error;
use std::fs::File;
use std::io::Read;
-use self::byteorder::{ByteOrder, LittleEndian};
+use self::byteorder::{ByteOrder, LE};
use path::PathManager;
// What's the .GRP file format?
@@ -40,13 +40,15 @@ use path::PathManager;
/// Silverman's original code goes about loading game data.
#[derive(Debug)]
pub struct GroupManager {
- path_manager: PathManager,
+ paths: PathManager,
files: HashMap<String, Vec<u8>>,
}
impl GroupManager {
- pub fn new(path_manager: PathManager) -> GroupManager {
- GroupManager { path_manager, files: HashMap::new() }
+ /// Creates a new GroupManager that uses the given PathManager to resolve
+ /// the locations of named GRP files.
+ pub fn new(paths: PathManager) -> GroupManager {
+ GroupManager { paths, files: HashMap::new() }
}
/// Loads the contents of an in-memory group file into the cache.
@@ -68,14 +70,14 @@ impl GroupManager {
bail!("Invalid GRP header.");
}
- let file_count = LittleEndian::read_u32(&data[12..16]) as usize;
+ let file_count = LE::read_u32(&data[12..16]) as usize;
// 16 bytes for the header, and 16 bytes for each table entry. The raw
// data will follow.
let data_start = 16 * (file_count + 1) as usize;
if data_start >= len {
- bail!("Invalid number of files.");
+ bail!(format!("Invalid number of files. (given: {})", file_count));
}
let mut data_off = data_start;
@@ -94,7 +96,7 @@ impl GroupManager {
};
let size = &data[table_off+12..table_off+16];
- let size = LittleEndian::read_u32(size) as usize;
+ let size = LE::read_u32(size) as usize;
if data_off + size > len {
bail!("`data_off >= len` - Table was likely corrupted.");
@@ -116,7 +118,7 @@ impl GroupManager {
///
/// A return value of 'Err' indicates that the given path did not exist.
pub fn load_file(&mut self, name: &str) -> Result<(), Box<Error>> {
- if let Some(path) = self.path_manager.find(name) {
+ if let Some(path) = self.paths.find(name) {
let mut file = File::open(path)?;
let mut bytes: Vec<u8> = Vec::new();
@@ -136,7 +138,7 @@ impl GroupManager {
}
#[cfg(test)]
-mod grp_tests {
+mod tests {
use super::*;
#[test]
@@ -161,12 +163,11 @@ mod grp_tests {
0x01, 0x02, 0x02, 0x03, 0x03, 0x03,
];
- let path_manager = PathManager::new();
- let mut group_manager = GroupManager::new(path_manager);
+ let paths = PathManager::new();
+ let mut group_manager = GroupManager::new(paths);
- match group_manager.load_data(&data) {
- Err(e) => panic!("{}", e),
- Ok(_) => (),
+ if let Err(e) = group_manager.load_data(&data) {
+ panic!("{}", e);
}
let data = match group_manager.get("TESTFILEA") {
@@ -205,8 +206,8 @@ mod grp_tests {
b'J', b'a', b'k', b'o', b'b',
];
- let path_manager = PathManager::new();
- let mut group_manager = GroupManager::new(path_manager);
+ let paths = PathManager::new();
+ let mut group_manager = GroupManager::new(paths);
if let Ok(_) = group_manager.load_data(&data) {
panic!("Accepted invalid header.");
@@ -225,8 +226,8 @@ mod grp_tests {
0x01,
];
- let path_manager = PathManager::new();
- let mut group_manager = GroupManager::new(path_manager);
+ let paths = PathManager::new();
+ let mut group_manager = GroupManager::new(paths);
if let Ok(_) = group_manager.load_data(&data) {
panic!("Accepted invalid header.");
@@ -246,8 +247,8 @@ mod grp_tests {
0x01,
];
- let path_manager = PathManager::new();
- let mut group_manager = GroupManager::new(path_manager);
+ let paths = PathManager::new();
+ let mut group_manager = GroupManager::new(paths);
if let Ok(_) = group_manager.load_data(&data) {
panic!("Accepted invalid header.");
@@ -266,8 +267,8 @@ mod grp_tests {
0x01,
];
- let path_manager = PathManager::new();
- let mut group_manager = GroupManager::new(path_manager);
+ let paths = PathManager::new();
+ let mut group_manager = GroupManager::new(paths);
if let Ok(_) = group_manager.load_data(&data) {
panic!("Accepted invalid header.");
diff --git a/src/main.rs b/src/main.rs
index 3a95beb..7173f47 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -25,19 +25,18 @@ mod path;
mod world;
fn main() {
- let path_manager = path::PathManager::new();
+ let paths = path::PathManager::new();
let filename = "DUKE3D.GRP";
- let mut group_manager = grp::GroupManager::new(path_manager);
+ let mut groups = grp::GroupManager::new(paths);
- if let Err(e) = group_manager.load_file(filename) {
+ if let Err(e) = groups.load_file(filename) {
println!("Couldn't open {}: {}", filename, e);
process::exit(1);
}
- let map = group_manager.get("E1L1.MAP").unwrap();
- let _world = world::World::from_map(map);
+ let map = groups.get("E1L1.MAP").unwrap();
+ let world = world::World::from_map(map).unwrap();
- let bitmap_manager = bitmap::BitmapManager::new(&group_manager).unwrap();
- let _tile = bitmap_manager.get_tile(277).unwrap();
+ let bitmaps = bitmap::BitmapManager::new(&groups).unwrap();
}
diff --git a/src/renderer.rs b/src/renderer.rs
deleted file mode 100644
index 1f0d09f..0000000
--- a/src/renderer.rs
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (C) 2018 Jakob L. Kreuze, All Rights Reserved.
-//
-// This file is part of rebuild.
-//
-// rebuild is free software: you can redistribute it and/or modify it under the
-// terms of the GNU General Public License as published by the Free Software
-// Foundation, either version 3 of the License, or (at your option) any later
-// version.
-//
-// rebuild is distributed in the hope that it will be useful, but WITHOUT ANY
-// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-// A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License along with
-// rebuild. If not, see <http://www.gnu.org/licenses/>.
-
-extern crate sdl2;
-
-pub trait Renderer {
- pub fn new() -> Self;
-
- // FIXME: Error handling?
- pub fn window_title(title: &str);
-
- pub fn draw_rooms(player_num: i32, smooth_ratio: i32);
-}
-
-// TODO: Document this.
-pub struct ClassicRenderer;
-
-impl Renderer for ClassicRenderer {
- // TODO: Document this.
- // FIXME: Proper error handling.
- pub fn new() -> ClassicRenderer {
- // let context = sdl2::init().unwrap();
- // let video_subsystem = context.video().unwrap();
-
- // let display = video_subsystem.window("SDL2", 800, 600)
- // // .resizable()
- // // .build_glium()
- // .unwrap();
-
- ClassicRenderer { }
- }
-
- pub fn window_title()
-}
diff --git a/src/timer.rs b/src/timer.rs
deleted file mode 100644
index 1ad1ac5..0000000
--- a/src/timer.rs
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright (C) 2018 Jakob L. Kreuze, All Rights Reserved.
-//
-// This file is part of rebuild.
-//
-// rebuild is free software: you can redistribute it and/or modify it under the
-// terms of the GNU General Public License as published by the Free Software
-// Foundation, either version 3 of the License, or (at your option) any later
-// version.
-//
-// rebuild is distributed in the hope that it will be useful, but WITHOUT ANY
-// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-// A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License along with
-// rebuild. If not, see <http://www.gnu.org/licenses/>.
-
-extern crate sdl2;
-
-use sdl2::TimerSubsystem;
-
-static const FREQUENCY = 1000;
-
-/// Implementation of a timer for managing game ticks.
-pub struct Timer {
- last_sample: u32,
- ticks_per_second: u32,
- ms_per_u64_tick: f64,
-
- sdl_timer: TimerSubsystem,
-}
-
-impl Timer {
- /// Create a new timer with the given frequency.
- pub fn new(ticks_per_second: u32, sdl_timer: TimerSubsystem) -> Timer {
- let last_sample = sdl_timer.ticks() * tics_per_second / FREQUENCY;
- let ms_per_u64_tick = 1000.0 / sdl_timer.performance_frequency();
- Timer { last_sample, ticks_per_second, ms_per_u64_tick, sdl_timer }
- }
-
- // TODO: Document this.
- pub fn update(&mut self) {
- let ms = self.sdl_timer.ticks();
- let ticks = ms * self.ticks_per_second / FREQUENCY - self.last_sample;
-
- if ticks > 0 {
- self.last_sample += ticks;
- }
- }
-}
diff --git a/src/world.rs b/src/world.rs
index 34a11f4..113e315 100644
--- a/src/world.rs
+++ b/src/world.rs
@@ -24,20 +24,67 @@ use std::io::Cursor;
use self::byteorder::{LE, ReadBytesExt};
-/// Maintains the current state of the game world - the map geometry and
-/// everything contained within it.
+/// The basic element of a level, as understood by the BUILD engine - a logical
+/// collection of a floor, a ceiling, and some number of walls.
+#[derive(Debug)]
+pub struct Sector {
+ first_wall: i16,
+ wall_count: i16,
+ visibility: u8,
+ tags: (i16, i16, i16),
+
+ ceiling_height: i32,
+ ceiling_slope: i16,
+ ceiling_status: i16,
+ ceiling_bitmap: i16,
+ ceiling_shade: i8,
+ ceiling_palette: u8,
+ ceiling_panning: (u8, u8),
+
+ floor_height: i32,
+ floor_slope: i16,
+ floor_status: i16,
+ floor_bitmap: i16,
+ floor_shade: i8,
+ floor_palette: u8,
+ floor_panning: (u8, u8),
+}
+
+/// A "wall," taken to be some line segment as part of a sector's enclosing
+/// geometry. Portals, openings between sectors, are represented as walls, even
+/// though that isn't in-line with our typical definition of a "wall" in the
+/// real world.
+#[derive(Debug)]
+pub struct Wall {
+ position: (i32, i32),
+
+ adjacent_wall_index: i16,
+ opposite_wall_index: i16,
+ into_sector_index: i16,
+
+ bitmap: i16,
+ bitmap_overlay: i16,
+ shade: i8,
+ palette: u8,
+ stretch: (u8, u8),
+ panning: (u8, u8),
+
+ status: i16,
+ tags: (i16, i16, i16),
+}
+
+/// Maintains and advances the state of the entire game world, indluding the
+/// geometry of the current map.
#[derive(Debug)]
pub struct World {
- sectors: Vec<Sector>,
- walls: Vec<Wall>,
+ pub sectors: Vec<Sector>,
+ pub walls: Vec<Wall>,
}
impl World {
- // TODO: Document this.
- // FIXME: Doesn't do any sort of sanity checks on length.
+ /// Create a new World from the geometry and sprites specified in the given
+ /// MAP file.
pub fn from_map(data: &[u8]) -> Result<World, Box<Error>> {
- let mut data = Cursor::new(data);
-
// From BUILDINF.TXT
//
// Here is Ken's documentation on the COMPLETE BUILD map format:
@@ -72,13 +119,26 @@ impl World {
// close(fil);
// }
+ let len = data.len();
+
+ // This is the absolute minimum possible size for a MAP, containing the
+ // header and a 0 short for each of the three arrays. This will be
+ // incremented as we find out more information about the MAP file -
+ // specifically, the number of sectors, walls, and sprites we expect.
+ let mut expected_len = 22;
+
+ if len < expected_len {
+ bail!("File too small to possibly contain valid MAP.");
+ }
+
+ let mut data = Cursor::new(data);
let version = data.read_u32::<LE>()?;
if version != 7 {
bail!("Unsupported MAP version.");
}
- // TODO: Use this to position the world's player (?)
+ // TODO: Use this to position when initializing the player.
let _start_x = data.read_i32::<LE>()?;
let _start_y = data.read_i32::<LE>()?;
let _start_z = data.read_i32::<LE>()?;
@@ -137,6 +197,11 @@ impl World {
let mut sectors = Vec::new();
let sector_count = data.read_u16::<LE>()?;
+ expected_len += 40 * (sector_count as usize);
+
+ if len < expected_len {
+ bail!(format!("Invalid sector count (given: {})", sector_count));
+ }
for _ in 0..sector_count {
let first_wall = data.read_i16::<LE>()?;
@@ -235,6 +300,11 @@ impl World {
let mut walls = Vec::new();
let wall_count = data.read_u16::<LE>()?;
+ expected_len += 32 * (wall_count as usize);
+
+ if len < expected_len {
+ bail!(format!("Invalid wall count (given: {})", wall_count));
+ }
for _ in 0..wall_count {
let position_x = data.read_i32::<LE>()?;
@@ -298,19 +368,21 @@ impl World {
// } spritetype;
// spritetype sprite[4096];
//
- // x, y, z - position of sprite - can be defined at center bottom or center
+ // x, y, z - position of sprite - can be defined at center bottom or
+ // center
+ //
// cstat:
- // bit 0: 1 = Blocking sprite (use with clipmove, getzrange) "B"
- // bit 1: 1 = transluscence, 0 = normal "T"
- // bit 2: 1 = x-flipped, 0 = normal "F"
- // bit 3: 1 = y-flipped, 0 = normal "F"
- // bits 5-4: 00 = FACE sprite (default) "R"
- // 01 = WALL sprite (like masked walls)
- // 10 = FLOOR sprite (parallel to ceilings&floors)
- // bit 6: 1 = 1-sided sprite, 0 = normal "1"
- // bit 7: 1 = Real centered centering, 0 = foot center "C"
- // bit 8: 1 = Blocking sprite (use with hitscan / cliptype 1) "H"
- // bit 9: 1 = Transluscence reversing, 0 = normal "T"
+ // bit 0: 1 = Blocking sprite (use with clipmove, getzrange) "B"
+ // bit 1: 1 = transluscence, 0 = normal "T"
+ // bit 2: 1 = x-flipped, 0 = normal "F"
+ // bit 3: 1 = y-flipped, 0 = normal "F"
+ // bits 5-4: 00 = FACE sprite (default) "R"
+ // 01 = WALL sprite (like masked walls)
+ // 10 = FLOOR sprite (parallel to ceilings&floors)
+ // bit 6: 1 = 1-sided sprite, 0 = normal "1"
+ // bit 7: 1 = Real centered centering, 0 = foot center "C"
+ // bit 8: 1 = Blocking sprite (use with hitscan/cliptype 1) "H"
+ // bit 9: 1 = Transluscence reversing, 0 = normal "T"
// bits 10-14: reserved
// bit 15: 1 = Invisible sprite, 0 = not invisible
// picnum - texture index into art file
@@ -324,10 +396,16 @@ impl World {
// statnum - current status of sprite (inactive/monster/bullet, etc.)
//
// ang - angle the sprite is facing
- // owner, xvel, yvel, zvel, lotag, hitag, extra - These variables used by the game programmer only
+ // owner, xvel, yvel, zvel, lotag, hitag, extra - These variables used
+ // by the game programmer only
let mut sprites = Vec::new();
let sprite_count = data.read_u16::<LE>()?;
+ expected_len += 44 * (sprite_count as usize);
+
+ if len < expected_len {
+ bail!(format!("Invalid sprite count (given: {})", sprite_count));
+ }
for _ in 0..sprite_count {
let position_x = data.read_i32::<LE>()?;
@@ -380,47 +458,40 @@ impl World {
}
}
-#[derive(Debug)]
-struct Sector {
- first_wall: i16,
- wall_count: i16,
- visibility: u8,
- tags: (i16, i16, i16),
+#[cfg(test)]
+mod tests {
+ use super::*;
- ceiling_height: i32,
- ceiling_slope: i16,
- ceiling_status: i16,
- ceiling_bitmap: i16,
- ceiling_shade: i8,
- ceiling_palette: u8,
- ceiling_panning: (u8, u8),
-
- floor_height: i32,
- floor_slope: i16,
- floor_status: i16,
- floor_bitmap: i16,
- floor_shade: i8,
- floor_palette: u8,
- floor_panning: (u8, u8),
-}
+ #[test]
+ fn test_load_slice() {
+ // Binary blob containing a MAP test vector, made by me. Contains an
+ // arbitrary header, no walls, no sectors, and no sprites.
+ let data = vec![
+ 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+ ];
-#[derive(Debug)]
-struct Wall {
- position: (i32, i32),
+ if let Err(e) = World::from_map(&data) {
+ panic!("{}", e);
+ }
- adjacent_wall_index: i16,
- opposite_wall_index: i16,
- into_sector_index: i16,
+ // TODO: Test the contents of the map.
+ }
- bitmap: i16,
- bitmap_overlay: i16,
- shade: i8,
- palette: u8,
- stretch: (u8, u8),
- panning: (u8, u8),
+ #[test]
+ fn test_incomplete_header() {
+ // Binary blob similar to the MAP test vector above, but with a header
+ // that would be too small to be valid.
+ let data = vec![
+ 0x07, 0x00, 0x00, 0x00,
+ ];
- status: i16,
- tags: (i16, i16, i16),
+ if let Ok(_) = World::from_map(&data) {
+ panic!("Accepted invalid header.");
+ }
+ }
}
#[derive(Debug)]