diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..15f5f25 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target + +# This may change as we write a UI for the app +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..61087a1 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "wad-reader" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/WADs/doom1.wad b/WADs/doom1.wad new file mode 100644 index 0000000..1a58f66 Binary files /dev/null and b/WADs/doom1.wad differ diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..d0a7a14 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,8 @@ +#[cfg(test)] +mod tests; + +mod lumps; +mod utils; +mod wad; + +pub use wad::WADFile; diff --git a/src/lumps/mod.rs b/src/lumps/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/tests/helpers.rs b/src/tests/helpers.rs new file mode 100644 index 0000000..2f0e41a --- /dev/null +++ b/src/tests/helpers.rs @@ -0,0 +1,15 @@ +use crate::utils::*; + +#[test] +pub fn valid_wad() { + let wad_path = String::from("WADs/doom1.wad"); + + assert!(validate_wad(wad_path.as_str()).unwrap_or(false)); +} + +#[test] +pub fn wad_path_invalid() { + let wad_path = String::from("WADs/invalid.wad"); + + assert!(!validate_wad(wad_path.as_str()).unwrap_or(false)); +} diff --git a/src/tests/mod.rs b/src/tests/mod.rs new file mode 100644 index 0000000..ea51371 --- /dev/null +++ b/src/tests/mod.rs @@ -0,0 +1,5 @@ +#[cfg(test)] +mod wad; + +#[cfg(test)] +mod helpers; diff --git a/src/tests/wad.rs b/src/tests/wad.rs new file mode 100644 index 0000000..aa4eaeb --- /dev/null +++ b/src/tests/wad.rs @@ -0,0 +1,36 @@ +use crate::WADFile; + +#[test] +pub fn successful_wad_id() { + let wad_file = WADFile::new(String::from("WADs/doom1.wad")); + + assert_eq!(wad_file.wad_id, "IWAD"); +} + +#[test] +pub fn correct_lumps() { + let wad_file = WADFile::new(String::from("WADs/doom1.wad")); + + assert_eq!(wad_file.num_lumps, 1264); +} + +#[test] +pub fn correct_dir_size() { + let wad_file = WADFile::new(String::from("WADs/doom1.wad")); + + assert_eq!(wad_file.directory.len(), 1264); +} + +#[test] +pub fn correct_lump_name() { + let wad_file = WADFile::new(String::from("WADs/doom1.wad")); + + assert_eq!(wad_file.directory[0].name, "PLAYPAL"); +} + +#[test] +pub fn read_level_lump() { + let wad_file = WADFile::new(String::from("WADs/doom1.wad")); + + assert_eq!(wad_file.directory[6].name, "E1M1"); +} diff --git a/src/utils/error.rs b/src/utils/error.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/helpers.rs b/src/utils/helpers.rs new file mode 100644 index 0000000..99ae264 --- /dev/null +++ b/src/utils/helpers.rs @@ -0,0 +1,43 @@ +use std::{ + fs::File, + io::{self, Read}, + path::Path, +}; + +/// Validates a WAD file to make sure that it is a legitimate file +/// +/// Parameters: +/// - path: &str - Path to the WAD to validate +pub fn validate_wad(path: &str) -> io::Result { + let wad_file = Path::new(path); + + // Check to see if the WAD exists + if !(wad_file.exists()) { + // Return back false because we didn't pass a valid file + return Ok(false); + } + + // If the file exists open it and read the first 4 bytes + // of the file and see if we get "IWAD" or "PWAD" + let mut file = File::open(wad_file)?; + let mut magic = [0u8; 4]; + file.read_exact(&mut magic)?; + + // Now we return based on what we found + Ok(magic == *b"IWAD" || magic == *b"PWAD") +} + +pub fn read_ascii(bytes: &[u8]) -> String { + std::str::from_utf8(&bytes[..bytes.len()]) + .unwrap_or("") + .trim_end_matches('\0') + .to_string() +} + +pub fn read_u32_le(bytes: &[u8]) -> u32 { + if bytes.len() < 4 { + 0 + } else { + u32::from_le_bytes(bytes[..4].try_into().unwrap()) + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..1873480 --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,6 @@ +mod error; +mod helpers; + +pub use helpers::read_ascii; +pub use helpers::read_u32_le; +pub use helpers::validate_wad; diff --git a/src/wad/directory.rs b/src/wad/directory.rs new file mode 100644 index 0000000..14654da --- /dev/null +++ b/src/wad/directory.rs @@ -0,0 +1,5 @@ +pub struct Directory { + pub filepos: u32, + pub size: u32, + pub name: String, +} diff --git a/src/wad/header.rs b/src/wad/header.rs new file mode 100644 index 0000000..22ac646 --- /dev/null +++ b/src/wad/header.rs @@ -0,0 +1,21 @@ +pub struct Header { + pub wad_id: String, + pub num_lumps: u32, + pub dir_offset: u32, +} + +use crate::utils::{read_ascii, read_u32_le}; + +impl Header { + pub fn read_data(data: &[u8]) -> Self { + let id = read_ascii(&data[..4]); + let lumps = read_u32_le(&data[4..8]); + let offset = read_u32_le(&data[8..12]); + + Self { + wad_id: id, + num_lumps: lumps, + dir_offset: offset, + } + } +} diff --git a/src/wad/mod.rs b/src/wad/mod.rs new file mode 100644 index 0000000..f9fd273 --- /dev/null +++ b/src/wad/mod.rs @@ -0,0 +1,5 @@ +mod directory; +mod header; +mod wadfile; + +pub use wadfile::WADFile; diff --git a/src/wad/wadfile.rs b/src/wad/wadfile.rs new file mode 100644 index 0000000..941e554 --- /dev/null +++ b/src/wad/wadfile.rs @@ -0,0 +1,54 @@ +use std::{fs::File, io::Read, path::Path}; + +use super::{directory::Directory, header::Header}; +use crate::utils::{read_ascii, read_u32_le, validate_wad}; + +pub struct WADFile { + pub path: String, + pub wad_id: String, + pub num_lumps: u32, + pub dir_offset: u32, + pub directory: Vec, + pub wad_data: Vec, +} + +impl WADFile { + pub fn new(path: String) -> Self { + if !(validate_wad(path.as_str()).unwrap()) { + panic!(); + } + + let file_path = Path::new(path.as_str()); + let mut wad_file = File::open(file_path).unwrap(); + let file_size = wad_file.metadata().unwrap().len() as usize; + let mut file_buffer: Vec = Vec::with_capacity(file_size); + + wad_file.read_to_end(&mut file_buffer).unwrap(); + + let wad_header = Header::read_data(&file_buffer[0..12]); + + let mut wad_dir: Vec = Vec::with_capacity(wad_header.num_lumps as usize); + let offset = wad_header.dir_offset; + + for entry in 0..wad_header.num_lumps { + let startpos = (offset + 16 * entry) as usize; + let filepos = read_u32_le(&file_buffer[startpos..startpos + 4]); + let size = read_u32_le(&file_buffer[startpos + 4..startpos + 8]); + let name = read_ascii(&file_buffer[startpos + 8..startpos + 16]); + wad_dir.push(Directory { + filepos, + name, + size, + }); + } + + Self { + path, + wad_id: wad_header.wad_id, + num_lumps: wad_header.num_lumps, + dir_offset: wad_header.dir_offset, + directory: wad_dir, + wad_data: file_buffer, + } + } +}