1use crate::error;
2use crate::lump::kind;
3use crate::slice_to_cstring;
4use crate::Palette;
5use std::boxed::Box;
6use std::ffi::{CString, IntoStringError};
7use std::mem::size_of;
8use std::string::{String, ToString};
9
10#[derive(Clone, PartialEq, Eq, Debug)]
12pub enum Lump {
13 Palette(Box<Palette>),
14 StatusBar(Image),
15 MipTexture(MipTexture),
16 Flat(Box<[u8]>),
17}
18
19impl Lump {
20 pub fn kind(&self) -> u8 {
22 match self {
23 Self::Palette(_) => kind::PALETTE,
24 Self::StatusBar(_) => kind::SBAR,
25 Self::MipTexture(_) => kind::MIPTEX,
26 _ => kind::FLAT,
27 }
28 }
29}
30
31#[derive(Clone, PartialEq, Eq, Debug)]
33pub struct Image {
34 width: u32,
35 height: u32,
36 pixels: Box<[u8]>,
37}
38
39impl Image {
40 pub fn from_pixels(width: u32, pixels: Box<[u8]>) -> Self {
49 let pixel_ct: u32 = pixels.len().try_into().expect("Too many pixels");
50
51 if pixels.is_empty() {
52 return Image {
53 width: 0,
54 height: 0,
55 pixels,
56 };
57 }
58
59 if width == 0 {
60 panic!("Image with pixels must have width > 0");
61 }
62
63 if pixel_ct % width != 0 {
64 panic!("Incomplete pixel row");
65 }
66
67 Image {
68 width,
69 height: pixel_ct / width,
70 pixels,
71 }
72 }
73
74 pub fn width(&self) -> u32 {
75 self.width
76 }
77
78 pub fn height(&self) -> u32 {
79 self.height
80 }
81
82 pub fn pixels(&self) -> &[u8] {
84 &self.pixels[..]
85 }
86}
87
88#[derive(Clone, PartialEq, Eq, Debug)]
94pub struct MipTexture {
95 name: [u8; 16],
96 mips: [Image; 4],
97}
98
99impl MipTexture {
100 pub const MIP_COUNT: usize = 4;
101
102 pub fn from_parts(name: [u8; 16], mips: [Image; Self::MIP_COUNT]) -> Self {
109 Self::validate_mips(&mips);
110 MipTexture { name, mips }
111 }
112
113 pub fn new(name: String, mips: [Image; Self::MIP_COUNT]) -> Self {
120 let mut name_field = [0u8; 16];
121 let name_bytes = &name.into_bytes();
122 name_field[..name_bytes.len()].copy_from_slice(name_bytes);
123 let name = name_field;
124 Self::validate_mips(&mips);
125
126 MipTexture { name, mips }
127 }
128
129 fn validate_mips(mips: &[Image; Self::MIP_COUNT]) {
130 for l in 0..(Self::MIP_COUNT - 1) {
131 let r = l + 1;
132
133 if Some(mips[l].width) != mips[r].width.checked_mul(2) {
134 panic!("Bad mipmaps");
135 }
136
137 if Some(mips[l].height) != mips[r].height.checked_mul(2) {
138 panic!("Bad mipmaps");
139 }
140 }
141 }
142
143 pub fn name_to_cstring(&self) -> CString {
147 slice_to_cstring(&self.name)
148 }
149
150 pub fn name_to_string(&self) -> Result<String, IntoStringError> {
152 self.name_to_cstring().into_string()
153 }
154
155 pub fn name(&self) -> [u8; 16] {
156 self.name
157 }
158
159 pub fn mip(&self, index: usize) -> &Image {
165 if index < Self::MIP_COUNT {
166 &self.mips[index]
167 } else {
168 panic!("Outside mip bounds ([0..{}])", Self::MIP_COUNT);
169 }
170 }
171
172 pub fn mips(&self) -> &[Image] {
174 &self.mips[..]
175 }
176}
177
178#[derive(Clone, Copy, PartialEq, Eq, Debug)]
180#[repr(C, packed)]
181pub struct MipTextureHead {
182 pub(crate) name: [u8; 16],
183 pub(crate) width: u32,
184 pub(crate) height: u32,
185 pub(crate) offsets: [u32; 4],
186}
187
188impl TryFrom<[u8; size_of::<MipTextureHead>()]> for MipTextureHead {
189 type Error = error::BinParse;
190
191 fn try_from(
199 bytes: [u8; size_of::<MipTextureHead>()],
200 ) -> Result<Self, Self::Error> {
201 let name = <[u8; 16]>::try_from(&bytes[..16]).unwrap();
202
203 let bytes = &bytes[16..];
204
205 let width =
206 u32::from_le_bytes(<[u8; 4]>::try_from(&bytes[..4]).unwrap());
207
208 let bytes = &bytes[4..];
209
210 let height =
211 u32::from_le_bytes(<[u8; 4]>::try_from(&bytes[..4]).unwrap());
212
213 if width % 8 != 0 {
214 return Err(error::BinParse::Parse(format!(
215 "Invalid width {}",
216 width
217 )));
218 }
219
220 if height % 8 != 0 {
221 return Err(error::BinParse::Parse(format!(
222 "Invalid height {}",
223 height
224 )));
225 }
226
227 width
228 .checked_mul(height)
229 .ok_or(error::BinParse::Parse("Texture too large".to_string()))?;
230
231 let bytes = &bytes[4..];
232
233 let mut offsets = [0u32; 4];
234
235 for i in 0..4 {
236 offsets[i] = u32::from_le_bytes(
237 <[u8; 4]>::try_from(&bytes[(4 * i)..(4 * i + 4)]).unwrap(),
238 );
239 }
240
241 Ok(MipTextureHead {
242 name,
243 width,
244 height,
245 offsets,
246 })
247 }
248}