├── .gitignore ├── src ├── drawing │ ├── mod.rs │ ├── bezier.rs │ └── framebuffer.rs ├── files │ ├── mod.rs │ ├── lmp.rs │ ├── filemanager.rs │ └── packfile.rs ├── main.rs ├── util │ ├── timer.rs │ ├── options.rs │ ├── mod.rs │ └── vector.rs └── host.rs ├── .travis.yml ├── Cargo.toml ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | *.dll -------------------------------------------------------------------------------- /src/drawing/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod bezier; 2 | pub mod framebuffer; 3 | 4 | pub use self::bezier::BezierCurve; 5 | pub use self::framebuffer::Framebuffer; 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | install: 3 | - sudo add-apt-repository ppa:team-xbmc/ppa -y 4 | - sudo apt-get update -q 5 | - sudo apt-get install libsdl2-dev -------------------------------------------------------------------------------- /src/files/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod filemanager; 2 | pub mod lmp; 3 | pub mod packfile; 4 | 5 | pub use self::filemanager::*; 6 | pub use self::lmp::LmpImage; 7 | pub use self::packfile::*; 8 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Martin Tomasi "] 3 | name = "quake-rs" 4 | version = "0.1.0" 5 | 6 | [dependencies] 7 | rand = "*" 8 | sdl2 = "*" 9 | byteorder = "*" 10 | hprof = "*" 11 | 12 | [dependencies.clippy] 13 | optional = true 14 | version = "*" 15 | 16 | [features] 17 | nightly = ["clippy"] 18 | 19 | [profile] 20 | [profile.release] 21 | debug = true 22 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![cfg_attr(feature="nightly", feature(plugin, test))] 3 | #![cfg_attr(feature="nightly", plugin(clippy))] 4 | 5 | extern crate sdl2; 6 | extern crate rand; 7 | extern crate byteorder; 8 | extern crate hprof; 9 | 10 | #[cfg(feature="nightly")] 11 | extern crate test; 12 | 13 | use host::Host; 14 | 15 | mod drawing; 16 | mod files; 17 | mod util; 18 | mod host; 19 | 20 | fn main() { 21 | Host::new().run(); 22 | } 23 | -------------------------------------------------------------------------------- /src/drawing/bezier.rs: -------------------------------------------------------------------------------- 1 | use util::vector::Vec2; 2 | 3 | pub struct BezierCurve { 4 | start: Vec2, 5 | control: Vec2, 6 | end: Vec2, 7 | } 8 | 9 | impl BezierCurve { 10 | pub fn new(s: Vec2, c: Vec2, e: Vec2) -> BezierCurve { 11 | BezierCurve { 12 | start: s, 13 | control: c, 14 | end: e, 15 | } 16 | } 17 | 18 | pub fn evaluate(&self, t: f32) -> Vec2 { 19 | let s = 1.0 - t; 20 | let c0 = s * s; 21 | let c1 = 2.0 * s * t; 22 | let c2 = t * t; 23 | 24 | self.start * c0 + self.control * c1 + self.end * c2 25 | } 26 | 27 | pub fn approximate(&self, num_points: usize) -> Vec { 28 | let mut t = 0.0; 29 | let step = 1.0 / num_points as f32; 30 | let mut pts = vec![]; 31 | 32 | while t <= 1.0 { 33 | pts.push(self.evaluate(t)); 34 | t += step; 35 | } 36 | 37 | pts 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # quake-rs 2 | A Rust re-implementation of Handmade Quake. 3 | 4 | ## Goals 5 | * Multiplatform: I would love this to run on Windows 7+ and Linux. I don't have a Mac OS device, so I won't be able to test there. 6 | * Idiomatic Rust, preserving the broad strokes of the original implementation. 7 | * No unsafe code! (since I'm now using SDL2 and the Rust `std::time` API for the timer) 8 | 9 | ## Building 10 | As far as pre-installed libraries go, quake-rs requires SDL2. For instructions on installation, see the GitHub page 11 | for [rust-sdl2](https://github.com/AngryLawyer/rust-sdl2). Currently, the project only builds on nightly, since I want to 12 | use `std::time::Instant`, which is still unstable (added recently). I recommend using multirust-rs to manage multiple Rust versions. 13 | 14 | To run tests: 15 | ``` 16 | cargo test 17 | ``` 18 | 19 | To run the application: 20 | ``` 21 | cargo run [--release] 22 | ``` 23 | 24 | ## Contributing 25 | Contributions are very welcome. I'll try to keep up with the progress of the videos on a week-to-week basis, but I can't guarantee 26 | I'll always have enough time. 27 | -------------------------------------------------------------------------------- /src/util/timer.rs: -------------------------------------------------------------------------------- 1 | use std::time::{Duration, Instant}; 2 | 3 | const MAX_FRAMERATE: f32 = 72.0; 4 | 5 | pub struct Timer { 6 | start: Instant, 7 | last_frame: Instant, 8 | total: Duration, 9 | frame_duration: Duration, 10 | unlocked: bool, 11 | } 12 | 13 | impl Timer { 14 | pub fn new(unlocked: bool) -> Timer { 15 | Timer { 16 | start: Instant::now(), 17 | last_frame: Instant::now(), 18 | total: Duration::from_millis(0), 19 | frame_duration: Duration::new(0, (1e9 / MAX_FRAMERATE) as u32), 20 | unlocked: unlocked, 21 | } 22 | } 23 | 24 | pub fn step(&mut self) -> Option { 25 | let now = Instant::now(); 26 | let timestep = now.duration_since(self.last_frame); 27 | self.total = self.start.elapsed(); 28 | if self.unlocked || timestep > self.frame_duration { 29 | self.last_frame = now; 30 | Some(timestep) 31 | } else { 32 | None 33 | } 34 | } 35 | 36 | pub fn elapsed(&self) -> Duration { 37 | self.total 38 | } 39 | } 40 | 41 | #[cfg(test)] 42 | mod tests { 43 | use super::Timer; 44 | use std::thread; 45 | use std::time::Duration; 46 | 47 | #[test] 48 | fn test_tick() { 49 | let mut timer = Timer::new(false); 50 | thread::sleep(Duration::from_millis(5)); 51 | assert_eq!(timer.step().is_some(), false); 52 | thread::sleep(Duration::from_millis(9)); 53 | assert_eq!(timer.step().is_some(), true); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/files/lmp.rs: -------------------------------------------------------------------------------- 1 | use std::{io, fmt}; 2 | use byteorder::{LittleEndian, ReadBytesExt}; 3 | use hprof; 4 | 5 | pub struct LmpImage<'a> { 6 | width: u32, 7 | height: u32, 8 | data: &'a [u8], 9 | } 10 | 11 | impl<'a> LmpImage<'a> { 12 | pub fn from_bytes(data: &'a [u8]) -> io::Result> { 13 | hprof::enter("LmpImage::from_bytes"); 14 | let mut cursor = io::Cursor::new(data); 15 | let width = try!(cursor.read_u32::()); 16 | let height = try!(cursor.read_u32::()); 17 | let bytes = &cursor.get_ref()[8..]; 18 | 19 | Ok(LmpImage { 20 | width: width, 21 | height: height, 22 | data: bytes, 23 | }) 24 | } 25 | 26 | #[inline] 27 | fn index(&self, y: u32, x: u32) -> usize { 28 | (x * self.width + y) as usize 29 | } 30 | 31 | pub fn get(&self, x: u32, y: u32) -> u8 { 32 | self.data[self.index(x, y)] 33 | } 34 | 35 | pub fn width(&self) -> u32 { 36 | self.width 37 | } 38 | 39 | pub fn height(&self) -> u32 { 40 | self.height 41 | } 42 | 43 | pub fn pixels(&self) -> &[u8] { 44 | self.data 45 | } 46 | } 47 | 48 | impl<'a> fmt::Debug for LmpImage<'a> { 49 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 50 | let mut s = String::new(); 51 | for y in 0..self.height { 52 | for x in 0..self.width { 53 | s.push_str(&format!("{} ", self.data[self.index(x, y)])); 54 | } 55 | 56 | s.push('\n'); 57 | } 58 | 59 | write!(f, "{}", s) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/util/options.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::str::FromStr; 3 | 4 | /// Rustified version of Quake's parameter parsing. Stores 5 | /// the command line arguments and provides methods to find 6 | /// parameters with arguments (e.g. "-alpha 50") and check the 7 | /// existence of boolean parameters (like "-windowed"). 8 | #[derive(Debug, Default)] 9 | pub struct Options { 10 | args: Vec, 11 | } 12 | 13 | impl Options { 14 | pub fn new() -> Options { 15 | Options { args: env::args().collect() } 16 | } 17 | 18 | pub fn with_args(args: Vec) -> Options { 19 | Options { args: args } 20 | } 21 | 22 | /// Return the parameter of the given argument. Returns None if the argument 23 | /// is either not found, couldn't be parsed to the desired type or the next 24 | /// argument is itself a commandline option (starts with '-'). 25 | pub fn check_param(&self, argument: &str) -> Option 26 | where T: FromStr 27 | { 28 | // Find the index of the argument in the argument list 29 | self.args.iter().position(|s| s == argument).and_then(|idx| { 30 | // Get the value occurring after that index 31 | self.args.get(idx + 1).and_then(|arg| { 32 | // Don't return other commandline options 33 | if arg.starts_with('-') { 34 | None 35 | } else { 36 | // Try to parse the value to the desired type 37 | arg.parse::().ok() 38 | } 39 | }) 40 | }) 41 | } 42 | 43 | /// Checks if the given parameter is set. 44 | pub fn is_set(&self, param: &str) -> bool { 45 | for s in &self.args { 46 | if s == param { 47 | return true; 48 | } 49 | } 50 | 51 | false 52 | } 53 | } 54 | 55 | #[cfg(test)] 56 | mod tests { 57 | use super::Options; 58 | #[test] 59 | fn test_parse_args() { 60 | let options = Options::with_args(vec!["-windowed".into(), "-alpha".into(), "50".into()]); 61 | let windowed = options.is_set("-windowed"); 62 | assert_eq!(windowed, true); 63 | let alpha: Option = options.check_param("-alpha"); 64 | assert_eq!(alpha, Some(50)); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/util/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod options; 2 | pub mod timer; 3 | pub mod vector; 4 | 5 | pub use self::options::Options; 6 | pub use self::timer::Timer; 7 | pub use self::vector::{Vec2, Vec3}; 8 | 9 | use std::time::Duration; 10 | 11 | #[allow(unused_variables)] 12 | pub fn ignore(value: T) { 13 | } 14 | 15 | /// Provides some additional conversions for Duration types. 16 | pub trait DurationExt { 17 | /// Returns the whole duration in seconds, including the nano-second 18 | /// precision. 19 | fn seconds(&self) -> f64; 20 | 21 | /// Returns the whole duration in milliseconds, including 22 | /// the nano-second precision. 23 | fn millis(&self) -> f64; 24 | 25 | /// Creates a time from nanoseconds. (since the Duration::new function only 26 | // takes nanoseconds as a u32, which can easily overflow) 27 | fn from_nanos(nanos: u64) -> Duration; 28 | } 29 | 30 | impl DurationExt for Duration { 31 | #[inline] 32 | fn seconds(&self) -> f64 { 33 | self.as_secs() as f64 + self.subsec_nanos() as f64 / 1e9 34 | } 35 | 36 | #[inline] 37 | fn millis(&self) -> f64 { 38 | self.as_secs() as f64 * 1000.0 + (self.subsec_nanos() as f64 / 1e6) 39 | } 40 | 41 | #[inline] 42 | fn from_nanos(nanos: u64) -> Duration { 43 | if nanos > 1_000_000_000 { 44 | let seconds = nanos / 1_000_000_000; 45 | let nanos = nanos as u64 - (seconds as u64 * 1_000_000_000); 46 | Duration::new(seconds, nanos as u32) 47 | } else { 48 | Duration::new(0, nanos as u32) 49 | } 50 | } 51 | } 52 | 53 | #[derive(Debug, PartialEq, Eq, Default, Clone, Copy)] 54 | pub struct Color { 55 | pub r: u8, 56 | pub g: u8, 57 | pub b: u8, 58 | _unused: u8, 59 | } 60 | 61 | impl Color { 62 | pub fn new(r: u8, g: u8, b: u8) -> Color { 63 | Color { 64 | r: r, 65 | g: g, 66 | b: b, 67 | _unused: 0, 68 | } 69 | } 70 | } 71 | 72 | #[cfg(test)] 73 | mod test { 74 | use std::time::Duration; 75 | use super::DurationExt; 76 | 77 | #[test] 78 | fn test_from_nanos() { 79 | // 120 seconds 80 | let nanos: u64 = 1_000_000_000 * 120; 81 | let duration = Duration::from_nanos(nanos); 82 | assert_eq!(duration.as_secs(), 120); 83 | assert_eq!(duration.subsec_nanos(), 0); 84 | } 85 | 86 | #[test] 87 | fn test_from_nanos_2() { 88 | let nanos: u64 = 3_000_000_000 + 64; 89 | let duration = Duration::from_nanos(nanos); 90 | 91 | assert_eq!(duration.as_secs(), 3); 92 | assert_eq!(duration.subsec_nanos(), 64); 93 | } 94 | 95 | #[test] 96 | fn test_to_seconds() { 97 | let duration = Duration::new(3, 500_000_000); 98 | let secs = duration.seconds(); 99 | assert_eq!(secs, 3.5); 100 | } 101 | 102 | #[test] 103 | fn test_to_millis() { 104 | let duration = Duration::new(0, 500_000_000); 105 | let millis = duration.millis(); 106 | assert_eq!(millis, 500.0); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/files/filemanager.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::io::prelude::*; 3 | use std::fs::File; 4 | use std::path::{Path, PathBuf}; 5 | 6 | pub type FileHandle = usize; 7 | 8 | /// The file manager is the low-level interface for dealing with file IO. 9 | /// It's mainly going to be used to open and read PAK files. 10 | #[derive(Debug, Default)] 11 | pub struct FileManager { 12 | open_files: Vec, 13 | filenames: Vec, 14 | } 15 | 16 | impl FileManager { 17 | pub fn new() -> FileManager { 18 | FileManager { 19 | open_files: vec![], 20 | filenames: vec![], 21 | } 22 | } 23 | 24 | /// Opens a file for reading, puts its file descriptor on the open files list 25 | /// and returns a handle identifying that file. 26 | pub fn open_read

(&mut self, path: P) -> io::Result 27 | where P: AsRef 28 | { 29 | let pb = path.as_ref().to_path_buf(); 30 | let file = try!(File::open(path)); 31 | self.open_files.push(file); 32 | self.filenames.push(pb); 33 | 34 | Ok(self.open_files.len() - 1) 35 | } 36 | 37 | /// Opens a file for writing. 38 | pub fn open_write

(&mut self, path: P) -> io::Result 39 | where P: AsRef 40 | { 41 | // TODO try out ? operator 42 | let pb = path.as_ref().to_path_buf(); 43 | let file = try!(File::create(path)); 44 | self.open_files.push(file); 45 | self.filenames.push(pb); 46 | 47 | Ok(self.open_files.len() - 1) 48 | } 49 | 50 | /// Closes the file associated with the given file handle. 51 | pub fn close(&mut self, handle: FileHandle) { 52 | // Since we're deleting the element from the vector, the element drops 53 | // out of scope in this function, which also closes the file handle. 54 | // RAII is great. 55 | self.open_files.remove(handle); 56 | self.filenames.remove(handle); 57 | } 58 | 59 | /// See `std::io::Seek::seek`. 60 | pub fn seek(&mut self, handle: FileHandle, pos: io::SeekFrom) -> io::Result { 61 | self.open_files[handle].seek(pos) 62 | } 63 | 64 | /// See `std::io::Read#read`. 65 | pub fn read(&mut self, handle: FileHandle, buffer: &mut [u8]) -> io::Result { 66 | let mut file = &self.open_files[handle]; 67 | file.read(buffer) 68 | } 69 | 70 | pub fn read_to_end(&mut self, handle: FileHandle, buffer: &mut Vec) -> io::Result { 71 | self.open_files[handle].read_to_end(buffer) 72 | } 73 | 74 | pub fn write(&mut self, handle: FileHandle, source: &[u8]) -> io::Result { 75 | self.open_files[handle].write(source) 76 | } 77 | 78 | pub fn write_all(&mut self, handle: FileHandle, source: &[u8]) -> io::Result<()> { 79 | self.open_files[handle].write_all(source) 80 | } 81 | 82 | pub fn close_all(&mut self) { 83 | self.open_files.clear(); 84 | self.filenames.clear(); 85 | } 86 | 87 | pub fn filename(&self, handle: FileHandle) -> Option<&str> { 88 | self.filenames.get(handle).and_then(|f| f.file_name().and_then(|g| g.to_str())) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/util/vector.rs: -------------------------------------------------------------------------------- 1 | use std::ops; 2 | 3 | #[derive(Debug, Copy, Clone, PartialEq)] 4 | pub struct Vec2 { 5 | pub x: f32, 6 | pub y: f32, 7 | } 8 | 9 | impl Vec2 { 10 | pub fn new(x: f32, y: f32) -> Vec2 { 11 | Vec2 { x: x, y: y } 12 | } 13 | 14 | pub fn dot(self, other: Vec2) -> f32 { 15 | self.x * other.x + self.y * other.y 16 | } 17 | } 18 | 19 | impl ops::Add for Vec2 { 20 | type Output = Vec2; 21 | 22 | fn add(self, other: Vec2) -> Vec2 { 23 | Vec2 { 24 | x: self.x + other.x, 25 | y: self.y + other.y, 26 | } 27 | } 28 | } 29 | 30 | impl ops::Mul for Vec2 { 31 | type Output = Vec2; 32 | 33 | fn mul(self, t: f32) -> Vec2 { 34 | Vec2 { 35 | x: self.x * t, 36 | y: self.y * t, 37 | } 38 | } 39 | } 40 | 41 | #[derive(Debug, Copy, Clone, PartialEq)] 42 | pub struct Vec3 { 43 | pub x: f32, 44 | pub y: f32, 45 | pub z: f32, 46 | } 47 | 48 | impl Vec3 { 49 | pub fn new(x: f32, y: f32, z: f32) -> Vec3 { 50 | Vec3 { x: x, y: y, z: z } 51 | } 52 | 53 | pub fn dot(self, other: Vec3) -> f32 { 54 | self.x * other.x + self.y * other.y + self.z * other.z 55 | } 56 | } 57 | 58 | impl ops::Add for Vec3 { 59 | type Output = Vec3; 60 | 61 | fn add(self, other: Vec3) -> Vec3 { 62 | Vec3 { 63 | x: self.x + other.x, 64 | y: self.y + other.y, 65 | z: self.z + other.z, 66 | } 67 | } 68 | } 69 | 70 | impl ops::Mul for Vec3 { 71 | type Output = Vec3; 72 | 73 | fn mul(self, t: f32) -> Vec3 { 74 | Vec3 { 75 | x: self.x * t, 76 | y: self.y * t, 77 | z: self.z * t, 78 | } 79 | } 80 | } 81 | 82 | #[cfg(test)] 83 | mod test { 84 | use super::{Vec2, Vec3}; 85 | 86 | #[test] 87 | fn test_vec2_add() { 88 | let v1 = Vec2::new(5.0, 2.0); 89 | let v2 = Vec2::new(5.0, 3.0); 90 | let result = Vec2::new(10.0, 5.0); 91 | assert_eq!(v1 + v2, result); 92 | } 93 | 94 | #[test] 95 | fn test_vec2_mul() { 96 | let v1 = Vec2::new(5.0, 2.0); 97 | let t = 2.0; 98 | let result = Vec2::new(10.0, 4.0); 99 | assert_eq!(v1 * t, result); 100 | } 101 | 102 | #[test] 103 | fn test_vec2_dot() { 104 | let v1 = Vec2::new(5.0, 2.0); 105 | let result = 29.0; 106 | assert_eq!(v1.dot(v1), result); 107 | } 108 | 109 | #[test] 110 | fn test_vec3_add() { 111 | let v1 = Vec3::new(5.0, 3.0, 2.0); 112 | let v2 = Vec3::new(5.0, 2.0, 3.0); 113 | let result = Vec3::new(10.0, 5.0, 5.0); 114 | assert_eq!(v1 + v2, result); 115 | } 116 | 117 | #[test] 118 | fn test_vec3_mul() { 119 | let v1 = Vec3::new(5.0, 3.0, 2.0); 120 | let t = 2.0; 121 | let result = Vec3::new(10.0, 6.0, 4.0); 122 | assert_eq!(v1 * t, result); 123 | } 124 | 125 | #[test] 126 | fn test_vec3_dot() { 127 | let v1 = Vec3::new(5.0, 2.0, 1.0); 128 | let result = 30.0; 129 | assert_eq!(v1.dot(v1), result); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/host.rs: -------------------------------------------------------------------------------- 1 | use sdl2; 2 | use sdl2::video::Window; 3 | use sdl2::event::Event; 4 | use sdl2::EventPump; 5 | use sdl2::keyboard::Keycode; 6 | 7 | use util::{Timer, Options, DurationExt}; 8 | use drawing::Framebuffer; 9 | use files::*; 10 | 11 | use std::{io, ptr}; 12 | use std::io::prelude::*; 13 | 14 | use hprof; 15 | 16 | const DEFAULT_WIDTH: u32 = 800; 17 | const DEFAULT_HEIGHT: u32 = 600; 18 | 19 | pub struct Host { 20 | window: Window, 21 | event_pump: EventPump, 22 | timer: Timer, 23 | framebuffer: Framebuffer, 24 | options: Options, 25 | debug: bool, 26 | paks: PackContainer, 27 | image_bytes: Vec, 28 | } 29 | 30 | impl Default for Host { 31 | fn default() -> Host { 32 | Host::new() 33 | } 34 | } 35 | 36 | impl Host { 37 | pub fn new() -> Host { 38 | let options = Options::new(); 39 | let context = sdl2::init().unwrap(); 40 | let video = context.video().unwrap(); 41 | let width = options.check_param("-width").unwrap_or(DEFAULT_WIDTH); 42 | let height = options.check_param("-height").unwrap_or(DEFAULT_HEIGHT); 43 | let window_builder = video.window("rsquake", width, height); 44 | let window = window_builder.build().unwrap(); 45 | let debug = options.is_set("-debug"); 46 | // Unlock the framerate in debug mode 47 | let timer = Timer::new(debug); 48 | let mut paks = PackContainer::new(); 49 | paks.add_game_directory("Id1").unwrap(); 50 | let image = paks.read("gfx/pause.lmp").unwrap(); 51 | 52 | Host { 53 | window: window, 54 | event_pump: context.event_pump().unwrap(), 55 | timer: timer, 56 | framebuffer: Framebuffer::new(width as usize, height as usize, &mut paks), 57 | options: options, 58 | debug: debug, 59 | paks: paks, 60 | image_bytes: image, 61 | } 62 | } 63 | 64 | fn frame(&mut self, stdout: &mut io::StdoutLock) { 65 | if let Some(timestep) = self.timer.step() { 66 | hprof::start_frame(); 67 | if self.debug { 68 | let fps = (1.0 / timestep.seconds()).round(); 69 | write!(stdout, "\r{} FPS", fps).unwrap(); 70 | } 71 | 72 | self.draw(); 73 | self.swap_buffers(); 74 | hprof::end_frame(); 75 | if self.debug { 76 | hprof::profiler().print_timing(); 77 | } 78 | } 79 | } 80 | 81 | fn draw(&mut self) { 82 | hprof::enter("Host::draw()"); 83 | self.framebuffer.fill(0); 84 | let img = LmpImage::from_bytes(&self.image_bytes).unwrap(); 85 | self.framebuffer.draw_pic(0, 0, &img); 86 | } 87 | 88 | #[cfg(feature="nightly")] 89 | fn swap_buffers(&mut self) { 90 | hprof::enter("Host::swap_buffers()"); 91 | self.framebuffer.swap_buffers(); 92 | { 93 | let mut surface = self.window.surface_mut(&self.event_pump).unwrap(); 94 | let mut pixels = surface.without_lock_mut().unwrap(); 95 | let bytes = self.framebuffer.color_buffer(); 96 | pixels.copy_from_slice(bytes); 97 | } 98 | self.window.update_surface().unwrap(); 99 | } 100 | 101 | #[cfg(not(feature="nightly"))] 102 | fn swap_buffers(&mut self) { 103 | hprof::enter("Host::swap_buffers()"); 104 | self.framebuffer.swap_buffers(); 105 | { 106 | let mut surface = self.window.surface_mut(&self.event_pump).unwrap(); 107 | let mut pixels = surface.without_lock_mut().unwrap(); 108 | let bytes = self.framebuffer.color_buffer(); 109 | let src = bytes.as_ptr(); 110 | let dest = pixels.as_mut_ptr(); 111 | unsafe { 112 | ptr::copy_nonoverlapping(src, dest, bytes.len()); 113 | } 114 | } 115 | self.window.update_surface().unwrap(); 116 | } 117 | 118 | pub fn run(&mut self) { 119 | let stdout = io::stdout(); 120 | let mut lock = stdout.lock(); 121 | if self.debug { 122 | println!(""); 123 | } 124 | 'main: loop { 125 | let h = hprof::enter("Event loop"); 126 | for event in self.event_pump.poll_iter() { 127 | match event { 128 | Event::Quit { .. } | 129 | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => { 130 | break 'main; 131 | } 132 | _ => {} 133 | } 134 | } 135 | drop(h); 136 | self.frame(&mut lock); 137 | } 138 | if self.debug { 139 | println!(""); 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [root] 2 | name = "quake-rs" 3 | version = "0.1.0" 4 | dependencies = [ 5 | "byteorder 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 6 | "clippy 0.0.68 (registry+https://github.com/rust-lang/crates.io-index)", 7 | "hprof 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 8 | "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", 9 | "sdl2 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", 10 | ] 11 | 12 | [[package]] 13 | name = "bitflags" 14 | version = "0.6.0" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | 17 | [[package]] 18 | name = "byteorder" 19 | version = "0.5.1" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | 22 | [[package]] 23 | name = "clippy" 24 | version = "0.0.68" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | dependencies = [ 27 | "quine-mc_cluskey 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 28 | "regex-syntax 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 29 | "semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 30 | "toml 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)", 31 | "unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 32 | ] 33 | 34 | [[package]] 35 | name = "clock_ticks" 36 | version = "0.1.0" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | dependencies = [ 39 | "libc 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 40 | ] 41 | 42 | [[package]] 43 | name = "hprof" 44 | version = "0.1.3" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | dependencies = [ 47 | "clock_ticks 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 48 | "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 49 | ] 50 | 51 | [[package]] 52 | name = "lazy_static" 53 | version = "0.2.1" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | 56 | [[package]] 57 | name = "libc" 58 | version = "0.1.12" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | 61 | [[package]] 62 | name = "libc" 63 | version = "0.2.11" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | 66 | [[package]] 67 | name = "log" 68 | version = "0.3.6" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | 71 | [[package]] 72 | name = "nom" 73 | version = "1.2.3" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | 76 | [[package]] 77 | name = "num" 78 | version = "0.1.32" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | dependencies = [ 81 | "num-bigint 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", 82 | "num-complex 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", 83 | "num-integer 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", 84 | "num-iter 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", 85 | "num-rational 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", 86 | "num-traits 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", 87 | ] 88 | 89 | [[package]] 90 | name = "num-bigint" 91 | version = "0.1.32" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | dependencies = [ 94 | "num-integer 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", 95 | "num-traits 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", 96 | "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", 97 | "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", 98 | ] 99 | 100 | [[package]] 101 | name = "num-complex" 102 | version = "0.1.32" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | dependencies = [ 105 | "num-traits 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", 106 | "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", 107 | ] 108 | 109 | [[package]] 110 | name = "num-integer" 111 | version = "0.1.32" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | dependencies = [ 114 | "num-traits 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", 115 | ] 116 | 117 | [[package]] 118 | name = "num-iter" 119 | version = "0.1.32" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | dependencies = [ 122 | "num-integer 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", 123 | "num-traits 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", 124 | ] 125 | 126 | [[package]] 127 | name = "num-rational" 128 | version = "0.1.32" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | dependencies = [ 131 | "num-bigint 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", 132 | "num-integer 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", 133 | "num-traits 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", 134 | "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", 135 | ] 136 | 137 | [[package]] 138 | name = "num-traits" 139 | version = "0.1.32" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | 142 | [[package]] 143 | name = "quine-mc_cluskey" 144 | version = "0.2.2" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | 147 | [[package]] 148 | name = "rand" 149 | version = "0.3.14" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | dependencies = [ 152 | "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 153 | ] 154 | 155 | [[package]] 156 | name = "regex-syntax" 157 | version = "0.3.1" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | 160 | [[package]] 161 | name = "rustc-serialize" 162 | version = "0.3.19" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | 165 | [[package]] 166 | name = "sdl2" 167 | version = "0.19.0" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | dependencies = [ 170 | "bitflags 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 171 | "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 172 | "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 173 | "num 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", 174 | "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", 175 | "sdl2-sys 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", 176 | ] 177 | 178 | [[package]] 179 | name = "sdl2-sys" 180 | version = "0.19.0" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | dependencies = [ 183 | "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 184 | ] 185 | 186 | [[package]] 187 | name = "semver" 188 | version = "0.2.3" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | dependencies = [ 191 | "nom 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 192 | ] 193 | 194 | [[package]] 195 | name = "toml" 196 | version = "0.1.30" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | dependencies = [ 199 | "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", 200 | ] 201 | 202 | [[package]] 203 | name = "unicode-normalization" 204 | version = "0.1.2" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | 207 | -------------------------------------------------------------------------------- /src/drawing/framebuffer.rs: -------------------------------------------------------------------------------- 1 | use drawing::bezier::BezierCurve; 2 | use util::Color; 3 | use files::*; 4 | use hprof; 5 | 6 | pub struct Palette { 7 | colors: [Color; 256], 8 | } 9 | 10 | impl Palette { 11 | pub fn new(pack: &mut PackContainer) -> PackResult { 12 | let bytes = try!(pack.read("gfx/palette.lmp")); 13 | let mut buf = [Color::default(); 256]; 14 | for (i, b) in bytes.chunks(3).enumerate() { 15 | let (r, g, b) = (b[2], b[1], b[0]); 16 | buf[i] = Color::new(r, g, b); 17 | } 18 | 19 | Ok(Palette { colors: buf }) 20 | } 21 | 22 | pub fn get(&self, c: u8) -> Color { 23 | self.colors[c as usize] 24 | } 25 | } 26 | 27 | pub struct Framebuffer { 28 | /// Buffer of byte values, indexes into the palette. 29 | /// Size is width * height, treated like a fixed-size array. 30 | pixels: Vec, 31 | width: usize, 32 | height: usize, 33 | /// Buffer of colors as they will be rendered to the screen. 34 | /// Size is width * height * 4 (32 bpp), also treated like a fixed-size array. 35 | color_buffer: Vec, 36 | pub palette: Palette, 37 | } 38 | 39 | impl Framebuffer { 40 | pub fn new(width: usize, height: usize, pack: &mut PackContainer) -> Framebuffer { 41 | Framebuffer { 42 | pixels: vec![0; height * width], 43 | width: width as usize, 44 | height: height as usize, 45 | color_buffer: vec![0; height * width * 4], 46 | palette: Palette::new(pack).unwrap(), 47 | } 48 | } 49 | 50 | #[inline] 51 | fn index(&self, y: usize, x: usize) -> usize { 52 | x * self.width + y 53 | } 54 | 55 | #[inline] 56 | pub fn set(&mut self, x: usize, y: usize, color: u8) { 57 | let i = self.index(x, y); 58 | self.pixels[i] = color; 59 | } 60 | 61 | #[inline] 62 | pub fn get(&self, x: usize, y: usize) -> u8 { 63 | self.pixels[self.index(x, y)] 64 | } 65 | 66 | pub fn fill(&mut self, color: u8) { 67 | for v in &mut self.pixels { 68 | *v = color; 69 | } 70 | } 71 | 72 | pub fn width(&self) -> usize { 73 | self.width 74 | } 75 | 76 | pub fn height(&self) -> usize { 77 | self.height 78 | } 79 | 80 | /// Copies the values currently in the `pixels` array to the 81 | /// color buffer and translates them through the palette. 82 | pub fn swap_buffers(&mut self) { 83 | hprof::enter("Framebuffer::swap_buffers"); 84 | let mut i = 0; 85 | for px in &self.pixels { 86 | let color = self.palette.get(*px); 87 | self.color_buffer[i] = color.r; 88 | self.color_buffer[i + 1] = color.g; 89 | self.color_buffer[i + 2] = color.b; 90 | self.color_buffer[i + 3] = 0; 91 | 92 | i += 4; 93 | } 94 | } 95 | 96 | pub fn pixels(&self) -> &[u8] { 97 | &self.pixels 98 | } 99 | 100 | pub fn color_buffer(&self) -> &[u8] { 101 | &self.color_buffer 102 | } 103 | 104 | /// Bresenham line drawing 105 | pub fn line(&mut self, x0: usize, y0: usize, x1: usize, y1: usize, color: u8) { 106 | let dx = (x1 - x0) as i32; 107 | let dy = (y1 - y0) as i32; 108 | let mut d = 2 * dy - dx; 109 | 110 | self.set(x0, y0, color); 111 | let mut y = y0; 112 | 113 | if d > 0 { 114 | y += 1; 115 | d -= 2 * dx; 116 | } 117 | 118 | for x in x0 + 1..x1 { 119 | self.set(x, y, color); 120 | d += 2 * dy; 121 | if d > 0 { 122 | y += 1; 123 | d -= 2 * dx; 124 | } 125 | } 126 | } 127 | 128 | pub fn rect(&mut self, x: usize, y: usize, width: usize, height: usize, color: u8) { 129 | let xm = x + width + 1; 130 | let ym = y + height + 1; 131 | for h in y..ym { 132 | for w in x..xm { 133 | self.set(w, h, color); 134 | } 135 | } 136 | } 137 | 138 | pub fn bezier(&mut self, curve: BezierCurve, n: usize, color: u8) { 139 | for p in curve.approximate(n) { 140 | self.set(p.x as usize, p.y as usize, color); 141 | } 142 | } 143 | 144 | pub fn draw_pic(&mut self, x_pos: usize, y_pos: usize, image: &LmpImage) { 145 | // Not the fastst solution, probably 146 | for j in 0..image.height() { 147 | for i in 0..image.width() { 148 | let x = i as usize + x_pos; 149 | let y = j as usize + y_pos; 150 | self.set(x, y, image.get(i, j)); 151 | } 152 | } 153 | } 154 | 155 | pub fn draw_gradient(&mut self, start: u8, end: u8) { 156 | assert!(end > start); 157 | let w = self.width; 158 | let h = self.height; 159 | let s = start as f32; 160 | let d = (end - start) as f32; 161 | 162 | for y in 0..h { 163 | for x in 0..w { 164 | let p = x as f32 / w as f32; 165 | let c = s + p * d; 166 | self.set(x, y, c as u8); 167 | } 168 | } 169 | } 170 | } 171 | 172 | #[cfg(test)] 173 | mod tests { 174 | use super::*; 175 | use files::*; 176 | 177 | #[test] 178 | fn create_framebuffer() { 179 | let h = 20; 180 | let w = 16; 181 | let mut pc = PackContainer::new(); 182 | pc.read_pack("Id1/PAK0.PAK").unwrap(); 183 | let mut fb = Framebuffer::new(w, h, &mut pc); 184 | for i in 0..w { 185 | for j in 0..h { 186 | fb.set(i, j, 20); 187 | } 188 | } 189 | 190 | assert_eq!(fb.get(w - 1, h - 1), 20); 191 | } 192 | 193 | #[test] 194 | fn to_bytes() { 195 | let w = 200; 196 | let sz = w * w * 4; 197 | let palette_index = 4; 198 | let mut pc = PackContainer::new(); 199 | pc.read_pack("Id1/PAK0.PAK").unwrap(); 200 | let mut fb = Framebuffer::new(w, w, &mut pc); 201 | fb.fill(palette_index); 202 | let p = fb.palette.get(palette_index); 203 | fb.swap_buffers(); 204 | let bytes = fb.color_buffer(); 205 | assert_eq!(bytes.len(), sz); 206 | for b in bytes.chunks(4) { 207 | assert_eq!(b[0], p.r); 208 | assert_eq!(b[1], p.g); 209 | assert_eq!(b[2], p.b); 210 | } 211 | } 212 | 213 | #[test] 214 | fn test_set() { 215 | let w = 20; 216 | let h = 16; 217 | let mut pc = PackContainer::new(); 218 | pc.read_pack("Id1/PAK0.PAK").unwrap(); 219 | let mut fb = Framebuffer::new(w, h, &mut pc); 220 | 221 | for y in 0..h { 222 | for x in 0..w { 223 | fb.set(x, y, 3); 224 | } 225 | } 226 | 227 | for p in fb.pixels() { 228 | assert_eq!(*p, 3); 229 | } 230 | } 231 | } 232 | 233 | #[cfg(feature="nighty")] 234 | mod bench { 235 | use test::Bencher; 236 | use super::*; 237 | use files::*; 238 | 239 | const WIDTH: usize = 800; 240 | const HEIGHT: usize = 600; 241 | 242 | #[bench] 243 | fn bench_bresenham(b: &mut Bencher) { 244 | let mut pc = PackContainer::new(); 245 | pc.read_pack("Id1/PAK0.PAK").unwrap(); 246 | let mut fb = Framebuffer::new(WIDTH, HEIGHT, &mut pc); 247 | b.iter(|| { 248 | fb.line(0, 0, 799, 599, 12); 249 | }); 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/files/packfile.rs: -------------------------------------------------------------------------------- 1 | use files::*; 2 | use byteorder::{ReadBytesExt, LittleEndian}; 3 | use std::{io, fmt, str}; 4 | use std::path::Path; 5 | use std::io::prelude::*; 6 | use util; 7 | 8 | #[derive(Debug)] 9 | pub enum PackError { 10 | IoError(io::Error), 11 | UnknownContentFileName, 12 | UnknownPakFileName, 13 | } 14 | 15 | impl From for PackError { 16 | fn from(err: io::Error) -> PackError { 17 | PackError::IoError(err) 18 | } 19 | } 20 | 21 | pub type PackResult = Result; 22 | 23 | /// Header of the PAK file format. Contains 4 bytes ("PACK") identifying the 24 | /// file, the offset of the directory contents and the length of the directory. 25 | #[derive(Debug)] 26 | struct Header { 27 | magic: &'static [u8], 28 | directory_offset: i32, 29 | directory_length: i32, 30 | } 31 | 32 | impl Header { 33 | pub fn read(file_mgr: &mut FileManager, handle: FileHandle) -> io::Result

{ 34 | let mut buf = [0; 12]; 35 | try!(file_mgr.read(handle, &mut buf)); 36 | let mut rdr = io::Cursor::new(buf); 37 | let mut magic = [0; 4]; 38 | try!(rdr.read_exact(&mut magic)); 39 | assert_eq!(&magic, b"PACK"); 40 | let off = try!(rdr.read_i32::()); 41 | let len = try!(rdr.read_i32::()); 42 | 43 | Ok(Header { 44 | magic: b"PACK", 45 | directory_offset: off, 46 | directory_length: len, 47 | }) 48 | } 49 | 50 | pub fn size() -> usize { 51 | 12 52 | } 53 | } 54 | 55 | /// A directory entry in the PAK file, identifying a single content file 56 | /// in the PAK. Contains a name (56 bytes), an offset from the start of the file 57 | /// (4 bytes) and the size of the file (4 bytes). 58 | struct PackFile { 59 | name: [u8; 56], 60 | position: i32, 61 | length: i32, 62 | } 63 | 64 | impl PackFile { 65 | pub fn read(file_mgr: &mut FileManager, handle: FileHandle) -> io::Result { 66 | let mut buffer = vec![0; 64]; 67 | try!(file_mgr.read(handle, &mut buffer)); 68 | let mut rdr = io::Cursor::new(buffer); 69 | let mut name = [0; 56]; 70 | try!(rdr.read_exact(&mut name)); 71 | 72 | let pos = try!(rdr.read_i32::()); 73 | let length = try!(rdr.read_i32::()); 74 | 75 | Ok(PackFile { 76 | name: name, 77 | position: pos, 78 | length: length, 79 | }) 80 | } 81 | 82 | pub fn size() -> usize { 83 | 56 + 4 + 4 84 | } 85 | 86 | pub fn name_str(&self) -> &str { 87 | let name_bytes = &self.name; 88 | let nul = name_bytes.iter().position(|b| *b == 0).unwrap(); 89 | let valid = &name_bytes[..nul]; 90 | str::from_utf8(valid).unwrap() 91 | } 92 | } 93 | 94 | impl fmt::Debug for PackFile { 95 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 96 | write!(f, 97 | "PackFile {{ name: {}, position: {}, length: {} }}", 98 | self.name_str(), 99 | self.position, 100 | self.length) 101 | } 102 | } 103 | 104 | /// A PAK file. Has a name, a list of directory entries and an associated file handle. 105 | #[derive(Debug)] 106 | pub struct Pack { 107 | name: String, 108 | files: Vec, 109 | handle: FileHandle, 110 | } 111 | 112 | impl Pack { 113 | /// Opens a PAK file for reading with the supplied FileManager and file handle. 114 | pub fn open(file_mgr: &mut FileManager, handle: FileHandle, name: String) -> io::Result { 115 | let header = try!(Header::read(file_mgr, handle)); 116 | try!(file_mgr.seek(handle, io::SeekFrom::Start(header.directory_offset as u64))); 117 | let file_count = header.directory_length as usize / PackFile::size(); 118 | let mut files = vec![]; 119 | 120 | for _ in 0..file_count { 121 | let file = try!(PackFile::read(file_mgr, handle)); 122 | files.push(file); 123 | } 124 | 125 | Ok(Pack { 126 | name: name, 127 | files: files, 128 | handle: handle, 129 | }) 130 | } 131 | 132 | /// Reads the contents of a file within this PAK and returns it as a `Vec`. 133 | pub fn read_file(&self, name: &str, file_mgr: &mut FileManager) -> PackResult> { 134 | let file = self.files.iter().find(|f| f.name_str() == name); 135 | match file { 136 | Some(f) => { 137 | try!(file_mgr.seek(self.handle, io::SeekFrom::Start(f.position as u64))); 138 | let mut buf = vec![0; f.length as usize]; 139 | let mut bytes_read = 0; 140 | while bytes_read < f.length { 141 | bytes_read += try!(file_mgr.read(self.handle, &mut buf)) as i32; 142 | } 143 | Ok(buf) 144 | } 145 | None => Err(PackError::UnknownContentFileName), 146 | } 147 | } 148 | } 149 | 150 | /// A structure containing one or more PAK files and a file manager, for convenience. 151 | #[derive(Default, Debug)] 152 | pub struct PackContainer { 153 | files: Vec, 154 | file_mgr: FileManager, 155 | } 156 | 157 | impl PackContainer { 158 | pub fn new() -> PackContainer { 159 | PackContainer { 160 | files: vec![], 161 | file_mgr: FileManager::new(), 162 | } 163 | } 164 | 165 | pub fn file_mgr(&mut self) -> &mut FileManager { 166 | &mut self.file_mgr 167 | } 168 | 169 | pub fn get(&self, idx: usize) -> &Pack { 170 | &self.files[idx] 171 | } 172 | 173 | /// Looks through the PAK files and tries to find the given file and reads it into a buffer. 174 | pub fn read(&mut self, filename: &str) -> PackResult> { 175 | for pak in &self.files { 176 | let result = pak.read_file(filename, &mut self.file_mgr); 177 | match result { 178 | r@Ok(_) => return r, 179 | Err(_) => continue, 180 | } 181 | } 182 | Err(PackError::UnknownContentFileName) 183 | } 184 | 185 | /// Opens a PAK file, reads the contents of its directory and appends the contents to the list 186 | /// of PAK files of this PackContainer. 187 | pub fn read_pack

(&mut self, path: P) -> PackResult<()> 188 | where P: AsRef 189 | { 190 | let handle = try!(self.file_mgr.open_read(path)); 191 | let name = try!(self.file_mgr.filename(handle).ok_or(PackError::UnknownContentFileName)) 192 | .into(); 193 | let pack = try!(Pack::open(&mut self.file_mgr, handle, name)); 194 | self.files.push(pack); 195 | Ok(()) 196 | } 197 | 198 | pub fn add_game_directory

(&mut self, path: P) -> PackResult<()> 199 | where P: AsRef 200 | { 201 | // Some arbitrary number, I'm not sure where Quake starts counting 202 | const HIGHEST_PAK_NUMBER: isize = 16; 203 | 204 | let mut i = HIGHEST_PAK_NUMBER; 205 | while i >= 0 { 206 | let filename = path.as_ref().join(format!("PAK{}.PAK", i)); 207 | util::ignore(self.read_pack(filename)); 208 | i -= 1; 209 | } 210 | Ok(()) 211 | } 212 | } 213 | 214 | #[cfg(test)] 215 | mod test { 216 | use std::io; 217 | use files::FileManager; 218 | use super::{Header, Pack, PackFile, PackContainer}; 219 | 220 | const PAK0: &'static str = "Id1/PAK0.PAK"; 221 | 222 | #[test] 223 | fn read_header() { 224 | let mut file_mgr = FileManager::new(); 225 | file_mgr.open_read(PAK0).unwrap(); 226 | let header = Header::read(&mut file_mgr, 0).unwrap(); 227 | assert_eq!(header.directory_length, 21696); 228 | assert_eq!(header.directory_offset, 18254423); 229 | } 230 | 231 | #[test] 232 | fn read_packfile() { 233 | let mut file_mgr = FileManager::new(); 234 | file_mgr.open_read(PAK0).unwrap(); 235 | let header = Header::read(&mut file_mgr, 0).unwrap(); 236 | file_mgr.seek(0, io::SeekFrom::Start(header.directory_offset as u64)).unwrap(); 237 | let packfile = PackFile::read(&mut file_mgr, 0).unwrap(); 238 | assert_eq!(packfile.name_str(), "sound/items/r_item1.wav"); 239 | } 240 | 241 | #[test] 242 | fn read_whole_pack() { 243 | let mut file_mgr = FileManager::new(); 244 | file_mgr.open_read(PAK0).unwrap(); 245 | 246 | let pak0 = Pack::open(&mut file_mgr, 0, "PAK0.PAK".into()).unwrap(); 247 | assert_eq!(pak0.files.len(), 339); 248 | } 249 | 250 | #[test] 251 | fn read_file_from_pack() { 252 | let mut file_mgr = FileManager::new(); 253 | let h = file_mgr.open_read(PAK0).unwrap(); 254 | 255 | let pak0 = Pack::open(&mut file_mgr, h, "PAK0.PAK".into()).unwrap(); 256 | let file = pak0.read_file("gfx/palette.lmp", &mut file_mgr).unwrap(); 257 | assert_eq!(file[0], 0); 258 | assert_eq!(file[1], 0); 259 | assert_eq!(file[2], 0); 260 | assert_eq!(file[3], 15); 261 | assert_eq!(file[4], 15); 262 | assert_eq!(file[5], 15); 263 | } 264 | 265 | #[test] 266 | fn pack_container() { 267 | let mut pc = PackContainer::new(); 268 | pc.read_pack("Id1/PAK0.PAK").unwrap(); 269 | let file = pc.read("gfx/palette.lmp").unwrap(); 270 | assert_eq!(file[0], 0); 271 | assert_eq!(file[1], 0); 272 | assert_eq!(file[2], 0); 273 | assert_eq!(file[3], 15); 274 | assert_eq!(file[4], 15); 275 | assert_eq!(file[5], 15); 276 | } 277 | 278 | #[test] 279 | fn game_directory() { 280 | let mut pc = PackContainer::new(); 281 | pc.add_game_directory("Id1").unwrap(); 282 | let file = pc.read("gfx/palette.lmp").unwrap(); 283 | assert_eq!(file[0], 0); 284 | assert_eq!(file[1], 0); 285 | assert_eq!(file[2], 0); 286 | assert_eq!(file[3], 15); 287 | assert_eq!(file[4], 15); 288 | assert_eq!(file[5], 15); 289 | } 290 | } 291 | --------------------------------------------------------------------------------