Skip to main content

siglus_assets/
g00.rs

1use anyhow::{anyhow, bail, Result};
2use image::ImageFormat;
3
4use crate::lzss::{lzss_unpack, lzss_unpack32};
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub enum G00Type {
8    Type0,
9    Type1,
10    Type2,
11    Type3,
12}
13
14impl G00Type {
15    fn from_u8(v: u8) -> Result<Self> {
16        match v {
17            0 => Ok(Self::Type0),
18            1 => Ok(Self::Type1),
19            2 => Ok(Self::Type2),
20            3 => Ok(Self::Type3),
21            _ => bail!("g00: unknown type {}", v),
22        }
23    }
24}
25
26#[derive(Clone, Debug)]
27pub struct G00 {
28    pub ty: G00Type,
29    pub cuts: Vec<G00Cut>,
30}
31
32#[derive(Clone, Debug)]
33pub struct G00Cut {
34    pub width: u32,
35    pub height: u32,
36    pub center_x: i32,
37    pub center_y: i32,
38    pub disp_left: i32,
39    pub disp_top: i32,
40    pub disp_right: i32,
41    pub disp_bottom: i32,
42    pub chips: Vec<G00Chip>,
43}
44
45#[derive(Clone, Debug)]
46pub struct G00Chip {
47    pub x: u32,
48    pub y: u32,
49    pub width: u32,
50    pub height: u32,
51    pub sprite: bool,
52    pub data: G00ChipData,
53}
54
55#[derive(Clone, Debug)]
56pub enum G00ChipData {
57    /// Type0: LZSS32 compressed 32bpp pixels.
58    Type0Lzss32(Vec<u8>),
59    /// Type1: LZSS compressed palette + indices.
60    Type1LzssIndexed(Vec<u8>),
61    /// Type2: Raw 32bpp pixels in the cut stream.
62    RawBgra(Vec<u8>),
63    /// Type3: JPEG bitstream (not decoded here).
64    Jpeg(Vec<u8>),
65}
66
67struct Cur<'a> {
68    buf: &'a [u8],
69    pos: usize,
70}
71
72impl<'a> Cur<'a> {
73    fn new(buf: &'a [u8]) -> Self {
74        Self { buf, pos: 0 }
75    }
76
77    fn remaining(&self) -> usize {
78        self.buf.len().saturating_sub(self.pos)
79    }
80
81    fn ensure(&self, n: usize) -> Result<()> {
82        if self.pos + n > self.buf.len() {
83            bail!(
84                "g00: unexpected EOF (need {}, have {})",
85                n,
86                self.remaining()
87            );
88        }
89        Ok(())
90    }
91
92    fn take(&mut self, n: usize) -> Result<&'a [u8]> {
93        self.ensure(n)?;
94        let out = &self.buf[self.pos..self.pos + n];
95        self.pos += n;
96        Ok(out)
97    }
98
99    fn skip(&mut self, n: usize) -> Result<()> {
100        self.ensure(n)?;
101        self.pos += n;
102        Ok(())
103    }
104
105    fn read_u8(&mut self) -> Result<u8> {
106        Ok(self.take(1)?[0])
107    }
108
109    fn read_u16_le(&mut self) -> Result<u16> {
110        let b = self.take(2)?;
111        Ok(u16::from_le_bytes([b[0], b[1]]))
112    }
113
114    fn read_u32_le(&mut self) -> Result<u32> {
115        let b = self.take(4)?;
116        Ok(u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
117    }
118
119    fn read_i32_le(&mut self) -> Result<i32> {
120        Ok(self.read_u32_le()? as i32)
121    }
122}
123
124impl G00 {
125    /// Parse a full `.g00` file.
126    pub fn parse(bytes: &[u8]) -> Result<Self> {
127        let mut cur = Cur::new(bytes);
128        let ty = G00Type::from_u8(cur.read_u8()?)?;
129
130        match ty {
131            G00Type::Type0 | G00Type::Type1 | G00Type::Type3 => {
132                let w = cur.read_u16_le()? as u32;
133                let h = cur.read_u16_le()? as u32;
134                let rest = cur.take(cur.remaining())?.to_vec();
135
136                let chip_data = match ty {
137                    G00Type::Type0 => G00ChipData::Type0Lzss32(rest),
138                    G00Type::Type1 => G00ChipData::Type1LzssIndexed(rest),
139                    G00Type::Type3 => G00ChipData::Jpeg(rest),
140                    _ => unreachable!(),
141                };
142
143                let chip = G00Chip {
144                    x: 0,
145                    y: 0,
146                    width: w,
147                    height: h,
148                    sprite: false,
149                    data: chip_data,
150                };
151
152                let cut = G00Cut {
153                    width: w,
154                    height: h,
155                    center_x: 0,
156                    center_y: 0,
157                    disp_left: 0,
158                    disp_top: 0,
159                    disp_right: w as i32,
160                    disp_bottom: h as i32,
161                    chips: vec![chip],
162                };
163
164                Ok(Self {
165                    ty,
166                    cuts: vec![cut],
167                })
168            }
169            G00Type::Type2 => {
170                // Header:
171                // [u16 width][u16 height][i32 cut_cnt][G00_CUT_DATABASE * cut_cnt][lzss payload...]
172                let _w = cur.read_u16_le()? as u32;
173                let _h = cur.read_u16_le()? as u32;
174                let cut_cnt = cur.read_i32_le()?;
175                if cut_cnt < 0 {
176                    bail!("g00: negative cut_cnt {}", cut_cnt);
177                }
178
179                // G00_CUT_DATABASE is 6 * i32.
180                let db_bytes = (cut_cnt as usize)
181                    .checked_mul(24)
182                    .ok_or_else(|| anyhow!("g00: cut database size overflow"))?;
183                cur.skip(db_bytes)?;
184
185                let compressed = cur.take(cur.remaining())?;
186                let decompressed = lzss_unpack(compressed)?;
187
188                let mut dcur = Cur::new(&decompressed);
189                let table_cut_cnt = dcur.read_u32_le()? as usize;
190
191                // Table entries: (offset, size) pairs.
192                let mut pairs: Vec<(usize, i32)> = Vec::with_capacity(table_cut_cnt);
193                for _ in 0..table_cut_cnt {
194                    let off = dcur.read_u32_le()? as usize;
195                    let size = dcur.read_u32_le()? as i32;
196                    pairs.push((off, size));
197                }
198
199                let mut cuts = Vec::new();
200                for (off, size) in pairs {
201                    if off == 0 || size <= 0 {
202                        continue;
203                    }
204                    let size_u = size as usize;
205                    let end = off
206                        .checked_add(size_u)
207                        .ok_or_else(|| anyhow!("g00: cut slice overflow"))?;
208                    if end > decompressed.len() {
209                        bail!(
210                            "g00: cut slice out of bounds (off={}, size={}, len={})",
211                            off,
212                            size_u,
213                            decompressed.len()
214                        );
215                    }
216                    let cut_data = &decompressed[off..end];
217                    cuts.push(parse_type2_cut(cut_data)?);
218                }
219
220                if cuts.is_empty() {
221                    bail!("g00: type2 has no cuts");
222                }
223
224                Ok(Self { ty, cuts })
225            }
226        }
227    }
228}
229
230fn parse_type2_cut(cut_data: &[u8]) -> Result<G00Cut> {
231    // Cut header layout (MSVC default packing, 32-bit):
232    //  u8 type
233    //  u8 pad
234    //  u16 count
235    //  i32 x, y, disp_xl, disp_yl, xc, yc, cut_xl, cut_yl
236    //  i32 keep[20]
237    // Total: 116 bytes.
238    let mut cur = Cur::new(cut_data);
239
240    let _cut_type = cur.read_u8()?;
241    cur.skip(1)?; // padding
242    let chip_cnt = cur.read_u16_le()? as usize;
243
244    let x = cur.read_i32_le()?;
245    let y = cur.read_i32_le()?;
246    let disp_xl = cur.read_i32_le()?;
247    let disp_yl = cur.read_i32_le()?;
248    let xc = cur.read_i32_le()?;
249    let yc = cur.read_i32_le()?;
250    let cut_xl = cur.read_i32_le()?;
251    let cut_yl = cur.read_i32_le()?;
252
253    cur.skip(20 * 4)?; // keep
254
255    if cut_xl <= 0 || cut_yl <= 0 {
256        bail!("g00: invalid cut size {}x{}", cut_xl, cut_yl);
257    }
258
259    let mut chips = Vec::with_capacity(chip_cnt);
260    for _ in 0..chip_cnt {
261        // Chip header layout (MSVC default packing, 32-bit):
262        //  u16 x
263        //  u16 y
264        //  u8 type
265        //  u8 pad
266        //  u16 xl
267        //  u16 yl
268        //  u16 pad2 (to align i32)
269        //  i32 keep[20]
270        // Total: 92 bytes.
271        let cx = cur.read_u16_le()? as u32;
272        let cy = cur.read_u16_le()? as u32;
273        let ctype = cur.read_u8()?;
274        cur.skip(1)?; // padding
275        let w = cur.read_u16_le()? as u32;
276        let h = cur.read_u16_le()? as u32;
277        cur.skip(2)?; // padding to i32
278        cur.skip(20 * 4)?; // keep
279
280        let pix_len = (w as usize)
281            .checked_mul(h as usize)
282            .and_then(|v| v.checked_mul(4))
283            .ok_or_else(|| anyhow!("g00: chip pixel size overflow"))?;
284        let pix = cur.take(pix_len)?.to_vec();
285
286        chips.push(G00Chip {
287            x: cx,
288            y: cy,
289            width: w,
290            height: h,
291            sprite: ctype == 1,
292            data: G00ChipData::RawBgra(pix),
293        });
294    }
295
296    Ok(G00Cut {
297        width: cut_xl as u32,
298        height: cut_yl as u32,
299        center_x: xc,
300        center_y: yc,
301        disp_left: x,
302        disp_top: y,
303        disp_right: x.saturating_add(disp_xl),
304        disp_bottom: y.saturating_add(disp_yl),
305        chips,
306    })
307}
308
309impl G00Chip {
310    /// Decode chip pixels into a BGRA8 buffer (little-endian bytes).
311    pub fn decode_bgra(&self) -> Result<Vec<u8>> {
312        match &self.data {
313            G00ChipData::Type0Lzss32(blob) => {
314                let out = lzss_unpack32(blob)?;
315                let expected = (self.width as usize)
316                    .checked_mul(self.height as usize)
317                    .and_then(|v| v.checked_mul(4))
318                    .ok_or_else(|| anyhow!("g00: expected size overflow"))?;
319                if out.len() != expected {
320                    bail!(
321                        "g00: type0 size mismatch (got={}, expected={})",
322                        out.len(),
323                        expected
324                    );
325                }
326                Ok(out)
327            }
328            G00ChipData::Type1LzssIndexed(blob) => {
329                let dec = lzss_unpack(blob)?;
330                let mut cur = Cur::new(&dec);
331
332                let pal_cnt = cur.read_u16_le()? as usize;
333                if pal_cnt == 0 {
334                    bail!("g00: type1 pal_cnt=0");
335                }
336
337                let pal_bytes = pal_cnt
338                    .checked_mul(4)
339                    .ok_or_else(|| anyhow!("g00: palette size overflow"))?;
340                let pal_raw = cur.take(pal_bytes)?;
341
342                let expected_px = (self.width as usize)
343                    .checked_mul(self.height as usize)
344                    .ok_or_else(|| anyhow!("g00: index size overflow"))?;
345
346                if cur.remaining() < expected_px {
347                    bail!(
348                        "g00: type1 truncated indices (need={}, have={})",
349                        expected_px,
350                        cur.remaining()
351                    );
352                }
353
354                let indices = cur.take(expected_px)?;
355
356                // Palette entries are stored as u32 and copied directly to the output dwords in the original implementation.
357                // On little-endian, u32::to_le_bytes gives BGRA bytes.
358                let mut out: Vec<u8> = Vec::with_capacity(expected_px * 4);
359                for &ix in indices {
360                    let ix = ix as usize;
361                    if ix >= pal_cnt {
362                        bail!(
363                            "g00: type1 palette index out of range (ix={}, pal_cnt={})",
364                            ix,
365                            pal_cnt
366                        );
367                    }
368                    let base = ix * 4;
369                    out.extend_from_slice(&pal_raw[base..base + 4]);
370                }
371
372                Ok(out)
373            }
374            G00ChipData::RawBgra(pix) => {
375                let expected = (self.width as usize)
376                    .checked_mul(self.height as usize)
377                    .and_then(|v| v.checked_mul(4))
378                    .ok_or_else(|| anyhow!("g00: expected size overflow"))?;
379                if pix.len() != expected {
380                    bail!(
381                        "g00: raw size mismatch (got={}, expected={})",
382                        pix.len(),
383                        expected
384                    );
385                }
386                Ok(pix.clone())
387            }
388            G00ChipData::Jpeg(bytes) => {
389                let img = image::load_from_memory_with_format(bytes, ImageFormat::Jpeg)
390                    .or_else(|_| image::load_from_memory(bytes))
391                    .map_err(|e| anyhow!("g00: JPEG decode failed: {e}"))?;
392                let rgba = img.to_rgba8();
393                let (w, h) = rgba.dimensions();
394                if w != self.width || h != self.height {
395                    bail!(
396                        "g00: JPEG size mismatch (got={}x{}, expected={}x{})",
397                        w,
398                        h,
399                        self.width,
400                        self.height
401                    );
402                }
403                let mut out = rgba.into_raw();
404                bgra_to_rgba_in_place(&mut out);
405                // We swapped RGBA->BGRA in-place above; decode_bgra expects BGRA.
406                Ok(out)
407            }
408        }
409    }
410
411    /// Decode chip pixels into an RGBA8 buffer.
412    pub fn decode_rgba(&self) -> Result<Vec<u8>> {
413        let mut bgra = self.decode_bgra()?;
414        bgra_to_rgba_in_place(&mut bgra);
415        Ok(bgra)
416    }
417
418    pub fn jpeg_bytes(&self) -> Option<&[u8]> {
419        match &self.data {
420            G00ChipData::Jpeg(b) => Some(b),
421            _ => None,
422        }
423    }
424}
425
426fn bgra_to_rgba_in_place(buf: &mut [u8]) {
427    for px in buf.chunks_exact_mut(4) {
428        // BGRA -> RGBA
429        px.swap(0, 2);
430    }
431}