1use crate::assets::RgbaImage;
8use anyhow::{bail, Context, Result};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11#[repr(u8)]
12pub enum G00Type {
13 Type24bit = 0,
14 Type8bit = 1,
15 TypeDir = 2,
16 TypeJpeg = 3,
17}
18
19#[derive(Debug, Clone)]
20pub struct DecodedG00 {
21 pub kind: G00Type,
22 pub width: u32,
23 pub height: u32,
24 pub frames: Vec<RgbaImage>,
26}
27
28fn read_u16le(buf: &[u8], off: usize) -> Result<u16> {
29 if off + 2 > buf.len() {
30 bail!("read u16le out of bounds at {off}");
31 }
32 Ok(u16::from_le_bytes([buf[off], buf[off + 1]]))
33}
34
35fn read_u32le(buf: &[u8], off: usize) -> Result<u32> {
36 if off + 4 > buf.len() {
37 bail!("read u32le out of bounds at {off}");
38 }
39 Ok(u32::from_le_bytes([
40 buf[off],
41 buf[off + 1],
42 buf[off + 2],
43 buf[off + 3],
44 ]))
45}
46
47fn read_i32le(buf: &[u8], off: usize) -> Result<i32> {
48 if off + 4 > buf.len() {
49 bail!("read i32le out of bounds at {off}");
50 }
51 Ok(i32::from_le_bytes([
52 buf[off],
53 buf[off + 1],
54 buf[off + 2],
55 buf[off + 3],
56 ]))
57}
58
59pub fn decode_g00(data: &[u8]) -> Result<DecodedG00> {
61 if data.len() < 1 + 2 + 2 {
62 bail!("g00 too small");
63 }
64 let ty = data[0];
65 let width = read_u16le(data, 1)? as u32;
66 let height = read_u16le(data, 3)? as u32;
67 let kind = match ty {
68 0 => G00Type::Type24bit,
69 1 => G00Type::Type8bit,
70 2 => G00Type::TypeDir,
71 3 => G00Type::TypeJpeg,
72 other => bail!("unknown g00 type: {other}"),
73 };
74
75 let mut off = 5;
76
77 match kind {
78 G00Type::Type24bit => {
79 let decompress_length = read_u32le(data, off + 4)? as usize;
81 off += 8;
82 if off > data.len() {
83 bail!("g00 type0 header out of bounds");
84 }
85 if decompress_length == 0 {
86 bail!("g00 type0 decompress_length=0");
87 }
88 let mut out = vec![0u8; decompress_length];
89 lzss_decompress_24bit(&data[off..], &mut out).context("lzss_decompress_24bit")?;
90
91 let rgba = bgra_to_rgba_inplace(out);
93 Ok(DecodedG00 {
94 kind,
95 width,
96 height,
97 frames: vec![RgbaImage {
98 width,
99 height,
100 center_x: 0,
101 center_y: 0,
102 rgba,
103 }],
104 })
105 }
106 G00Type::Type8bit => {
107 let (mut out, out_len) =
109 real_live_type1_uncompress(&data[off..]).context("type1 uncompress")?;
110 if out_len == 0 {
111 bail!("type1 produced empty output");
112 }
113 out.truncate(out_len);
114 let rgba = bgra_to_rgba_inplace(out);
116 Ok(DecodedG00 {
117 kind,
118 width,
119 height,
120 frames: vec![RgbaImage {
121 width,
122 height,
123 center_x: 0,
124 center_y: 0,
125 rgba,
126 }],
127 })
128 }
129 G00Type::TypeDir => {
130 let index_entries = read_u32le(data, off)? as usize;
133 off += 4;
134 let g02_info_size = 24usize;
136 let skip = index_entries
137 .checked_mul(g02_info_size)
138 .context("index_entries overflow")?;
139 if off + skip > data.len() {
140 bail!("type2 g02_info_list out of bounds");
141 }
142 off += skip;
143
144 let decompress_length = read_u32le(data, off + 4)? as usize;
146 off += 8;
147 if decompress_length == 0 {
148 bail!("type2 decompress_length=0");
149 }
150 if off > data.len() {
151 bail!("type2 payload out of bounds");
152 }
153
154 let mut debuf = vec![0u8; decompress_length];
155 lzss_decompress(&data[off..], &mut debuf).context("lzss_decompress")?;
156
157 if debuf.len() < 4 {
159 bail!("type2 debuf too small");
160 }
161 let debuf_entries = read_u32le(&debuf, 0)? as usize;
162 let pairs_off = 4usize;
163 let pair_size = 8usize;
164 let pairs_bytes = debuf_entries
165 .checked_mul(pair_size)
166 .context("debuf_entries overflow")?;
167 if pairs_off + pairs_bytes > debuf.len() {
168 bail!("type2 pairs out of bounds");
169 }
170
171 let mut frames: Vec<RgbaImage> = Vec::with_capacity(debuf_entries);
172 for i in 0..debuf_entries {
173 let p_off = pairs_off + i * pair_size;
174 let offset = read_u32le(&debuf, p_off)? as usize;
175 let length_raw = read_u32le(&debuf, p_off + 4)? as i32;
176 if offset == 0 || length_raw == 0 || offset >= debuf.len() {
177 frames.push(transparent_missing_g00_cut());
178 continue;
179 }
180
181 let end = if length_raw > 0 {
186 offset.saturating_add(length_raw as usize).min(debuf.len())
187 } else {
188 debuf.len()
189 };
190 if end <= offset {
191 frames.push(transparent_missing_g00_cut());
192 continue;
193 }
194 let part_bytes = &debuf[offset..end];
195 let img = extract_g02_part(part_bytes)
196 .with_context(|| format!("extract g02 part idx={i}"))?;
197 frames.push(img);
198 }
199
200 if frames.is_empty() {
201 bail!("type2 produced no frames");
202 }
203
204 Ok(DecodedG00 {
205 kind,
206 width,
207 height,
208 frames,
209 })
210 }
211 G00Type::TypeJpeg => {
212 if off > data.len() {
213 bail!("g00 type3 header out of bounds");
214 }
215 let jpeg = &data[off..];
216 let img = image::load_from_memory_with_format(jpeg, image::ImageFormat::Jpeg)
217 .or_else(|_| image::load_from_memory(jpeg))
218 .context("decode g00 jpeg")?;
219 let rgba = img.to_rgba8();
220 let (w, h) = rgba.dimensions();
221 if w != width || h != height {
222 bail!("g00 jpeg size mismatch: got={w}x{h}, expect={width}x{height}");
223 }
224 Ok(DecodedG00 {
225 kind,
226 width,
227 height,
228 frames: vec![RgbaImage {
229 width,
230 height,
231 center_x: 0,
232 center_y: 0,
233 rgba: rgba.into_raw(),
234 }],
235 })
236 }
237 }
238}
239
240
241fn transparent_missing_g00_cut() -> RgbaImage {
242 RgbaImage {
243 width: 1,
244 height: 1,
245 center_x: 0,
246 center_y: 0,
247 rgba: vec![0, 0, 0, 0],
248 }
249}
250
251fn bgra_to_rgba_inplace(mut bgra: Vec<u8>) -> Vec<u8> {
252 for px in bgra.chunks_exact_mut(4) {
253 let b = px[0];
254 let g = px[1];
255 let r = px[2];
256 let a = px[3];
257 px[0] = r;
258 px[1] = g;
259 px[2] = b;
260 px[3] = a;
261 }
262 bgra
263}
264
265fn real_live_type1_uncompress(compr: &[u8]) -> Result<(Vec<u8>, usize)> {
266 if compr.len() < 8 {
267 bail!("type1 data too small");
268 }
269 let total_len = read_u32le(compr, 0)? as usize;
270 let uncomprlen = read_u32le(compr, 4)? as usize;
271 if total_len < 8 {
272 bail!("type1 total_len < 8");
273 }
274 if total_len > compr.len() {
275 bail!(
277 "type1 total_len out of bounds: total_len={total_len} buf={}",
278 compr.len()
279 );
280 }
281
282 if uncomprlen != 0 {
283 let mut out = vec![0u8; uncomprlen + 64];
284 let mut curbyte = 8usize;
285 let mut act = 0usize;
286 let mut bit_count = 0u8;
287 let mut flag = 0u8;
288
289 while act < uncomprlen && curbyte < total_len {
290 if bit_count == 0 {
291 flag = compr[curbyte];
292 curbyte += 1;
293 bit_count = 8;
294 }
295
296 if (flag & 1) != 0 {
297 if curbyte >= total_len {
298 break;
299 }
300 out[act] = compr[curbyte];
301 act += 1;
302 curbyte += 1;
303 } else {
304 if curbyte + 2 > total_len {
305 break;
306 }
307 let count0 = compr[curbyte] as usize;
308 let b1 = compr[curbyte + 1] as usize;
309 curbyte += 2;
310
311 let offset = (b1 << 4) | (count0 >> 4);
312 let count = (count0 & 0xF) + 2;
313 if offset == 0 {
314 bail!("type1 invalid offset=0");
315 }
316 if act < offset {
317 bail!("type1 backref before start: act={act} offset={offset}");
318 }
319 for _ in 0..count {
320 if act >= uncomprlen {
321 break;
322 }
323 let v = out[act - offset];
324 out[act] = v;
325 act += 1;
326 }
327 }
328
329 flag >>= 1;
330 bit_count = bit_count.saturating_sub(1);
331 }
332
333 Ok((out, uncomprlen))
334 } else {
335 let payload_len = total_len - 8;
336 let mut out = vec![0u8; payload_len];
337 out.copy_from_slice(&compr[8..8 + payload_len]);
338 Ok((out, payload_len))
339 }
340}
341
342fn lzss_decompress(src: &[u8], dst: &mut [u8]) -> Result<()> {
343 let mut s = 0usize;
344 let mut d = 0usize;
345 while d < dst.len() {
346 if s >= src.len() {
347 break;
348 }
349 let mut flags = src[s];
350 s += 1;
351 for _ in 0..8 {
352 if d >= dst.len() {
353 break;
354 }
355 if (flags & 1) != 0 {
356 if s >= src.len() {
357 break;
358 }
359 dst[d] = src[s];
360 d += 1;
361 s += 1;
362 } else {
363 if s + 2 > src.len() {
364 break;
365 }
366 let w = u16::from_le_bytes([src[s], src[s + 1]]) as usize;
367 s += 2;
368 let offset = w >> 4;
369 let count = (w & 0xF) + 2;
370 if offset == 0 {
371 bail!("lzss offset=0");
372 }
373 if d < offset {
374 bail!("lzss backref before start: d={d} offset={offset}");
375 }
376 for _ in 0..count {
377 if d >= dst.len() {
378 break;
379 }
380 let v = dst[d - offset];
381 dst[d] = v;
382 d += 1;
383 }
384 }
385 flags >>= 1;
386 }
387 }
388
389 if d != dst.len() {
390 bail!(
391 "lzss_decompress did not fill output: wrote {d} of {}",
392 dst.len()
393 );
394 }
395 Ok(())
396}
397
398fn lzss_decompress_24bit(src: &[u8], dst: &mut [u8]) -> Result<()> {
399 let mut s = 0usize;
401 let mut d = 0usize;
402 while d < dst.len() {
403 if s >= src.len() {
404 break;
405 }
406 let mut flags = src[s];
407 s += 1;
408 for _ in 0..8 {
409 if d >= dst.len() {
410 break;
411 }
412 if (flags & 1) != 0 {
413 if s + 3 > src.len() {
414 break;
415 }
416 if d + 4 > dst.len() {
417 bail!("lzss24 literal would overflow dst");
418 }
419 dst[d] = src[s];
421 dst[d + 1] = src[s + 1];
422 dst[d + 2] = src[s + 2];
423 dst[d + 3] = 0xFF;
424 d += 4;
425 s += 3;
426 } else {
427 if s + 2 > src.len() {
428 break;
429 }
430 let w = u16::from_le_bytes([src[s], src[s + 1]]) as usize;
431 s += 2;
432 let offset_bytes = (w >> 4) << 2; let dword_count = (w & 0xF) + 1;
434 let count_bytes = dword_count * 4;
435 if offset_bytes == 0 {
436 bail!("lzss24 offset_bytes=0");
437 }
438 if d < offset_bytes {
439 bail!("lzss24 backref before start: d={d} offset={offset_bytes}");
440 }
441 for _ in 0..count_bytes {
442 if d >= dst.len() {
443 break;
444 }
445 let v = dst[d - offset_bytes];
446 dst[d] = v;
447 d += 1;
448 }
449 }
450 flags >>= 1;
451 }
452 }
453
454 if d != dst.len() {
455 bail!(
456 "lzss_decompress_24bit did not fill output: wrote {d} of {}",
457 dst.len()
458 );
459 }
460 Ok(())
461}
462
463#[derive(Debug, Clone)]
464struct G02PartInfo {
465 part_type: u16,
466 block_count: u16,
467 hs_orig_x: i32,
468 hs_orig_y: i32,
469 width: u32,
470 height: u32,
471 screen_show_x: i32,
472 screen_show_y: i32,
473 full_part_width: u32,
474 full_part_height: u32,
475}
476
477#[derive(Debug, Clone)]
478struct G02BlockInfo {
479 orig_x: u16,
480 orig_y: u16,
481 _info: u16,
482 width: u16,
483 height: u16,
484}
485
486const G02_BLOCK_INFO_SIZE: usize = 92; fn parse_g02_block(buf: &[u8]) -> Result<G02BlockInfo> {
489 if buf.len() < G02_BLOCK_INFO_SIZE {
490 bail!("g02_block_info_t truncated");
491 }
492 let orig_x = read_u16le(buf, 0)?;
493 let orig_y = read_u16le(buf, 2)?;
494 let info = read_u16le(buf, 4)?;
495 let width = read_u16le(buf, 6)?;
496 let height = read_u16le(buf, 8)?;
497 Ok(G02BlockInfo {
498 orig_x,
499 orig_y,
500 _info: info,
501 width,
502 height,
503 })
504}
505
506fn parse_g02_part_info_prefix(buf: &[u8]) -> Result<G02PartInfo> {
507 if buf.len() < 0x24 {
517 bail!("g02_part_info prefix too small");
518 }
519 let part_type = read_u16le(buf, 0)?;
520 let block_count = read_u16le(buf, 2)?;
521 let disp_x = read_i32le(buf, 4)?;
522 let disp_y = read_i32le(buf, 8)?;
523 let disp_width = read_u32le(buf, 0x0C)?;
524 let disp_height = read_u32le(buf, 0x10)?;
525 let center_x = read_i32le(buf, 0x14)?;
526 let center_y = read_i32le(buf, 0x18)?;
527 let cut_width = read_u32le(buf, 0x1C)?;
528 let cut_height = read_u32le(buf, 0x20)?;
529 Ok(G02PartInfo {
530 part_type,
531 block_count,
532 hs_orig_x: disp_x,
533 hs_orig_y: disp_y,
534 width: cut_width,
535 height: cut_height,
536 screen_show_x: center_x,
537 screen_show_y: center_y,
538 full_part_width: disp_width,
539 full_part_height: disp_height,
540 })
541}
542
543fn fix_vertical_flip_bgra(width: u32, height: u32, buf: &mut [u8]) -> Result<()> {
544 let stride = width.checked_mul(4).context("stride overflow")? as usize;
545 let h = height as usize;
546 if buf.len() != stride * h {
547 bail!("fix_vertical_flip_bgra length mismatch");
548 }
549 let mut tmp = vec![0u8; buf.len()];
550 for y in 0..h {
551 let src_off = y * stride;
552 let dst_off = (h - 1 - y) * stride;
553 tmp[dst_off..dst_off + stride].copy_from_slice(&buf[src_off..src_off + stride]);
554 }
555 buf.copy_from_slice(&tmp);
556 Ok(())
557}
558
559fn extract_g02_part(part_bytes: &[u8]) -> Result<RgbaImage> {
560 let part = parse_g02_part_info_prefix(part_bytes).context("parse part prefix")?;
565 if part.width == 0 || part.height == 0 {
566 bail!("g02 part has zero full-cut dimensions");
567 }
568 if part.full_part_width == 0 || part.full_part_height == 0 {
569 bail!("g02 part has zero display dimensions");
570 }
571
572 let candidates: [usize; 10] = [0x74, 0x24, 0xD0, 0xC0, 0xE0, 0x80, 0x90, 0xA0, 0xB0, 0x100];
576
577 let mut chosen_header: Option<usize> = None;
578 for &hdr in &candidates {
579 if hdr > part_bytes.len() {
580 continue;
581 }
582 if validate_g02_layout(part_bytes, &part, hdr).is_ok() {
583 chosen_header = Some(hdr);
584 break;
585 }
586 }
587
588 let header_size = chosen_header.context("unable to determine g02_part_info header size")?;
589
590 let out_w = part.full_part_width;
591 let out_h = part.full_part_height;
592 let stride = (out_w as usize)
593 .checked_mul(4)
594 .context("stride overflow")?;
595 let mut dib = vec![0u8; stride * (out_h as usize)];
596
597 let mut off = header_size;
598 for _ in 0..part.block_count {
599 let block = parse_g02_block(&part_bytes[off..])?;
600 off += G02_BLOCK_INFO_SIZE;
601
602 let bw = block.width as usize;
603 let bh = block.height as usize;
604 if bw == 0 || bh == 0 {
605 bail!("g02 block zero size");
606 }
607 let px_len = bw
608 .checked_mul(bh)
609 .and_then(|v| v.checked_mul(4))
610 .context("pixel len overflow")?;
611 if off + px_len > part_bytes.len() {
612 bail!("g02 block pixel data out of bounds");
613 }
614 let src = &part_bytes[off..off + px_len];
615 off += px_len;
616
617 let dst_x_i = block.orig_x as i32 - part.hs_orig_x;
618 let dst_y_i = block.orig_y as i32 - part.hs_orig_y;
619 if dst_x_i < 0 || dst_y_i < 0 {
620 bail!(
621 "g02 block outside display rect: chip=({}, {}) disp=({}, {})",
622 block.orig_x,
623 block.orig_y,
624 part.hs_orig_x,
625 part.hs_orig_y
626 );
627 }
628 let dst_x = dst_x_i as usize;
629 let dst_y = dst_y_i as usize;
630 if dst_x.saturating_add(bw) > out_w as usize || dst_y.saturating_add(bh) > out_h as usize {
631 bail!(
632 "g02 block write outside display rect: dst=({}, {}) size={}x{} out={}x{}",
633 dst_x,
634 dst_y,
635 bw,
636 bh,
637 out_w,
638 out_h
639 );
640 }
641
642 for row in 0..bh {
643 let src_row_off = row * bw * 4;
644 let dst_row_off = (dst_y + row) * stride + dst_x * 4;
645 let dst_end = dst_row_off + bw * 4;
646 if dst_end > dib.len() {
647 bail!("g02 block write out of bounds");
648 }
649 dib[dst_row_off..dst_end].copy_from_slice(&src[src_row_off..src_row_off + bw * 4]);
650 }
651 }
652
653 let rgba = bgra_to_rgba_inplace(dib);
654
655 Ok(RgbaImage {
656 width: out_w,
657 height: out_h,
658 center_x: part.screen_show_x - part.hs_orig_x,
659 center_y: part.screen_show_y - part.hs_orig_y,
660 rgba,
661 })
662}
663
664fn validate_g02_layout(part_bytes: &[u8], part: &G02PartInfo, header_size: usize) -> Result<()> {
665 let mut off = header_size;
666 for _ in 0..part.block_count {
667 if off + G02_BLOCK_INFO_SIZE > part_bytes.len() {
668 bail!("block header out of bounds");
669 }
670 let block = parse_g02_block(&part_bytes[off..off + G02_BLOCK_INFO_SIZE])?;
671 off += G02_BLOCK_INFO_SIZE;
672
673 if block.width == 0 || block.height == 0 {
674 bail!("block zero size");
675 }
676 if (block.width as u32) > part.width || (block.height as u32) > part.height {
677 bail!("block larger than full cut");
678 }
679 if (block.orig_x as u32).saturating_add(block.width as u32) > part.width
680 || (block.orig_y as u32).saturating_add(block.height as u32) > part.height
681 {
682 bail!("block outside full cut");
683 }
684 let bw = block.width as usize;
687 let bh = block.height as usize;
688 let px_len = bw
689 .checked_mul(bh)
690 .and_then(|v| v.checked_mul(4))
691 .context("pixel len overflow")?;
692 if off + px_len > part_bytes.len() {
693 bail!("block pixels out of bounds");
694 }
695 off += px_len;
696 }
697 Ok(())
698}