#[derive(Debug, Clone)]
pub struct WadHeader {
pub identification: [u8; 4],
pub num_lumps: i32,
pub info_table_ofs: i32,
}
#[derive(Debug, Clone)]
pub struct DirEntry {
pub filepos: i32,
pub size: i32,
pub name: [u8; 8],
}
impl DirEntry {
pub fn name_str(&self) -> String {
let end = self.name.iter().position(|&b| b == 0).unwrap_or(8);
String::from_utf8_lossy(&self.name[..end]).to_uppercase()
}
}
#[derive(Debug, Clone, Copy)]
pub struct RawVertex {
pub x: i16,
pub y: i16,
}
#[derive(Debug, Clone, Copy)]
pub struct RawLineDef {
pub v1: u16,
pub v2: u16,
pub flags: u16,
pub special: u16,
pub tag: u16,
pub right_sidedef: u16,
pub left_sidedef: u16,
}
pub const ML_BLOCKING: u16 = 1;
pub const ML_TWOSIDED: u16 = 4;
pub const ML_DONTPEGTOP: u16 = 8;
pub const ML_DONTPEGBOTTOM: u16 = 16;
pub const ML_SECRET: u16 = 32;
pub const ML_SOUNDBLOCK: u16 = 64;
pub const ML_DONTDRAW: u16 = 128;
pub const ML_MAPPED: u16 = 256;
#[derive(Debug, Clone)]
pub struct RawSideDef {
pub x_offset: i16,
pub y_offset: i16,
pub upper_texture: [u8; 8],
pub lower_texture: [u8; 8],
pub middle_texture: [u8; 8],
pub sector: u16,
}
impl RawSideDef {
pub fn upper_name(&self) -> String {
lump_name_str(&self.upper_texture)
}
pub fn lower_name(&self) -> String {
lump_name_str(&self.lower_texture)
}
pub fn middle_name(&self) -> String {
lump_name_str(&self.middle_texture)
}
}
#[derive(Debug, Clone)]
pub struct RawSector {
pub floor_height: i16,
pub ceiling_height: i16,
pub floor_texture: [u8; 8],
pub ceiling_texture: [u8; 8],
pub light_level: u16,
pub special: u16,
pub tag: u16,
}
impl RawSector {
pub fn floor_name(&self) -> String {
lump_name_str(&self.floor_texture)
}
pub fn ceiling_name(&self) -> String {
lump_name_str(&self.ceiling_texture)
}
}
#[derive(Debug, Clone, Copy)]
pub struct RawSeg {
pub v1: u16,
pub v2: u16,
pub angle: i16,
pub linedef: u16,
pub direction: u16,
pub offset: i16,
}
#[derive(Debug, Clone, Copy)]
pub struct RawSubSector {
pub num_segs: u16,
pub first_seg: u16,
}
#[derive(Debug, Clone, Copy)]
pub struct RawNode {
pub x: i16,
pub y: i16,
pub dx: i16,
pub dy: i16,
pub bbox_right: [i16; 4],
pub bbox_left: [i16; 4],
pub right_child: u16,
pub left_child: u16,
}
pub const NF_SUBSECTOR: u16 = 0x8000;
#[derive(Debug, Clone, Copy)]
pub struct RawThing {
pub x: i16,
pub y: i16,
pub angle: u16,
pub thing_type: u16,
pub flags: u16,
}
pub const MTF_EASY: u16 = 1;
pub const MTF_NORMAL: u16 = 2;
pub const MTF_HARD: u16 = 4;
pub const MTF_AMBUSH: u16 = 8;
pub const MTF_MULTI: u16 = 16;
pub const THING_PLAYER1: u16 = 1;
pub fn lump_name_str(name: &[u8; 8]) -> String {
let end = name.iter().position(|&b| b == 0).unwrap_or(8);
String::from_utf8_lossy(&name[..end]).to_uppercase()
}
pub mod parse {
#[inline]
pub fn i16_le(data: &[u8], offset: usize) -> i16 {
i16::from_le_bytes([data[offset], data[offset + 1]])
}
#[inline]
pub fn u16_le(data: &[u8], offset: usize) -> u16 {
u16::from_le_bytes([data[offset], data[offset + 1]])
}
#[inline]
pub fn i32_le(data: &[u8], offset: usize) -> i32 {
i32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
])
}
#[inline]
pub fn name8(data: &[u8], offset: usize) -> [u8; 8] {
let mut buf = [0u8; 8];
buf.copy_from_slice(&data[offset..offset + 8]);
buf
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lump_name_str_full_name() {
let name = [b'E', b'1', b'M', b'1', 0, 0, 0, 0];
assert_eq!(lump_name_str(&name), "E1M1");
}
#[test]
fn lump_name_str_full_8_chars() {
let name = *b"LINEDEFS";
assert_eq!(lump_name_str(&name), "LINEDEFS");
}
#[test]
fn lump_name_str_all_zeroes() {
let name = [0u8; 8];
assert_eq!(lump_name_str(&name), "");
}
#[test]
fn lump_name_str_lowercase_uppercased() {
let name = [b'f', b'l', b'a', b't', 0, 0, 0, 0];
assert_eq!(lump_name_str(&name), "FLAT");
}
#[test]
fn dir_entry_name_str() {
let entry = DirEntry {
filepos: 0,
size: 100,
name: [b'T', b'H', b'I', b'N', b'G', b'S', 0, 0],
};
assert_eq!(entry.name_str(), "THINGS");
}
#[test]
fn sidedef_texture_names() {
let sd = RawSideDef {
x_offset: 0,
y_offset: 0,
upper_texture: [b'S', b'K', b'Y', b'1', 0, 0, 0, 0],
lower_texture: [b'-', 0, 0, 0, 0, 0, 0, 0],
middle_texture: [b'D', b'O', b'O', b'R', b'1', 0, 0, 0],
sector: 0,
};
assert_eq!(sd.upper_name(), "SKY1");
assert_eq!(sd.lower_name(), "-");
assert_eq!(sd.middle_name(), "DOOR1");
}
#[test]
fn sector_texture_names() {
let sector = RawSector {
floor_height: 0,
ceiling_height: 128,
floor_texture: [b'F', b'L', b'A', b'T', b'1', 0, 0, 0],
ceiling_texture: [b'C', b'E', b'I', b'L', b'3', 0, 0, 0],
light_level: 160,
special: 0,
tag: 0,
};
assert_eq!(sector.floor_name(), "FLAT1");
assert_eq!(sector.ceiling_name(), "CEIL3");
}
#[test]
fn parse_i16_le_positive() {
let data = [0x34, 0x12]; assert_eq!(parse::i16_le(&data, 0), 0x1234);
}
#[test]
fn parse_i16_le_negative() {
let data = [0xFF, 0xFF]; assert_eq!(parse::i16_le(&data, 0), -1);
}
#[test]
fn parse_i16_le_with_offset() {
let data = [0xAA, 0xBB, 0x34, 0x12];
assert_eq!(parse::i16_le(&data, 2), 0x1234);
}
#[test]
fn parse_u16_le() {
let data = [0xFF, 0xFF]; assert_eq!(parse::u16_le(&data, 0), 0xFFFF);
}
#[test]
fn parse_i32_le() {
let data = [0x78, 0x56, 0x34, 0x12]; assert_eq!(parse::i32_le(&data, 0), 0x12345678);
}
#[test]
fn parse_i32_le_negative() {
let data = [0xFF, 0xFF, 0xFF, 0xFF]; assert_eq!(parse::i32_le(&data, 0), -1);
}
#[test]
fn parse_name8() {
let data = [0, 0, b'T', b'E', b'S', b'T', 0, 0, 0, 0];
let name = parse::name8(&data, 2);
assert_eq!(&name, b"TEST\0\0\0\0");
}
#[test]
fn linedef_flag_values() {
assert_eq!(ML_BLOCKING, 1);
assert_eq!(ML_TWOSIDED, 4);
assert_eq!(ML_DONTPEGTOP, 8);
assert_eq!(ML_DONTPEGBOTTOM, 16);
assert_eq!(ML_SECRET, 32);
assert_eq!(ML_SOUNDBLOCK, 64);
assert_eq!(ML_DONTDRAW, 128);
assert_eq!(ML_MAPPED, 256);
}
#[test]
fn thing_flag_values() {
assert_eq!(MTF_EASY, 1);
assert_eq!(MTF_NORMAL, 2);
assert_eq!(MTF_HARD, 4);
assert_eq!(MTF_AMBUSH, 8);
assert_eq!(MTF_MULTI, 16);
}
#[test]
fn nf_subsector_flag() {
assert_eq!(NF_SUBSECTOR, 0x8000);
}
#[test]
fn thing_player1_type() {
assert_eq!(THING_PLAYER1, 1);
}
}