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>> {
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)?;
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 export_multi_image<'a>(
169 &'a self,
170 ) -> Result<Box<dyn Iterator<Item = Result<ImageDataWithName>> + 'a>> {
171 Ok(Box::new(Hg3ImageIter {
172 iter: self.entries.iter(),
173 index: 0,
174 data: self.data.to_ref(),
175 draw_canvas: self.draw_canvas,
176 }))
177 }
178}
179
180struct Hg3ImageIter<'a, T: Iterator<Item = &'a (Hg3Entry, usize, usize)> + 'a> {
181 iter: T,
182 index: usize,
183 data: MemReaderRef<'a>,
184 draw_canvas: bool,
185}
186
187impl<'a, T: Iterator<Item = &'a (Hg3Entry, usize, usize)> + 'a> Iterator for Hg3ImageIter<'a, T> {
188 type Item = Result<ImageDataWithName>;
189
190 fn next(&mut self) -> Option<Self::Item> {
191 if let Some((entry, offset, size)) = self.iter.next() {
192 let data = &self.data.data[*offset..*offset + *size];
193 let reader = Hg3Reader {
194 m_input: MemReaderRef::new(data),
195 m_info: entry.clone(),
196 m_pixel_size: entry.bpp / 8,
197 };
198 self.index += 1;
199 match reader.unpack() {
200 Ok(mut img) => {
201 if self.draw_canvas {
202 if entry.canvas_width > 0 && entry.canvas_height > 0 {
203 img = match draw_on_canvas(
204 img,
205 entry.canvas_width,
206 entry.canvas_height,
207 entry.offset_x,
208 entry.offset_y,
209 ) {
210 Ok(canvas_img) => canvas_img,
211 Err(e) => return Some(Err(e)),
212 };
213 }
214 }
215 Some(Ok(ImageDataWithName {
216 name: format!("{:04}", self.index - 1),
217 data: img,
218 }))
219 }
220 Err(e) => Some(Err(e)),
221 }
222 } else {
223 None
224 }
225 }
226}
227
228struct Hg3Reader<'a> {
229 m_input: MemReaderRef<'a>,
230 m_info: Hg3Entry,
231 m_pixel_size: u32,
232}
233
234impl<'a> Hg3Reader<'a> {
235 fn unpack_stream(
236 &mut self,
237 data_offset: usize,
238 data_packed: usize,
239 data_unpacked: usize,
240 ctl_packed: usize,
241 ctl_unpacked: usize,
242 ) -> Result<Vec<u8>> {
243 let ctl_offset = data_offset + data_packed;
244 let mut data = Vec::with_capacity(data_unpacked);
245 data.resize(data_unpacked, 0);
246 let z = &self.m_input.data[data_offset..data_offset + data_packed];
247 let mut decompressor = Decompress::new(true);
248 decompressor.decompress(z, &mut data, FlushDecompress::Finish)?;
249 let z = &self.m_input.data[ctl_offset..ctl_offset + ctl_packed];
250 let mut ctl = Vec::with_capacity(ctl_unpacked);
251 ctl.resize(ctl_unpacked, 0);
252 let mut decompressor = Decompress::new(true);
253 decompressor.decompress(z, &mut ctl, FlushDecompress::Finish)?;
254 let mut bits = LsbBitStream::new(MemReaderRef::new(&ctl));
255 let mut copy = bits.get_next_bit()?;
256 let output_size = Self::get_bit_count(&mut bits)? as usize;
257 let mut output = Vec::with_capacity(output_size);
258 output.resize(output_size, 0);
259 let mut src = 0;
260 let mut dst = 0;
261 while dst < output_size {
262 let count = Self::get_bit_count(&mut bits)? as usize;
263 if copy {
264 output[dst..dst + count].copy_from_slice(&data[src..src + count]);
265 src += count;
266 }
267 dst += count;
268 copy = !copy;
269 }
270 Ok(self.apply_delta(&output))
271 }
272
273 fn get_bit_count(bits: &mut LsbBitStream<MemReaderRef<'_>>) -> Result<u32> {
274 let mut n = 0;
275 while !bits.get_next_bit()? {
276 n += 1;
277 if n >= 0x20 {
278 return Err(anyhow::anyhow!("Overflow at HG-3 Reader."));
279 }
280 }
281 let mut value = 1;
282 for _ in 0..n {
283 value = (value << 1) | (bits.get_next_bit()? as u32);
284 }
285 Ok(value)
286 }
287
288 fn convert_value(mut val: u8) -> u8 {
289 let carry = val & 1 != 0;
290 val >>= 1;
291 if carry { val ^ 0xff } else { val }
292 }
293
294 fn apply_delta(&self, pixels: &[u8]) -> Vec<u8> {
295 let mut table = [[0u32; 0x100]; 4];
296 for i in 0..0x100u32 {
297 let mut val = i & 0xC0;
298 val <<= 6;
299 val |= i & 0x30;
300 val <<= 6;
301 val |= i & 0x0C;
302 val <<= 6;
303 val |= i & 0x03;
304 table[0][i as usize] = val << 6;
305 table[1][i as usize] = val << 4;
306 table[2][i as usize] = val << 2;
307 table[3][i as usize] = val;
308 }
309 let pxl_len = pixels.len();
310 let plane_size = pxl_len / 4;
311 let mut plane0 = 0;
312 let mut plane1 = plane0 + plane_size;
313 let mut plane2 = plane1 + plane_size;
314 let mut plane3 = plane2 + plane_size;
315 let mut output = Vec::with_capacity(pxl_len);
316 output.resize(pxl_len, 0);
317 let mut dst = 0;
318 while dst < pxl_len {
319 let val = table[0][pixels[plane0] as usize]
320 | table[1][pixels[plane1] as usize]
321 | table[2][pixels[plane2] as usize]
322 | table[3][pixels[plane3] as usize];
323 plane0 += 1;
324 plane1 += 1;
325 plane2 += 1;
326 plane3 += 1;
327 output[dst] = Self::convert_value(val as u8);
328 dst += 1;
329 output[dst] = Self::convert_value((val >> 8) as u8);
330 dst += 1;
331 output[dst] = Self::convert_value((val >> 16) as u8);
332 dst += 1;
333 output[dst] = Self::convert_value((val >> 24) as u8);
334 dst += 1;
335 }
336 let stride = self.m_info.width * self.m_pixel_size;
337 for x in self.m_pixel_size..stride {
338 let target = x as usize - self.m_pixel_size as usize;
339 wrapping! {
340 output[x as usize] += output[target];
341 }
342 }
343 let mut prev = 0;
344 for _ in 1..self.m_info.height {
345 let line = prev + stride;
346 for x in 0..stride {
347 let src = line as usize + x as usize;
348 let target = prev as usize + x as usize;
349 wrapping! {
350 output[src] += output[target];
351 }
352 }
353 prev = line;
354 }
355 output
356 }
357
358 fn unpack(mut self) -> Result<ImageData> {
359 self.m_input.pos = self.m_info.header_size as usize;
360 let mut image_type = [0; 8];
361 self.m_input.read_exact(&mut image_type)?;
362 if &image_type == b"img0000\0" {
363 return self.unpack_img0000();
364 } else if &image_type == b"img_jpg\0" {
365 return self.unpack_jpeg();
366 } else {
367 return Err(anyhow::anyhow!("Unsupported image type: {:?}", image_type));
368 }
369 }
370
371 fn unpack_img0000(&mut self) -> Result<ImageData> {
372 self.m_input.pos = self.m_info.header_size as usize + 0x18;
373 let packed_data_size = self.m_input.read_u32()?;
374 let data_size = self.m_input.read_u32()?;
375 let ctl_packed_size = self.m_input.read_u32()?;
376 let ctl_size = self.m_input.read_u32()?;
377 let mut data = self.unpack_stream(
378 self.m_info.header_size as usize + 0x28,
379 packed_data_size as usize,
380 data_size as usize,
381 ctl_packed_size as usize,
382 ctl_size as usize,
383 )?;
384 let expected_size =
385 self.m_info.width as usize * self.m_info.height as usize * self.m_pixel_size as usize;
386 let data_len = data.len();
387 if data_len < expected_size {
388 return Err(anyhow::anyhow!(
389 "Unpacked data size {} is less than expected size {}",
390 data.len(),
391 expected_size
392 ));
393 }
394 if data_len > expected_size {
395 if data.iter().skip(expected_size).any(|&x| x != 0) {
396 eprintln!(
397 "WARN: Unpacked data size {} is greater than expected size {} and contains non zero data, truncating excess data.",
398 data_len, expected_size
399 );
400 crate::COUNTER.inc_warning();
401 }
402 data.truncate(expected_size);
403 }
404 let fmt = match self.m_info.bpp {
405 24 => ImageColorType::Bgr,
406 32 => ImageColorType::Bgra,
407 _ => {
408 return Err(anyhow::anyhow!(
409 "Unsupported BPP: {} in HG-3 image",
410 self.m_info.bpp
411 ));
412 }
413 };
414 let mut img = ImageData {
415 width: self.m_info.width,
416 height: self.m_info.height,
417 color_type: fmt,
418 depth: 8,
419 data,
420 };
421 flip_image(&mut img)?;
422 Ok(img)
423 }
424
425 fn unpack_jpeg(&mut self) -> Result<ImageData> {
426 let toc = self.read_sections()?;
427 self.m_input.pos = (*toc
428 .get("img_jpg")
429 .ok_or(anyhow::anyhow!("Missing img_jpg section"))?)
430 as usize
431 + 12;
432 let jpeg_size = self.m_input.read_u32()?;
433 let mut data = {
434 let jpeg = StreamRegion::with_size(&mut self.m_input, jpeg_size as u64)?;
435 load_jpg(jpeg)?
436 };
437 if data.color_type.bpp(1) < 3 {
438 return Err(anyhow::anyhow!(
439 "Unsupported JPEG color type: {:?} in HG-3 image",
440 data.color_type
441 ));
442 }
443 let src_pixel_size = data.color_type.bpp(1) as usize;
444 let alpha = if let Some(&alpha_offset) = toc.get("img_al") {
445 Some(self.read_alpha(alpha_offset as u32)?)
446 } else {
447 None
448 };
449 let target_color_type = if alpha.is_some() {
450 ImageColorType::Rgba
451 } else {
452 data.color_type
453 };
454 let pixel_size = target_color_type.bpp(1) as usize;
455 let stride = self.m_info.width as usize * pixel_size;
456 let mut pixels = vec![0; stride * self.m_info.height as usize];
457 let mut src = 0;
458 let mut dst = 0;
459 let mut src_a = 0;
460 let src_g = 1;
461 let (src_b, src_r) = if toc.contains_key("imgmode") {
462 (2, 0)
463 } else {
464 (0, 2)
465 };
466 for _ in 0..self.m_info.width as usize * self.m_info.height as usize {
467 pixels[dst] = data.data[src + src_b];
468 pixels[dst + 1] = data.data[src + src_g];
469 pixels[dst + 2] = data.data[src + src_r];
470 if let Some(ref alpha_data) = alpha {
471 pixels[dst + 3] = alpha_data[src_a];
472 src_a += 1;
473 }
474 dst += pixel_size;
475 src += src_pixel_size;
476 }
477 data.data = pixels;
478 data.color_type = target_color_type;
479 Ok(data)
480 }
481
482 fn read_alpha(&mut self, start_pos: u32) -> Result<Vec<u8>> {
483 self.m_input.pos = start_pos as usize + 0x10;
484 let packed_size = self.m_input.read_u32()?;
485 let alpha_size = self.m_input.read_u32()?;
486 let alpha_in = StreamRegion::with_size(&mut self.m_input, packed_size as u64)?;
487 let mut alpha = Vec::new();
488 flate2::read::ZlibDecoder::new(alpha_in).read_to_end(&mut alpha)?;
489 if alpha.len() != alpha_size as usize {
490 return Err(anyhow::anyhow!(
491 "Alpha data size {} does not match expected size {}",
492 alpha.len(),
493 alpha_size
494 ));
495 }
496 Ok(alpha)
497 }
498
499 fn read_sections(&mut self) -> Result<HashMap<String, u32>> {
500 let mut sections = HashMap::new();
501 let mut next_offset = self.m_info.header_size;
502 loop {
503 self.m_input.pos = next_offset as usize;
504 let section_name = self.m_input.read_fstring(8, Encoding::Cp932, true)?;
505 let section_size = self.m_input.read_u32()?;
506 sections.insert(section_name, next_offset);
507 next_offset += section_size;
508 if section_size == 0 {
509 break;
510 }
511 }
512 Ok(sections)
513 }
514}