quake_util/wad/
repr.rs

1use std::ffi::{CString, IntoStringError};
2use std::mem::size_of;
3use std::string::{String, ToString};
4
5use crate::common::Junk;
6use crate::{error, slice_to_cstring};
7
8pub const MAGIC: [u8; 4] = *b"WAD2";
9
10#[derive(Clone, Copy, PartialEq, Eq, Debug)]
11#[repr(C, packed)]
12pub struct Head {
13    magic: [u8; 4],
14    entry_count: u32,
15    directory_offset: u32,
16}
17
18impl Head {
19    pub fn new(entry_count: u32, directory_offset: u32) -> Self {
20        Head {
21            magic: MAGIC,
22            entry_count,
23            directory_offset,
24        }
25    }
26
27    pub fn entry_count(&self) -> u32 {
28        self.entry_count
29    }
30
31    pub fn directory_offset(&self) -> u32 {
32        self.directory_offset
33    }
34}
35
36impl TryFrom<[u8; size_of::<Head>()]> for Head {
37    type Error = error::BinParse;
38
39    fn try_from(bytes: [u8; size_of::<Head>()]) -> Result<Self, Self::Error> {
40        let mut chunks = bytes.chunks_exact(4usize);
41
42        if chunks.next().unwrap() != &MAGIC[..] {
43            let magic_str: String =
44                MAGIC.iter().copied().map(char::from).collect();
45
46            return Err(error::BinParse::Parse(format!(
47                "Magic number does not match `{magic_str}`"
48            )));
49        }
50
51        let entry_count = u32::from_le_bytes(
52            <[u8; 4]>::try_from(chunks.next().unwrap()).unwrap(),
53        );
54
55        let directory_offset = u32::from_le_bytes(
56            <[u8; 4]>::try_from(chunks.next().unwrap()).unwrap(),
57        );
58
59        Ok(Head::new(entry_count, directory_offset))
60    }
61}
62
63/// Provides the location of a lump within a WAD archive, length of the lump,
64/// name (16 bytes, null-terminated), and lump kind
65#[derive(Clone, Copy, PartialEq, Eq, Debug)]
66#[repr(C, packed)]
67pub struct Entry {
68    offset: u32,
69    length: u32,
70    uncompressed_length: u32, // unused?
71    lump_kind: u8,
72    compression: u8, // 0 - uncompressed, other values unused?
73    _padding: Junk<u16>,
74    name: [u8; 16],
75}
76
77impl Entry {
78    pub(crate) fn from_config(config: EntryConfig) -> Entry {
79        Entry {
80            offset: config.offset,
81            length: config.length,
82            uncompressed_length: config.length,
83            lump_kind: config.lump_kind,
84            compression: 0u8,
85            _padding: Junk::default(),
86            name: config.name,
87        }
88    }
89
90    /// Obtain the name as a C string.  If the name is not already
91    /// null-terminated (in which case the entry is not well-formed) a null byte
92    /// is appended to make a valid C string.
93    pub fn name_to_cstring(&self) -> CString {
94        slice_to_cstring(&self.name)
95    }
96
97    /// Attempt to interpret the name as UTF-8 encoded string
98    pub fn name_to_string(&self) -> Result<String, IntoStringError> {
99        self.name_to_cstring().into_string()
100    }
101
102    /// Name in raw bytes
103    pub fn name(&self) -> [u8; 16] {
104        self.name
105    }
106
107    /// WAD offset of lump
108    pub fn offset(&self) -> u32 {
109        self.offset
110    }
111
112    /// Length of lump in bytes
113    pub fn length(&self) -> u32 {
114        self.length
115    }
116
117    /// Lump kind as a byte
118    pub fn kind(&self) -> u8 {
119        self.lump_kind
120    }
121}
122
123impl TryFrom<[u8; size_of::<Entry>()]> for Entry {
124    type Error = error::BinParse;
125
126    // Attempt to read an entry from a block of bytes.  Fails if compression
127    // flag is on (unsupported).
128    fn try_from(bytes: [u8; size_of::<Entry>()]) -> Result<Self, Self::Error> {
129        let (offset_bytes, rest) = bytes.split_at(4);
130
131        let offset =
132            u32::from_le_bytes(<[u8; 4]>::try_from(offset_bytes).unwrap());
133
134        let (length_bytes, rest) = rest.split_at(4);
135
136        let length =
137            u32::from_le_bytes(<[u8; 4]>::try_from(length_bytes).unwrap());
138
139        let (uc_length_bytes, rest) = rest.split_at(4);
140
141        let _uc_length =
142            u32::from_le_bytes(<[u8; 4]>::try_from(uc_length_bytes).unwrap());
143
144        let (&[lump_kind], rest) = rest.split_at(1) else {
145            unreachable!()
146        };
147
148        let (&[compression], rest) = rest.split_at(1) else {
149            unreachable!()
150        };
151
152        if compression != 0 {
153            return Err(error::BinParse::Parse(
154                "Compression is unsupported".to_string(),
155            ));
156        }
157
158        let name: [u8; 16] = rest[2..].try_into().unwrap();
159
160        Ok(Entry::from_config(EntryConfig {
161            offset,
162            length,
163            lump_kind,
164            name,
165        }))
166    }
167}
168
169#[derive(Debug, Clone, PartialEq, Eq)]
170pub(crate) struct EntryConfig {
171    pub offset: u32,
172    pub length: u32,
173    pub lump_kind: u8,
174    pub name: [u8; 16],
175}