msg_tool\scripts\cat_system\image/
hg3.rs1use crate::ext::io::*;
3use crate::scripts::base::*;
4use crate::types::*;
5use crate::utils::bit_stream::*;
6use crate::utils::img::*;
7use crate::utils::struct_pack::*;
8use anyhow::Result;
9use flate2::{Decompress, FlushDecompress};
10use msg_tool_macro::*;
11use overf::wrapping;
12use std::collections::HashMap;
13use std::io::{Read, Seek, Write};
14
15#[derive(Debug)]
16pub struct Hg3ImageBuilder {}
18
19impl Hg3ImageBuilder {
20 pub const fn new() -> Self {
22 Hg3ImageBuilder {}
23 }
24}
25
26impl ScriptBuilder for Hg3ImageBuilder {
27 fn default_encoding(&self) -> Encoding {
28 Encoding::Cp932
29 }
30
31 fn build_script(
32 &self,
33 data: Vec<u8>,
34 _filename: &str,
35 _encoding: Encoding,
36 _archive_encoding: Encoding,
37 config: &ExtraConfig,
38 _archive: Option<&Box<dyn Script>>,
39 ) -> Result<Box<dyn Script + Send + Sync>> {
40 Ok(Box::new(Hg3Image::new(data, config)?))
41 }
42
43 fn extensions(&self) -> &'static [&'static str] {
44 &["hg3"]
45 }
46
47 fn script_type(&self) -> &'static ScriptType {
48 &ScriptType::CatSystemHg3
49 }
50
51 fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
52 if buf_len >= 4 && &buf[0..4] == b"HG-3" {
53 return Some(255);
54 }
55 None
56 }
57}
58
59#[derive(Debug, Clone, StructPack, StructUnpack)]
60struct Hg3Entry {
61 header_size: u32,
62 _unk: u32,
63 width: u32,
64 height: u32,
65 bpp: u32,
66 offset_x: u32,
67 offset_y: u32,
68 canvas_width: u32,
69 canvas_height: u32,
70}
71
72#[derive(Debug)]
73pub struct Hg3Image {
75 data: MemReader,
76 entries: Vec<(Hg3Entry, usize, usize)>,
77 draw_canvas: bool,
78}
79
80impl Hg3Image {
81 pub fn new(buf: Vec<u8>, config: &ExtraConfig) -> Result<Self> {
86 let mut reader = MemReader::new(buf);
87 let mut magic = [0u8; 4];
88 reader.read_exact(&mut magic)?;
89 if &magic != b"HG-3" {
90 return Err(anyhow::anyhow!("Invalid HG-3 image format"));
91 }
92 let mut offset = 0xC;
93 let mut entries = Vec::new();
94 let len = reader.data.len() as u64;
95 while offset + 0x14 < len && reader.cpeek_and_equal_at(offset + 8, b"stdinfo").is_ok() {
96 let mut section_size = reader.cpeek_u32_at(offset)?;
97 if section_size == 0 {
98 section_size = (len - offset) as u32;
99 }
100 let stdinfo_size = reader.cpeek_u32_at(offset + 0x10)?;
101 if reader
102 .cpeek_and_equal_at(offset + 8 + stdinfo_size as u64, b"img")
103 .is_ok()
104 {
105 reader.pos = (offset + 16) as usize;
106 let entry = Hg3Entry::unpack(&mut reader, false, Encoding::Cp932, &None)?;
107 entries.push((entry, (offset + 8) as usize, section_size as usize - 8));
108 }
109 offset += section_size as u64;
110 }
111 if entries.is_empty() {
112 return Err(anyhow::anyhow!("No valid entries found in HG-3 image"));
113 }
114 Ok(Hg3Image {
115 data: reader,
116 entries,
117 draw_canvas: config.cat_system_image_canvas,
118 })
119 }
120}
121
122impl Script for Hg3Image {
123 fn default_output_script_type(&self) -> OutputScriptType {
124 OutputScriptType::Json
125 }
126
127 fn default_format_type(&self) -> FormatOptions {
128 FormatOptions::None
129 }
130
131 fn is_image(&self) -> bool {
132 true
133 }
134
135 fn export_image(&self) -> Result<ImageData> {
136 if self.entries.len() > 1 {
137 eprintln!(
138 "WARN: There are multiple entries in the HG-3 image, only the first one will be exported."
139 );
140 crate::COUNTER.inc_warning();
141 }
142 let (entry, offset, size) = &self.entries[0];
143 let data = &self.data.data[*offset..*offset + *size];
144 let reader = Hg3Reader {
145 m_input: MemReaderRef::new(data),
146 m_info: entry.clone(),
147 m_pixel_size: entry.bpp / 8,
148 };
149 let mut img = reader.unpack()?;
150 if self.draw_canvas {
151 if entry.canvas_width > 0 && entry.canvas_height > 0 {
152 img = draw_on_canvas(
153 img,
154 entry.canvas_width,
155 entry.canvas_height,
156 entry.offset_x,
157 entry.offset_y,
158 )?;
159 }
160 }
161 Ok(img)
162 }
163
164 fn is_multi_image(&self) -> bool {
165 self.entries.len() > 1
166 }
167
168 fn iter_multi_image_name<'a>(
169 &'a self,
170 ) -> Result<Box<dyn Iterator<Item = Result<String>> + 'a>> {
171 Ok(Box::new(
172 (0..self.entries.len()).map(|i| Ok(format!("{:04}", i))),
173 ))
174 }
175
176 fn open_image<'a>(&'a self, index: usize) -> Result<ImageDataWithName> {
177 let (entry, offset, size) = &self.entries[index];
178 let data = &self.data.data[*offset..*offset + *size];
179 let reader = Hg3Reader {
180 m_input: MemReaderRef::new(data),
181 m_info: entry.clone(),
182 m_pixel_size: entry.bpp / 8,
183 };
184 let mut img = reader.unpack()?;
185 if self.draw_canvas {
186 if entry.canvas_width > 0 && entry.canvas_height > 0 {
187 img = draw_on_canvas(
188 img,
189 entry.canvas_width,
190 entry.canvas_height,
191 entry.offset_x,
192 entry.offset_y,
193 )?;
194 }
195 }
196 Ok(ImageDataWithName {
197 name: format!("{:04}", index),
198 data: img,
199 })
200 }
201}
202
203struct Hg3Reader<'a> {
204 m_input: MemReaderRef<'a>,
205 m_info: Hg3Entry,
206 m_pixel_size: u32,
207}
208
209impl<'a> Hg3Reader<'a> {
210 fn unpack_stream(
211 &mut self,
212 data_offset: usize,
213 data_packed: usize,
214 data_unpacked: usize,
215 ctl_packed: usize,
216 ctl_unpacked: usize,
217 ) -> Result<Vec<u8>> {
218 let ctl_offset = data_offset + data_packed;
219 let mut data = Vec::with_capacity(data_unpacked);
220 data.resize(data_unpacked, 0);
221 let z = &self.m_input.data[data_offset..data_offset + data_packed];
222 let mut decompressor = Decompress::new(true);
223 decompressor.decompress(z, &mut data, FlushDecompress::Finish)?;
224 let z = &self.m_input.data[ctl_offset..ctl_offset + ctl_packed];
225 let mut ctl = Vec::with_capacity(ctl_unpacked);
226 ctl.resize(ctl_unpacked, 0);
227 let mut decompressor = Decompress::new(true);
228 decompressor.decompress(z, &mut ctl, FlushDecompress::Finish)?;
229 let mut bits = LsbBitStream::new(MemReaderRef::new(&ctl));
230 let mut copy = bits.get_next_bit()?;
231 let output_size = Self::get_bit_count(&mut bits)? as usize;
232 let mut output = Vec::with_capacity(output_size);
233 output.resize(output_size, 0);
234 let mut src = 0;
235 let mut dst = 0;
236 while dst < output_size {
237 let count = Self::get_bit_count(&mut bits)? as usize;
238 if copy {
239 output[dst..dst + count].copy_from_slice(&data[src..src + count]);
240 src += count;
241 }
242 dst += count;
243 copy = !copy;
244 }
245 Ok(self.apply_delta(&output))
246 }
247
248 fn get_bit_count(bits: &mut LsbBitStream<MemReaderRef<'_>>) -> Result<u32> {
249 let mut n = 0;
250 while !bits.get_next_bit()? {
251 n += 1;
252 if n >= 0x20 {
253 return Err(anyhow::anyhow!("Overflow at HG-3 Reader."));
254 }
255 }
256 let mut value = 1;
257 for _ in 0..n {
258 value = (value << 1) | (bits.get_next_bit()? as u32);
259 }
260 Ok(value)
261 }
262
263 fn convert_value(mut val: u8) -> u8 {
264 let carry = val & 1 != 0;
265 val >>= 1;
266 if carry { val ^ 0xff } else { val }
267 }
268
269 fn apply_delta(&self, pixels: &[u8]) -> Vec<u8> {
270 let mut table = [[0u32; 0x100]; 4];
271 for i in 0..0x100u32 {
272 let mut val = i & 0xC0;
273 val <<= 6;
274 val |= i & 0x30;
275 val <<= 6;
276 val |= i & 0x0C;
277 val <<= 6;
278 val |= i & 0x03;
279 table[0][i as usize] = val << 6;
280 table[1][i as usize] = val << 4;
281 table[2][i as usize] = val << 2;
282 table[3][i as usize] = val;
283 }
284 let pxl_len = pixels.len();
285 let plane_size = pxl_len / 4;
286 let mut plane0 = 0;
287 let mut plane1 = plane0 + plane_size;
288 let mut plane2 = plane1 + plane_size;
289 let mut plane3 = plane2 + plane_size;
290 let mut output = Vec::with_capacity(pxl_len);
291 output.resize(pxl_len, 0);
292 let mut dst = 0;
293 while dst < pxl_len {
294 let val = table[0][pixels[plane0] as usize]
295 | table[1][pixels[plane1] as usize]
296 | table[2][pixels[plane2] as usize]
297 | table[3][pixels[plane3] as usize];
298 plane0 += 1;
299 plane1 += 1;
300 plane2 += 1;
301 plane3 += 1;
302 output[dst] = Self::convert_value(val as u8);
303 dst += 1;
304 output[dst] = Self::convert_value((val >> 8) as u8);
305 dst += 1;
306 output[dst] = Self::convert_value((val >> 16) as u8);
307 dst += 1;
308 output[dst] = Self::convert_value((val >> 24) as u8);
309 dst += 1;
310 }
311 let stride = self.m_info.width * self.m_pixel_size;
312 for x in self.m_pixel_size..stride {
313 let target = x as usize - self.m_pixel_size as usize;
314 wrapping! {
315 output[x as usize] += output[target];
316 }
317 }
318 let mut prev = 0;
319 for _ in 1..self.m_info.height {
320 let line = prev + stride;
321 for x in 0..stride {
322 let src = line as usize + x as usize;
323 let target = prev as usize + x as usize;
324 wrapping! {
325 output[src] += output[target];
326 }
327 }
328 prev = line;
329 }
330 output
331 }
332
333 fn unpack(mut self) -> Result<ImageData> {
334 self.m_input.pos = self.m_info.header_size as usize;
335 let mut image_type = [0; 8];
336 self.m_input.read_exact(&mut image_type)?;
337 if &image_type == b"img0000\0" {
338 return self.unpack_img0000();
339 } else if &image_type == b"img_jpg\0" {
340 return self.unpack_jpeg();
341 } else {
342 return Err(anyhow::anyhow!("Unsupported image type: {:?}", image_type));
343 }
344 }
345
346 fn unpack_img0000(&mut self) -> Result<ImageData> {
347 self.m_input.pos = self.m_info.header_size as usize + 0x18;
348 let packed_data_size = self.m_input.read_u32()?;
349 let data_size = self.m_input.read_u32()?;
350 let ctl_packed_size = self.m_input.read_u32()?;
351 let ctl_size = self.m_input.read_u32()?;
352 let mut data = self.unpack_stream(
353 self.m_info.header_size as usize + 0x28,
354 packed_data_size as usize,
355 data_size as usize,
356 ctl_packed_size as usize,
357 ctl_size as usize,
358 )?;
359 let expected_size =
360 self.m_info.width as usize * self.m_info.height as usize * self.m_pixel_size as usize;
361 let data_len = data.len();
362 if data_len < expected_size {
363 return Err(anyhow::anyhow!(
364 "Unpacked data size {} is less than expected size {}",
365 data.len(),
366 expected_size
367 ));
368 }
369 if data_len > expected_size {
370 if data.iter().skip(expected_size).any(|&x| x != 0) {
371 eprintln!(
372 "WARN: Unpacked data size {} is greater than expected size {} and contains non zero data, truncating excess data.",
373 data_len, expected_size
374 );
375 crate::COUNTER.inc_warning();
376 }
377 data.truncate(expected_size);
378 }
379 let fmt = match self.m_info.bpp {
380 24 => ImageColorType::Bgr,
381 32 => ImageColorType::Bgra,
382 _ => {
383 return Err(anyhow::anyhow!(
384 "Unsupported BPP: {} in HG-3 image",
385 self.m_info.bpp
386 ));
387 }
388 };
389 let mut img = ImageData {
390 width: self.m_info.width,
391 height: self.m_info.height,
392 color_type: fmt,
393 depth: 8,
394 data,
395 };
396 flip_image(&mut img)?;
397 Ok(img)
398 }
399
400 fn unpack_jpeg(&mut self) -> Result<ImageData> {
401 let toc = self.read_sections()?;
402 self.m_input.pos = (*toc
403 .get("img_jpg")
404 .ok_or(anyhow::anyhow!("Missing img_jpg section"))?)
405 as usize
406 + 12;
407 let jpeg_size = self.m_input.read_u32()?;
408 let mut data = {
409 let jpeg = StreamRegion::with_size(&mut self.m_input, jpeg_size as u64)?;
410 load_jpg(jpeg)?
411 };
412 if data.color_type.bpp(1) < 3 {
413 return Err(anyhow::anyhow!(
414 "Unsupported JPEG color type: {:?} in HG-3 image",
415 data.color_type
416 ));
417 }
418 let src_pixel_size = data.color_type.bpp(1) as usize;
419 let alpha = if let Some(&alpha_offset) = toc.get("img_al") {
420 Some(self.read_alpha(alpha_offset as u32)?)
421 } else {
422 None
423 };
424 let target_color_type = if alpha.is_some() {
425 ImageColorType::Rgba
426 } else {
427 data.color_type
428 };
429 let pixel_size = target_color_type.bpp(1) as usize;
430 let stride = self.m_info.width as usize * pixel_size;
431 let mut pixels = vec![0; stride * self.m_info.height as usize];
432 let mut src = 0;
433 let mut dst = 0;
434 let mut src_a = 0;
435 let src_g = 1;
436 let (src_b, src_r) = if toc.contains_key("imgmode") {
437 (2, 0)
438 } else {
439 (0, 2)
440 };
441 for _ in 0..self.m_info.width as usize * self.m_info.height as usize {
442 pixels[dst] = data.data[src + src_b];
443 pixels[dst + 1] = data.data[src + src_g];
444 pixels[dst + 2] = data.data[src + src_r];
445 if let Some(ref alpha_data) = alpha {
446 pixels[dst + 3] = alpha_data[src_a];
447 src_a += 1;
448 }
449 dst += pixel_size;
450 src += src_pixel_size;
451 }
452 data.data = pixels;
453 data.color_type = target_color_type;
454 Ok(data)
455 }
456
457 fn read_alpha(&mut self, start_pos: u32) -> Result<Vec<u8>> {
458 self.m_input.pos = start_pos as usize + 0x10;
459 let packed_size = self.m_input.read_u32()?;
460 let alpha_size = self.m_input.read_u32()?;
461 let alpha_in = StreamRegion::with_size(&mut self.m_input, packed_size as u64)?;
462 let mut alpha = Vec::new();
463 flate2::read::ZlibDecoder::new(alpha_in).read_to_end(&mut alpha)?;
464 if alpha.len() != alpha_size as usize {
465 return Err(anyhow::anyhow!(
466 "Alpha data size {} does not match expected size {}",
467 alpha.len(),
468 alpha_size
469 ));
470 }
471 Ok(alpha)
472 }
473
474 fn read_sections(&mut self) -> Result<HashMap<String, u32>> {
475 let mut sections = HashMap::new();
476 let mut next_offset = self.m_info.header_size;
477 loop {
478 self.m_input.pos = next_offset as usize;
479 let section_name = self.m_input.read_fstring(8, Encoding::Cp932, true)?;
480 let section_size = self.m_input.read_u32()?;
481 sections.insert(section_name, next_offset);
482 next_offset += section_size;
483 if section_size == 0 {
484 break;
485 }
486 }
487 Ok(sections)
488 }
489}