1use crate::{lump, wad, BinParseError, BinParseResult, Palette};
2use io::{Read, Seek, SeekFrom};
3use lump::Lump;
4use std::boxed::Box;
5use std::collections::hash_map::Entry as MapEntry;
6use std::collections::HashMap;
7use std::io;
8use std::mem::size_of;
9use std::mem::size_of_val;
10use std::string::{String, ToString};
11use std::vec::Vec;
12use wad::repr::Head;
13
14#[derive(Debug)]
17pub struct Parser<'a, Reader: Seek + Read> {
18 cursor: &'a mut Reader,
19 start: u64,
20 directory: HashMap<String, wad::Entry>,
21}
22
23impl<'a, Reader: Seek + Read> Parser<'a, Reader> {
24 pub fn new(cursor: &'a mut Reader) -> BinParseResult<(Self, Vec<String>)> {
28 let start = cursor.stream_position().map_err(BinParseError::Io)?;
29 let (directory, warnings) = parse_directory(cursor, start)?;
30
31 Ok((
32 Self {
33 cursor,
34 start,
35 directory,
36 },
37 warnings,
38 ))
39 }
40
41 pub fn directory(&self) -> HashMap<String, wad::Entry> {
44 self.directory.clone()
45 }
46
47 pub fn parse_mip_texture(
50 &mut self,
51 entry: &wad::Entry,
52 ) -> BinParseResult<lump::MipTexture> {
53 self.seek_to_entry(entry)?;
54 lump::parse_mip_texture(self.cursor)
55 }
56
57 pub fn parse_image(
59 &mut self,
60 entry: &wad::Entry,
61 ) -> BinParseResult<lump::Image> {
62 self.seek_to_entry(entry)?;
63 lump::parse_image(self.cursor)
64 }
65
66 pub fn parse_palette(
68 &mut self,
69 entry: &wad::Entry,
70 ) -> BinParseResult<Box<Palette>> {
71 self.seek_to_entry(entry)?;
72 lump::parse_palette(self.cursor)
73 }
74
75 pub fn read_raw(
78 &mut self,
79 entry: &wad::Entry,
80 ) -> BinParseResult<Box<[u8]>> {
81 self.seek_to_entry(entry)?;
82 let length = usize::try_from(entry.length()).map_err(|_| {
83 BinParseError::Parse("Length too large".to_string())
84 })?;
85 lump::read_raw(self.cursor, length)
86 }
87
88 pub fn parse_inferred(
93 &mut self,
94 entry: &wad::Entry,
95 ) -> BinParseResult<Lump> {
96 const CONCHARS_NAME: &[u8; 9] = b"CONCHARS\0";
97
98 let mut attempt_order = [
99 lump::kind::MIPTEX,
100 lump::kind::SBAR,
101 lump::kind::PALETTE,
102 lump::kind::FLAT,
103 ];
104
105 let mut prioritize = |first_kind| {
108 let mut index = 0;
109
110 for (i, kind) in attempt_order.into_iter().enumerate() {
111 if kind == first_kind {
112 index = i;
113 }
114 }
115
116 while index > 0 {
117 attempt_order[index] = attempt_order[index - 1];
118 attempt_order[index - 1] = first_kind;
119 index -= 1;
120 }
121 };
122
123 prioritize(entry.kind());
124
125 let length = usize::try_from(entry.length()).map_err(|_| {
126 BinParseError::Parse("Length too large".to_string())
127 })?;
128
129 if length == size_of::<Palette>() && entry.kind() != lump::kind::FLAT {
134 prioritize(lump::kind::PALETTE);
135 }
136
137 if entry.name()[..size_of_val(CONCHARS_NAME)] == CONCHARS_NAME[..] {
140 prioritize(lump::kind::FLAT);
141 }
142
143 let mut last_error = BinParseError::Parse("Unreachable".to_string());
144
145 for attempt_kind in attempt_order {
146 match attempt_kind {
147 lump::kind::MIPTEX => match self.parse_mip_texture(entry) {
148 Ok(miptex) => {
149 return Ok(Lump::MipTexture(miptex));
150 }
151 Err(e) => {
152 last_error = e;
153 }
154 },
155 lump::kind::SBAR => match self.parse_image(entry) {
156 Ok(img) => {
157 return Ok(Lump::StatusBar(img));
158 }
159 Err(e) => {
160 last_error = e;
161 }
162 },
163 lump::kind::PALETTE => match self.parse_palette(entry) {
164 Ok(pal) => {
165 return Ok(Lump::Palette(pal));
166 }
167 Err(e) => {
168 last_error = e;
169 }
170 },
171 lump::kind::FLAT => match self.read_raw(entry) {
172 Ok(bytes) => {
173 return Ok(Lump::Flat(bytes));
174 }
175 Err(e) => {
176 last_error = e;
177 }
178 },
179 _ => unreachable!(),
180 }
181 }
182
183 Err(last_error)
184 }
185
186 fn seek_to_entry(&mut self, entry: &wad::Entry) -> BinParseResult<()> {
187 let offset = self
188 .start
189 .checked_add(entry.offset().into())
190 .ok_or(BinParseError::Parse("Offset too large".to_string()))?;
191
192 self.cursor.seek(SeekFrom::Start(offset))?;
193 Ok(())
194 }
195}
196
197fn parse_directory(
198 cursor: &mut (impl Seek + Read),
199 start: u64,
200) -> BinParseResult<(HashMap<String, wad::Entry>, Vec<String>)> {
201 let mut header_bytes = [0u8; size_of::<Head>()];
202 cursor.read_exact(&mut header_bytes[..])?;
203 let header: Head = header_bytes.try_into()?;
204 let entry_ct = header.entry_count();
205 let dir_offset = header.directory_offset();
206
207 let dir_pos = start
208 .checked_add(dir_offset.into())
209 .ok_or(BinParseError::Parse("Offset too large".to_string()))?;
210
211 cursor
212 .seek(SeekFrom::Start(dir_pos))
213 .map_err(BinParseError::Io)?;
214
215 let mut entries = HashMap::<String, wad::Entry>::with_capacity(
216 entry_ct.try_into().unwrap(),
217 );
218
219 let mut warnings = Vec::new();
220
221 for _ in 0..entry_ct {
222 const WAD_ENTRY_SIZE: usize = size_of::<wad::Entry>();
223 let mut entry_bytes = [0u8; WAD_ENTRY_SIZE];
224 cursor.read_exact(&mut entry_bytes[0..WAD_ENTRY_SIZE])?;
225 let entry: wad::Entry = entry_bytes.try_into()?;
226
227 let entry_name = entry
228 .name_to_string()
229 .map_err(|e| BinParseError::Parse(e.to_string()))?;
230
231 if let MapEntry::Vacant(map_entry) = entries.entry(entry_name.clone()) {
232 map_entry.insert(entry);
233 } else {
234 warnings
235 .push(format!("Skipping duplicate entry for `{entry_name}`"));
236 }
237 }
238
239 Ok((entries, warnings))
240}