msg_tool\scripts\will_plus\img/
wip.rs1use crate::ext::io::*;
2use crate::scripts::base::*;
3use crate::types::*;
4use crate::utils::img::*;
5use crate::utils::struct_pack::*;
6use anyhow::{Context, Result, anyhow};
7use msg_tool_macro::*;
8use std::convert::TryFrom;
9use std::io::{Read, Seek, SeekFrom, Write};
10use std::ops::Range;
11
12#[derive(Debug)]
13pub struct WillPlusWipImageBuilder {}
15
16impl WillPlusWipImageBuilder {
17 pub const fn new() -> Self {
19 Self {}
20 }
21}
22
23impl ScriptBuilder for WillPlusWipImageBuilder {
24 fn default_encoding(&self) -> Encoding {
25 Encoding::Cp932
26 }
27
28 fn build_script(
29 &self,
30 data: Vec<u8>,
31 _filename: &str,
32 _encoding: Encoding,
33 _archive_encoding: Encoding,
34 config: &ExtraConfig,
35 _archive: Option<&Box<dyn Script>>,
36 ) -> Result<Box<dyn Script>> {
37 Ok(Box::new(WillPlusWipImage::new(
38 MemReader::new(data),
39 config,
40 )?))
41 }
42
43 fn extensions(&self) -> &'static [&'static str] {
44 &["wip", "wi0", "msk", "mos"]
45 }
46
47 fn script_type(&self) -> &'static ScriptType {
48 &ScriptType::WillPlusWip
49 }
50
51 fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
52 if buf_len >= 4 && buf.starts_with(b"WIPF") {
53 Some(10)
54 } else {
55 None
56 }
57 }
58
59 #[cfg(feature = "image")]
60 fn is_image(&self) -> bool {
61 true
62 }
63}
64
65#[derive(Debug, Clone, StructPack, StructUnpack)]
66struct FrameHeader {
67 width: u32,
68 height: u32,
69 offset_x: u32,
70 offset_y: u32,
71 _reserved: i32,
72 frame_size: u32,
73}
74
75#[derive(Debug, Clone)]
76struct WillPlusWipFrame {
77 index: usize,
78 header: FrameHeader,
79 data_range: Range<usize>,
80 palette_range: Option<Range<usize>>,
81}
82
83#[derive(Debug)]
84pub struct WillPlusWipImage {
86 data: MemReader,
87 frames: Vec<WillPlusWipFrame>,
88 bpp: u16,
89}
90
91impl WillPlusWipImage {
92 pub fn new(mut data: MemReader, _config: &ExtraConfig) -> Result<Self> {
94 if data.data.len() < 8 {
95 return Err(anyhow!("WIP image too small"));
96 }
97 let mut header = [0u8; 4];
98 data.read_exact(&mut header)?;
99 if &header != b"WIPF" {
100 return Err(anyhow!("Invalid WIP image header"));
101 }
102
103 let frame_count = data.read_u16()? as usize;
104 if frame_count == 0 {
105 return Err(anyhow!("WIP image has no frames"));
106 }
107 let bpp = data.read_u16()?;
108 if bpp != 8 && bpp != 24 {
109 return Err(anyhow!("Unsupported WIP bits-per-pixel: {}", bpp));
110 }
111
112 let index_size = frame_count
113 .checked_mul(0x18)
114 .ok_or_else(|| anyhow!("Frame table size overflow"))?;
115 let mut data_offset = 8usize
116 .checked_add(index_size)
117 .ok_or_else(|| anyhow!("Frame data offset overflow"))?;
118 if data.data.len() < data_offset {
119 return Err(anyhow!("WIP image truncated before frame data"));
120 }
121
122 let mut frames = Vec::with_capacity(frame_count);
123 for frame_index in 0..frame_count {
124 let header_offset = 8 + frame_index * 0x18;
125 data.seek(SeekFrom::Start(header_offset as u64))?;
126 let header = FrameHeader::unpack(&mut data, false, Encoding::Utf8)
127 .with_context(|| format!("Failed to read header for frame {}", frame_index))?;
128 let frame_size = usize::try_from(header.frame_size)
129 .map_err(|_| anyhow!("Frame {} data size too large", frame_index))?;
130
131 let data_start = data_offset;
132 let data_end = data_start
133 .checked_add(frame_size)
134 .ok_or_else(|| anyhow!("Frame {} data range overflow", frame_index))?;
135 if data_end > data.data.len() {
136 return Err(anyhow!("Frame {} data exceeds file length", frame_index));
137 }
138
139 let (palette_range, next_offset) = if bpp == 8 {
140 let palette_start = data_end;
141 let palette_end = palette_start
142 .checked_add(0x400)
143 .ok_or_else(|| anyhow!("Frame {} palette range overflow", frame_index))?;
144 if palette_end > data.data.len() {
145 return Err(anyhow!("Frame {} palette exceeds file length", frame_index));
146 }
147 (Some(palette_start..palette_end), palette_end)
148 } else {
149 (None, data_end)
150 };
151
152 frames.push(WillPlusWipFrame {
153 index: frame_index,
154 header,
155 data_range: data_start..data_end,
156 palette_range,
157 });
158 data_offset = next_offset;
159 }
160
161 if frames.is_empty() {
162 return Err(anyhow!("No valid frames found in WIP image"));
163 }
164
165 Ok(WillPlusWipImage { data, frames, bpp })
166 }
167
168 fn decode_frame(&self, frame: &WillPlusWipFrame) -> Result<ImageData> {
169 let width_usize = usize::try_from(frame.header.width)
170 .map_err(|_| anyhow!("Frame {} width is too large", frame.index))?;
171 let height_usize = usize::try_from(frame.header.height)
172 .map_err(|_| anyhow!("Frame {} height is too large", frame.index))?;
173 let plane_size = width_usize
174 .checked_mul(height_usize)
175 .ok_or_else(|| anyhow!("Frame {} dimensions overflow", frame.index))?;
176
177 let compressed = self
178 .data
179 .data
180 .get(frame.data_range.clone())
181 .ok_or_else(|| anyhow!("Frame {} data range is invalid", frame.index))?;
182
183 let expected = match self.bpp {
184 8 => plane_size,
185 24 => plane_size
186 .checked_mul(3)
187 .ok_or_else(|| anyhow!("Frame {} buffer size overflow for 24bpp", frame.index))?,
188 _ => return Err(anyhow!("Unsupported bits-per-pixel: {}", self.bpp)),
189 };
190
191 let decoded = lzss_decompress(compressed, expected)
192 .with_context(|| format!("Failed to decompress frame {}", frame.index))?;
193
194 match self.bpp {
195 24 => {
196 let required = plane_size
197 .checked_mul(3)
198 .ok_or_else(|| anyhow!("Frame {} plane size overflow", frame.index))?;
199 if decoded.len() < required {
200 return Err(anyhow!(
201 "Frame {} decompressed data too short: {} < {}",
202 frame.index,
203 decoded.len(),
204 required
205 ));
206 }
207 let mut pixels = Vec::with_capacity(required);
208 for i in 0..plane_size {
209 let b = decoded[i];
210 let g = decoded[i + plane_size];
211 let r = decoded[i + plane_size * 2];
212 pixels.push(b);
213 pixels.push(g);
214 pixels.push(r);
215 }
216 Ok(ImageData {
217 width: frame.header.width,
218 height: frame.header.height,
219 color_type: ImageColorType::Bgr,
220 depth: 8,
221 data: pixels,
222 })
223 }
224 8 => {
225 let indices = decoded.get(0..plane_size).ok_or_else(|| {
226 anyhow!(
227 "Frame {} decompressed data too short for indices",
228 frame.index
229 )
230 })?;
231 let palette_range = frame.palette_range.as_ref().ok_or_else(|| {
232 anyhow!("Frame {} missing palette data for 8bpp image", frame.index)
233 })?;
234 let palette = self
235 .data
236 .data
237 .get(palette_range.clone())
238 .ok_or_else(|| anyhow!("Frame {} palette range is invalid", frame.index))?;
239 convert_index_palette_to_normal_bitmap(
240 indices,
241 8,
242 palette,
243 PaletteFormat::RgbX,
244 width_usize,
245 height_usize,
246 )
247 .with_context(|| format!("Failed to apply palette for frame {}", frame.index))
248 }
249 _ => Err(anyhow!("Unsupported bits-per-pixel: {}", self.bpp)),
250 }
251 }
252}
253
254impl Script for WillPlusWipImage {
255 fn default_output_script_type(&self) -> OutputScriptType {
256 OutputScriptType::Json
257 }
258
259 fn default_format_type(&self) -> FormatOptions {
260 FormatOptions::None
261 }
262
263 fn is_image(&self) -> bool {
264 true
265 }
266
267 fn export_image(&self) -> Result<ImageData> {
268 if self.frames.len() > 1 {
269 eprintln!("WARN: WIP image contains multiple frames, exporting only the first frame");
270 crate::COUNTER.inc_warning();
271 }
272 self.frames
273 .get(0)
274 .ok_or_else(|| anyhow!("No frames available in WIP image"))
275 .and_then(|frame| self.decode_frame(frame))
276 }
277
278 fn is_multi_image(&self) -> bool {
279 self.frames.len() > 1
280 }
281
282 fn export_multi_image<'a>(
283 &'a self,
284 ) -> Result<Box<dyn Iterator<Item = Result<ImageDataWithName>> + 'a>> {
285 Ok(Box::new(WillPlusWipIterator {
286 image: self,
287 index: 0,
288 }))
289 }
290}
291
292struct WillPlusWipIterator<'a> {
293 image: &'a WillPlusWipImage,
294 index: usize,
295}
296
297impl<'a> Iterator for WillPlusWipIterator<'a> {
298 type Item = Result<ImageDataWithName>;
299
300 fn next(&mut self) -> Option<Self::Item> {
301 if let Some(frame) = self.image.frames.get(self.index) {
302 let frame_index = self.index;
303 self.index += 1;
304 Some(
305 self.image
306 .decode_frame(frame)
307 .map(|data| ImageDataWithName {
308 name: format!("{:04}", frame_index),
309 data,
310 }),
311 )
312 } else {
313 None
314 }
315 }
316}
317
318fn lzss_decompress(compressed: &[u8], expected: usize) -> Result<Vec<u8>> {
319 let mut output = Vec::with_capacity(expected.max(0x1000));
320 let mut window = [0u8; 0x1000];
321 let mut window_index: usize = 1;
322 let mut control: u32 = 0;
323 let mut remaining = compressed.len();
324 let mut cursor = 0usize;
325
326 while remaining > 0 {
327 control >>= 1;
328 if control & 0x100 == 0 {
329 if remaining == 0 {
330 break;
331 }
332 let value = compressed[cursor];
333 cursor += 1;
334 remaining -= 1;
335 control = (value as u32) | 0xFF00;
336 }
337
338 if control & 1 != 0 {
339 if remaining < 1 {
340 return Err(anyhow!("Unexpected end of data while reading literal"));
341 }
342 let value = compressed[cursor];
343 cursor += 1;
344 remaining -= 1;
345 output.push(value);
346 window[window_index] = value;
347 window_index = (window_index + 1) & 0x0FFF;
348 } else {
349 if remaining < 2 {
350 return Err(anyhow!(
351 "Unexpected end of data while reading back-reference"
352 ));
353 }
354 let hi = compressed[cursor] as usize;
355 let lo = compressed[cursor + 1] as usize;
356 cursor += 2;
357 remaining -= 2;
358 let mut offset = (hi << 4) | (lo >> 4);
359 let mut count = (lo & 0x0F) + 2;
360 while count > 0 {
361 let value = window[offset & 0x0FFF];
362 offset = offset.wrapping_add(1);
363 output.push(value);
364 window[window_index] = value;
365 window_index = (window_index + 1) & 0x0FFF;
366 count -= 1;
367 }
368 }
369 }
370
371 if expected > 0 && output.len() < expected {
372 return Err(anyhow!(
373 "Decompressed data shorter than expected: {} < {}",
374 output.len(),
375 expected
376 ));
377 }
378
379 Ok(output)
380}