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#[derive(Clone, Copy, PartialEq, Eq, Debug)]
66#[repr(C, packed)]
67pub struct Entry {
68 offset: u32,
69 length: u32,
70 uncompressed_length: u32, lump_kind: u8,
72 compression: u8, _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 pub fn name_to_cstring(&self) -> CString {
94 slice_to_cstring(&self.name)
95 }
96
97 pub fn name_to_string(&self) -> Result<String, IntoStringError> {
99 self.name_to_cstring().into_string()
100 }
101
102 pub fn name(&self) -> [u8; 16] {
104 self.name
105 }
106
107 pub fn offset(&self) -> u32 {
109 self.offset
110 }
111
112 pub fn length(&self) -> u32 {
114 self.length
115 }
116
117 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 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}