msg_tool\scripts\cat_system\image/
hg3.rs

1//! CatSystem2 HG3 Image File (.hg3)
2use 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)]
16/// Builder for CatSystem2 HG3 Image scripts.
17pub struct Hg3ImageBuilder {}
18
19impl Hg3ImageBuilder {
20    /// Creates a new instance of `Hg3ImageBuilder`.
21    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)]
73/// CatSystem2 HG3 Image script.
74pub struct Hg3Image {
75    data: MemReader,
76    entries: Vec<(Hg3Entry, usize, usize)>,
77    draw_canvas: bool,
78}
79
80impl Hg3Image {
81    /// Creates a new instance of `Hg3Image` from a buffer.
82    ///
83    /// * `buf` - The buffer containing the script data.
84    /// * `config` - Extra configuration options.
85    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}