1use crate::{Result, error::Error};
4
5#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct SoftwareTexture {
8 pub x_offset: i16,
9 pub y_offset: i16,
10 pub width: u16,
11 pub height: u16,
12 pub flags: u16,
13 pub frame_delay: u16,
14 pub scale_x: i16,
15 pub scale_y: i16,
16 pub frames: Vec<Vec<u8>>,
17}
18
19impl SoftwareTexture {
20 pub fn rgba(&self, frame: usize, palette: Option<&super::palette::Palette>) -> Result<Vec<u8>> {
22 let pixels = self
23 .frames
24 .get(frame)
25 .ok_or_else(|| Error::Parse(format!("TEXTURE: frame {frame} outside image")))?;
26 Ok(pixels
27 .iter()
28 .flat_map(|&index| {
29 let [r, g, b] =
30 palette.map_or([index; 3], |palette| palette.colors[usize::from(index)]);
31 [
32 r,
33 g,
34 b,
35 if index == 0 && self.flags & 0x100 != 0 {
36 0
37 } else {
38 255
39 },
40 ]
41 })
42 .collect())
43 }
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub enum SoftwareTextureEntry {
48 SolidColor(u8),
49 Image(SoftwareTexture),
50}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub struct SoftwareTextureBank {
54 pub name: String,
55 pub entries: Vec<SoftwareTextureEntry>,
56}
57
58fn bytes(input: &[u8], offset: usize, count: usize) -> Result<&[u8]> {
59 offset
60 .checked_add(count)
61 .and_then(|end| input.get(offset..end))
62 .ok_or_else(|| {
63 Error::Parse(format!(
64 "TEXTURE: truncated data at byte {offset}, need {count} bytes"
65 ))
66 })
67}
68fn word(input: &[u8], offset: usize) -> Result<u16> {
69 Ok(u16::from_le_bytes(
70 bytes(input, offset, 2)?.try_into().unwrap(),
71 ))
72}
73fn dword(input: &[u8], offset: usize) -> Result<usize> {
74 usize::try_from(u32::from_le_bytes(
75 bytes(input, offset, 4)?.try_into().unwrap(),
76 ))
77 .map_err(|_| Error::Parse("TEXTURE: offset exceeds address space".into()))
78}
79fn relative(input: &[u8], base: usize, offset: usize) -> Result<usize> {
80 let result = base
81 .checked_add(offset)
82 .ok_or_else(|| Error::Parse("TEXTURE: offset overflow".into()))?;
83 bytes(input, result, 0)?;
84 Ok(result)
85}
86
87pub fn parse_texture_bank(input: &[u8]) -> Result<SoftwareTextureBank> {
89 let count = usize::from(word(input, 0)?);
90 let name_bytes = bytes(input, 2, 24)?;
91 let name_end = name_bytes.iter().position(|&b| b == 0).unwrap_or(24);
92 let name = String::from_utf8_lossy(&name_bytes[..name_end])
93 .trim()
94 .to_owned();
95 bytes(input, 26, count * 20)?;
96 let mut entries = Vec::with_capacity(count);
97 let mut total_pixels = 0usize;
98 for index in 0..count {
99 let directory = 26 + index * 20;
100 let offset = dword(input, directory + 2)?;
101 let entry = if offset == 0 {
102 SoftwareTextureEntry::SolidColor(bytes(input, directory + 1, 1)?[0])
103 } else {
104 let pixels =
105 usize::from(word(input, offset + 4)?) * usize::from(word(input, offset + 6)?);
106 let decoded = pixels
107 .checked_mul(usize::from(word(input, offset + 20)?))
108 .ok_or_else(|| Error::Parse("TEXTURE: decoded pixel count overflow".into()))?;
109 total_pixels = total_pixels
110 .checked_add(decoded)
111 .ok_or_else(|| Error::Parse("TEXTURE: decoded pixel count overflow".into()))?;
112 if total_pixels > 16 * 1024 * 1024 {
113 return Err(Error::Parse(
114 "TEXTURE: bank exceeds 16777216 decoded pixels".into(),
115 ));
116 }
117 parse_image(input, offset)
118 .map(SoftwareTextureEntry::Image)
119 .map_err(|e| Error::Parse(format!("TEXTURE record {index}: {e}")))?
120 };
121 entries.push(entry);
122 }
123 Ok(SoftwareTextureBank { name, entries })
124}
125
126fn parse_image(input: &[u8], offset: usize) -> Result<SoftwareTexture> {
127 bytes(input, offset, 28)?;
128 let width = word(input, offset + 4)?;
129 let height = word(input, offset + 6)?;
130 let flags = word(input, offset + 8)?;
131 let frame_count = usize::from(word(input, offset + 20)?);
132 let mut frames = Vec::with_capacity(frame_count);
133 if frame_count != 0 {
134 super::indexed_image::pixel_count(width, height)?;
135 let data = relative(input, offset, dword(input, offset + 14)?)?;
136 if frame_count == 1 {
137 let stride = if flags & 0x0800 != 0 {
138 256
139 } else {
140 usize::from(width)
141 };
142 if usize::from(width) > stride {
143 return Err(Error::Parse("TEXTURE: width exceeds row stride".into()));
144 }
145 let mut pixels = Vec::with_capacity(usize::from(width) * usize::from(height));
146 for row in 0..usize::from(height) {
147 pixels.extend_from_slice(bytes(input, data + row * stride, usize::from(width))?);
148 }
149 frames.push(pixels);
150 } else {
151 bytes(input, data, frame_count * 4)?;
152 for frame in 0..frame_count {
153 let start = relative(input, data, dword(input, data + frame * 4)?)?;
154 if word(input, start)? != width || word(input, start + 2)? != height {
155 return Err(Error::Parse(format!(
156 "TEXTURE frame {frame}: inconsistent dimensions"
157 )));
158 }
159 let mut cursor = start + 4;
160 let mut pixels = Vec::with_capacity(usize::from(width) * usize::from(height));
161 for _ in 0..height {
162 let mut x = 0;
163 while x < usize::from(width) {
164 let run = bytes(input, cursor, 2)?;
165 cursor += 2;
166 let transparent = usize::from(run[0]);
167 let literal = usize::from(run[1]);
168 if transparent + literal == 0
169 || x + transparent + literal > usize::from(width)
170 {
171 return Err(Error::Parse(format!(
172 "TEXTURE frame {frame}: invalid pixel run"
173 )));
174 }
175 pixels.resize(pixels.len() + transparent, 0);
176 pixels.extend_from_slice(bytes(input, cursor, literal)?);
177 cursor += literal;
178 x += transparent + literal;
179 }
180 }
181 frames.push(pixels);
182 }
183 }
184 }
185 Ok(SoftwareTexture {
186 x_offset: word(input, offset)? as i16,
187 y_offset: word(input, offset + 2)? as i16,
188 width,
189 height,
190 flags,
191 frame_delay: word(input, offset + 22)?,
192 scale_x: word(input, offset + 24)? as i16,
193 scale_y: word(input, offset + 26)? as i16,
194 frames,
195 })
196}
197
198#[derive(Debug, Clone, PartialEq, Eq)]
200pub struct PaletteLookupTable {
201 pub rows: Vec<[u8; 256]>,
202}
203
204impl PaletteLookupTable {
205 pub fn parse(input: &[u8]) -> Result<Self> {
206 if input.len() != 16384 {
207 return Err(Error::Parse(format!(
208 "SHADE/HAZE: expected 16384 bytes, got {}",
209 input.len()
210 )));
211 }
212 Ok(Self {
213 rows: input.as_chunks::<256>().0.to_vec(),
214 })
215 }
216
217 #[must_use]
218 pub fn map(&self, row: usize, index: u8) -> Option<u8> {
219 self.rows.get(row).map(|values| values[usize::from(index)])
220 }
221}