1 // This file is part of Deark.
2 // Copyright (C) 2024 Jason Summers
3 // See the file COPYING for terms of use.
5 // MS Comic Chat .AVB, .BGB
7 #include <deark-private.h>
8 #include <deark-fmtutil.h>
9 DE_DECLARE_MODULE(de_module_comicchat
);
11 typedef struct localctx_comicchat
{
12 de_encoding input_encoding
;
17 struct de_inthashtable
*images_seen
;
20 de_ucstring
*char_name
;
26 i64 ck_pos
; // everything; starting from start of type field
27 i64 ck_len
; // total length from ck_pos
30 struct image_lowlevel_ctx
{
36 struct image_highlevel_ctx
{
37 u8 special_2bpp_transparency
;
39 // [0] is the foreground image.
40 // [1] and [2] are optional transparency masks.
41 // Sometimes the fg image has transparency, in which case we wouldn't expect
42 // the masks to exist.
43 struct image_lowlevel_ctx llimg
[3];
46 struct image_extract_ctx
{
54 static void fixup_2bpp_transparency(deark
*c
, lctx
*d
, struct image_highlevel_ctx
*ih
)
60 oldimg
= ih
->llimg
[0].img
;
62 newimg
= de_bitmap_create(c
, oldimg
->width
, oldimg
->height
, 2);
63 for(j
=0; j
<oldimg
->height
; j
++) {
64 for(i
=0; i
<oldimg
->width
; i
++) {
67 clr
= de_bitmap_getpixel(oldimg
, i
, j
);
68 if(clr
==DE_MAKE_GRAY(128)) clr
= DE_MAKE_RGBA(128,128,128,0);
69 de_bitmap_setpixel_rgba(newimg
, i
, j
, clr
);
73 de_bitmap_destroy(ih
->llimg
[0].img
);
74 ih
->llimg
[0].img
= newimg
;
77 static void read_bmp_to_img(deark
*c
, lctx
*d
, dbuf
*inf
, i64 pos1
,
78 struct image_highlevel_ctx
*ih
, int itype
)
80 de_module_params
*mparams
= NULL
;
81 struct fmtutil_bmp_mparams_indata
*idata
= NULL
;
83 int saved_indent_level
;
85 de_dbg_indent_save(c
, &saved_indent_level
);
87 bmplen
= dbuf_getu32le(inf
, pos1
+2);
88 if(pos1
+bmplen
> inf
->len
) goto done
;
89 if(bmplen
< 54) goto done
;
91 de_dbg(c
, "decoding BMP");
93 mparams
= de_malloc(c
, sizeof(de_module_params
));
94 idata
= de_malloc(c
, sizeof(struct fmtutil_bmp_mparams_indata
));
95 mparams
->in_params
.flags
= 0x2;
96 mparams
->in_params
.obj1
= (void*)idata
;
97 de_run_module_by_id_on_slice(c
, "bmp", mparams
, inf
, pos1
, bmplen
);
99 ih
->llimg
[itype
].img
= idata
->img
;
101 ih
->llimg
[itype
].fi
= idata
->fi
;
103 ih
->llimg
[itype
].createflags
= idata
->createflags
;
106 de_err(c
, "Failed to decode BMP image");
110 if(itype
==0 && ih
->special_2bpp_transparency
) {
111 fixup_2bpp_transparency(c
, d
, ih
);
116 de_bitmap_destroy(idata
->img
);
117 de_finfo_destroy(c
, idata
->fi
);
123 de_dbg_indent_restore(c
, saved_indent_level
);
126 static void write_palette(deark
*c
, lctx
*d
, dbuf
*outf
,
127 de_color
*pal
, i64 num_entries
)
131 for(i
=0; i
<num_entries
; i
++) {
132 dbuf_writebyte(outf
, DE_COLOR_B(pal
[i
]));
133 dbuf_writebyte(outf
, DE_COLOR_G(pal
[i
]));
134 dbuf_writebyte(outf
, DE_COLOR_R(pal
[i
]));
135 dbuf_writebyte(outf
, 0);
139 static void reconstruct_bmp_and_cvt_to_img(deark
*c
, lctx
*d
,
140 struct image_extract_ctx
*ic
, struct image_highlevel_ctx
*ih
, int itype
)
144 tmpbmp
= dbuf_create_membuf(c
, 0, 0);
145 fmtutil_generate_bmpfileheader(c
, tmpbmp
, &ic
->bi
, 0);
146 dbuf_copy(c
->infile
, ic
->ihpos
, ic
->bi
.infohdrsize
, tmpbmp
);
147 write_palette(c
, d
, tmpbmp
, ic
->pal
, ic
->bi
.pal_entries
);
148 dbuf_copy(ic
->unc_pixels
, 0, ic
->unc_pixels
->len
, tmpbmp
);
150 read_bmp_to_img(c
, d
, tmpbmp
, 0, ih
, itype
);
154 static void read_dib_to_img(deark
*c
, lctx
*d
, i64 pos1
, UI magic
,
155 struct image_highlevel_ctx
*ih
, int itype
)
160 i64 orig_len
, cmpr_len
;
162 int saved_indent_level
;
163 struct image_extract_ctx
*ic
= NULL
;
164 struct de_dfilter_results dres
;
165 struct de_dfilter_in_params dcmpri
;
166 struct de_dfilter_out_params dcmpro
;
167 struct de_deflate_params deflateparams
;
169 de_dbg_indent_save(c
, &saved_indent_level
);
170 ic
= de_malloc(c
, sizeof(struct image_extract_ctx
));
171 de_zeromem(&deflateparams
, sizeof(struct de_deflate_params
));
172 de_dfilter_init_objects(c
, &dcmpri
, &dcmpro
, &dres
);
178 // The palette is formatted like a standard chunk.
179 // TODO: Ideally, we'd use shared chunk-reading code here.
180 de_dbg(c
, "palette chunk at %"I64_FMT
, pos1
);
182 pos
+= 2; // 0x0101 tag, already read
183 ck_len
= de_getu16le_p(&pos
);
184 ic
->pal_num_entries
= de_getu16le_p(&pos
);
185 de_dbg(c
, "num entries: %"I64_FMT
, ic
->pal_num_entries
);
186 if(ic
->pal_num_entries
>256) goto done
;
188 // Note that palette is RGB, not BGR as might be expected.
189 if(ic
->pal_num_entries
>0) {
190 de_read_simple_palette(c
, c
->infile
, pos
, ic
->pal_num_entries
, 3,
191 ic
->pal
, 256, DE_RDPALTYPE_24BIT
, 0);
193 pos
= pos1
+ 4 + ck_len
;
194 de_dbg_indent(c
, -1);
198 magic2
= (UI
)de_getu16be(ic
->ihpos
);
199 if(magic2
!= 0x2800) {
200 de_err(c
, "Image not found at %"I64_FMT
, ic
->ihpos
);
204 de_dbg(c
, "infoheader at %"I64_FMT
, ic
->ihpos
);
206 ret
= fmtutil_get_bmpinfo(c
, c
->infile
, &ic
->bi
, ic
->ihpos
,
207 c
->infile
->len
- ic
->ihpos
, 0);
208 de_dbg_indent(c
, -1);
210 if(ic
->bi
.infohdrsize
!= 40) goto done
;
211 if(ic
->bi
.pal_entries
> 256) goto done
;
212 // We don't expect the zlib decompression to result in an image that is still
213 // (RLE) compressed. We could support it, but we probably need samples.
214 if(ic
->bi
.is_compressed
) goto done
;
215 if(!de_good_image_dimensions(c
, ic
->bi
.width
, ic
->bi
.height
)) goto done
;
217 if(ic
->bi
.bitcount
==1 && itype
>0) {
218 ic
->pal_num_entries
= 2;
219 // 0 is transparent, and the palette is undefined.
220 // It'd be logical to make 0 black, but we make it white, so that masks
221 // from all format versions are white-is-transparent.
222 de_make_grayscale_palette(ic
->pal
, ic
->pal_num_entries
, 0x1);
224 else if(ic
->bi
.bitcount
==2 && itype
==0 && ic
->pal_num_entries
==0) {
225 ih
->special_2bpp_transparency
= 1;
226 ic
->pal_num_entries
= 4;
227 ic
->pal
[0] = DE_MAKE_GRAY(128); // Arbitrary key color. Will be made transparent.
228 // Sometimes pal[1] is used for the halo around the character, so it might
229 // be interesting to make it partially transparent or something.
230 // But unfortunately, other times the halo is pal[2].
231 ic
->pal
[1] = DE_MAKE_GRAY(254); // alternate white foreground
232 ic
->pal
[2] = DE_STOCKCOLOR_WHITE
; // normal white foregreound
233 ic
->pal
[3] = DE_STOCKCOLOR_BLACK
; // normal black foreground
236 pos
= ic
->ihpos
+ ic
->bi
.infohdrsize
;
237 orig_len
= de_getu32le_p(&pos
);
238 de_dbg(c
, "orig len: %"I64_FMT
, orig_len
);
239 cmpr_len
= de_getu32le_p(&pos
);
240 de_dbg(c
, "cmpr len: %"I64_FMT
, cmpr_len
);
242 de_dbg(c
, "cmpr data at %"I64_FMT
, cmpr_pos
);
243 if(cmpr_pos
+ cmpr_len
> c
->infile
->len
) goto done
;
245 ic
->unc_pixels
= dbuf_create_membuf(c
, 0, 0);
246 dcmpri
.f
= c
->infile
;
247 dcmpri
.pos
= cmpr_pos
;
248 dcmpri
.len
= cmpr_len
;
249 dcmpro
.f
= ic
->unc_pixels
;
250 dcmpro
.expected_len
= de_min_int(orig_len
, ic
->bi
.foreground_size
);
251 dcmpro
.len_known
= 1;
252 deflateparams
.flags
= DE_DEFLATEFLAG_ISZLIB
;
253 de_dbg(c
, "[decompressing]");
255 fmtutil_deflate_codectype1(c
, &dcmpri
, &dcmpro
, &dres
, (void*)&deflateparams
);
256 de_dbg_indent(c
, -1);
257 dbuf_flush(ic
->unc_pixels
);
258 if(dres
.errcode
!= 0) {
259 de_err(c
, "%s", de_dfilter_get_errmsg(c
, &dres
));
263 // This will ultimately result in the BMP header & palette being read
264 // again (by the bmp module this time), dbg info printed again, etc.
265 // It's messy, but it will do.
266 reconstruct_bmp_and_cvt_to_img(c
, d
, ic
, ih
, itype
);
270 dbuf_close(ic
->unc_pixels
);
273 de_dbg_indent_restore(c
, saved_indent_level
);
276 // Read an image component, decode it in whatever way required,
277 // and store it at ih->llimg[itype].
278 static void read_image_lowlevel(deark
*c
, lctx
*d
, i64 pos1
,
279 struct image_highlevel_ctx
*ih
, int itype
)
282 int saved_indent_level
;
283 de_dbg_indent_save(c
, &saved_indent_level
);
285 if(pos1
==0) goto done
;
287 de_dbg(c
, "[image component at %"I64_FMT
"]", pos1
);
290 magic
= (UI
)de_getu16be(pos1
);
291 de_dbg(c
, "sig: 0x%04x", magic
);
292 if(magic
!=0x0101 && magic
!=0x2800 && magic
!=0x424d) {
293 de_err(c
, "Image not found at %"I64_FMT
, pos1
);
298 read_bmp_to_img(c
, d
, c
->infile
, pos1
, ih
, itype
);
301 read_dib_to_img(c
, d
, pos1
, magic
, ih
, itype
);
305 de_dbg_indent_restore(c
, saved_indent_level
);
308 static void emit_images_highlevel_separate(deark
*c
, lctx
*d
,
309 struct image_highlevel_ctx
*ih
)
313 if(ih
->llimg
[0].img
) {
315 de_finfo_set_name_from_sz(c
, ih
->llimg
[0].fi
,
316 "icon", 0, DE_ENCODING_LATIN1
);
318 de_bitmap_write_to_file_finfo(ih
->llimg
[0].img
, ih
->llimg
[0].fi
,
319 ih
->llimg
[0].createflags
);
321 for(k
=1; k
<=2; k
++) {
322 if(ih
->llimg
[k
].img
) {
323 de_finfo_set_name_from_sz(c
, ih
->llimg
[k
].fi
,
324 (k
==1?"sm_mask":"lg_mask"), 0, DE_ENCODING_LATIN1
);
325 de_bitmap_write_to_file_finfo(ih
->llimg
[k
].img
, ih
->llimg
[k
].fi
,
326 (ih
->llimg
[k
].createflags
| DE_CREATEFLAG_IS_AUX
));
331 static void emit_images_highlevel_applymasks(deark
*c
, lctx
*d
,
332 struct image_highlevel_ctx
*ih
)
334 de_bitmap
*tmpimg
= NULL
;
338 if(!ih
->llimg
[0].img
) goto done
; // should be impossible
339 w
= ih
->llimg
[0].img
->width
;
340 h
= ih
->llimg
[0].img
->height
;
342 if(!ih
->llimg
[1].img
&& !ih
->llimg
[2].img
) { // should be impossible
343 emit_images_highlevel_separate(c
, d
, ih
);
347 for(k
=1; k
<=2; k
++) {
348 if(!ih
->llimg
[k
].img
) continue;
350 de_bitmap_destroy(tmpimg
);
352 tmpimg
= de_bitmap_create(c
, w
, h
, 4);
353 de_bitmap_copy_rect(ih
->llimg
[0].img
, tmpimg
, 0, 0, w
, h
, 0, 0, 0);
354 de_bitmap_apply_mask(tmpimg
, ih
->llimg
[k
].img
, DE_BITMAPFLAG_WHITEISTRNS
);
355 de_bitmap_write_to_file_finfo(tmpimg
, ih
->llimg
[0].fi
,
356 ih
->llimg
[0].createflags
);
360 de_bitmap_destroy(tmpimg
);
363 // We can either extract the foreground and masks to individual files, or
364 // apply the masks. For us to apply the masks, certain conditions must be met.
365 static int should_apply_masks(deark
*c
, lctx
*d
, struct image_highlevel_ctx
*ih
)
369 if(!d
->opt_applymasks
) return 0;
370 if(ih
->special_2bpp_transparency
) return 0;
371 if(!ih
->llimg
[0].img
) return 0;
372 if(!ih
->llimg
[1].img
&& !ih
->llimg
[2].img
) return 0;
374 for(k
=1; k
<=2; k
++) {
375 if(ih
->llimg
[k
].img
) {
376 if(ih
->llimg
[k
].img
->width
!= ih
->llimg
[0].img
->width
) return 0;
377 if(ih
->llimg
[k
].img
->height
!= ih
->llimg
[0].img
->height
) return 0;
383 static void extract_image_highlevel(deark
*c
, lctx
*d
, i64 imgpos
, i64 m1pos
, i64 m2pos
,
386 struct image_highlevel_ctx
*ih
= NULL
;
389 ih
= de_malloc(c
, sizeof(struct image_highlevel_ctx
));
391 de_dbg(c
, "[extracting image at %"I64_FMT
" : %"I64_FMT
" : %"I64_FMT
"]",
392 imgpos
, m1pos
, m2pos
);
394 read_image_lowlevel(c
, d
, imgpos
, ih
, 0);
395 read_image_lowlevel(c
, d
, m1pos
, ih
, 1);
396 read_image_lowlevel(c
, d
, m2pos
, ih
, 2);
398 ih
->is_icon
= is_icon
;
400 if(!ih
->llimg
[k
].fi
) {
401 ih
->llimg
[k
].fi
= de_finfo_create(c
);
403 ih
->llimg
[k
].createflags
|= DE_CREATEFLAG_OPT_IMAGE
;
406 if(should_apply_masks(c
, d
, ih
)) {
407 emit_images_highlevel_applymasks(c
, d
, ih
);
410 emit_images_highlevel_separate(c
, d
, ih
);
412 de_dbg_indent(c
, -1);
416 de_bitmap_destroy(ih
->llimg
[k
].img
);
417 de_finfo_destroy(c
, ih
->llimg
[k
].fi
);
423 static int found_image(deark
*c
, lctx
*d
, struct chunk_ctx
*cctx
,
424 i64 imgpos
, i64 m1pos
, i64 m2pos
)
430 if(imgpos
) imgpos
+= d
->img_ptr_bias
;
431 if(m1pos
) m1pos
+= d
->img_ptr_bias
;
432 if(m2pos
) m2pos
+= d
->img_ptr_bias
;
433 de_dbg(c
, "pointer: image at %"I64_FMT
", mask1 at %"I64_FMT
", mask2 at %"I64_FMT
,
434 imgpos
, m1pos
, m2pos
);
435 if(m1pos
==m2pos
) m1pos
= 0;
436 if(imgpos
==0 || imgpos
>=c
->infile
->len
|| m1pos
>=c
->infile
->len
||
437 m2pos
>=c
->infile
->len
)
443 ret
= de_inthashtable_add_item(c
, d
->images_seen
, imgpos
, NULL
);
445 de_dbg(c
, "[already handled this image]");
449 is_icon
= (cctx
->ck_type
==0x0003 || cctx
->ck_type
==0x0100);
450 extract_image_highlevel(c
, d
, imgpos
, m1pos
, m2pos
, is_icon
);
456 static void handle_chunk_1imageptr(deark
*c
, lctx
*d
, struct chunk_ctx
*cctx
, i64 offset
)
460 n
= de_getu32le(cctx
->ck_pos
+ offset
);
461 found_image(c
, d
, cctx
, n
, 0, 0);
464 static void handle_chunk_multiimageptr(deark
*c
, lctx
*d
, struct chunk_ctx
*cctx
,
465 i64 offset
, i64 item_size
, i64 item_count
)
470 de_dbg(c
, "item count: %"I64_FMT
, item_count
);
471 pos
= cctx
->ck_pos
+ offset
;
473 for(i
=0; i
<item_count
; i
++) {
476 n
= de_getu32le(pos
);
477 m1
= de_getu32le(pos
+4);
478 m2
= de_getu32le(pos
+8);
479 if(!found_image(c
, d
, cctx
, n
, m1
, m2
)) goto done
;
487 static void handle_chunk_string(deark
*c
, lctx
*d
, i64 pos
, i64 len
,
488 de_ucstring
*s
, const char *name
)
491 dbuf_read_to_ucstring_n(c
->infile
, pos
, len
, 512, s
,
492 DE_CONVFLAG_STOP_AT_NUL
, d
->input_encoding
);
493 de_dbg(c
, "%s: \"%s\"", name
, ucstring_getpsz_d(s
));
496 // sets d->last_chunklen (=total size)
497 // may set d->stopflag, ...
498 static void comicchat_read_chunk(deark
*c
, lctx
*d
, i64 pos1
)
500 struct chunk_ctx
*cctx
= NULL
;
504 int saved_indent_level
;
506 de_dbg_indent_save(c
, &saved_indent_level
);
507 cctx
= de_malloc(c
, sizeof(struct chunk_ctx
));
509 d
->last_chunklen
= 0;
510 if(cctx
->ck_pos
+2 > c
->infile
->len
) goto done
;
511 cctx
->ck_type
= (UI
)de_getu16le(cctx
->ck_pos
);
512 de_dbg(c
, "chunk at %"I64_FMT
", type=0x%04x", cctx
->ck_pos
, (UI
)cctx
->ck_type
);
515 if(cctx
->ck_type
==0x0001) {
516 ret
= dbuf_search_byte(c
->infile
, 0x00, cctx
->ck_pos
+2, 4096, &foundpos
);
518 cctx
->ck_len
= foundpos
+ 1 - cctx
->ck_pos
;
519 handle_chunk_string(c
, d
, cctx
->ck_pos
+2, cctx
->ck_len
-2,
520 d
->char_name
, "char name");
523 else if(cctx
->ck_type
==0x0002) {
526 else if(cctx
->ck_type
==0x0003) {
528 handle_chunk_1imageptr(c
, d
, cctx
, 2);
530 else if(cctx
->ck_type
==0x0004) {
531 n
= de_getu16le(cctx
->ck_pos
+2);
532 cctx
->ck_len
= 4 + 43*n
;
533 handle_chunk_multiimageptr(c
, d
, cctx
, 4, 43, n
);
535 else if(cctx
->ck_type
==0x0005 || cctx
->ck_type
==0x0009) {
536 n
= de_getu16le(cctx
->ck_pos
+2);
537 cctx
->ck_len
= 4 + 35*n
;
538 handle_chunk_multiimageptr(c
, d
, cctx
, 4, 35, n
);
540 else if(cctx
->ck_type
==0x0006 || cctx
->ck_type
==0x0007) {
545 else if(cctx
->ck_type
==0x0008) {
548 else if(cctx
->ck_type
==0x000a) {
549 n
= de_getu16le(cctx
->ck_pos
+2);
550 cctx
->ck_len
= 4 + 33*n
;
551 handle_chunk_multiimageptr(c
, d
, cctx
, 4, 33, n
);
553 else if(cctx
->ck_type
==0x000b || cctx
->ck_type
==0x000c) {
554 n
= de_getu16le(cctx
->ck_pos
+2);
555 cctx
->ck_len
= 4 + 25*n
;
556 handle_chunk_multiimageptr(c
, d
, cctx
, 4, 25, n
);
558 else if(cctx
->ck_type
>=0x0100 && cctx
->ck_type
<=0x01ff) {
561 dlen_field
= de_getu16le(cctx
->ck_pos
+2);
562 cctx
->ck_len
= dlen_field
+ 4;
564 if(cctx
->ck_type
==0x0100 || cctx
->ck_type
==0x0102) {
565 handle_chunk_1imageptr(c
, d
, cctx
, 4);
567 else if(cctx
->ck_type
==0x0103) {
568 handle_chunk_string(c
, d
, cctx
->ck_pos
+4, cctx
->ck_len
-4, d
->tmpstr
,
571 else if(cctx
->ck_type
==0x0104) {
572 handle_chunk_string(c
, d
, cctx
->ck_pos
+4, cctx
->ck_len
-4, d
->tmpstr
, "url1");
574 else if(cctx
->ck_type
==0x0105) {
575 handle_chunk_string(c
, d
, cctx
->ck_pos
+4, cctx
->ck_len
-4, d
->tmpstr
, "url2");
577 else if(cctx
->ck_type
==0x0107) {
578 n
= de_getu32le(cctx
->ck_pos
+4);
579 d
->img_ptr_bias
+= n
;
580 de_sanitize_offset(&d
->img_ptr_bias
);
581 de_dbg(c
, "image pointer bias: %"I64_FMT
, n
);
586 de_err(c
, "Invalid file or unsupported chunk (0x%04x); can't continue",
592 de_dbg(c
, "chunk len: %"I64_FMT
, cctx
->ck_len
);
593 if(c
->debug_level
>=3) {
594 de_dbg_hexdump(c
, c
->infile
, cctx
->ck_pos
, cctx
->ck_len
, 256, NULL
, 0x1);
596 d
->last_chunklen
= cctx
->ck_len
;
600 de_dbg_indent_restore(c
, saved_indent_level
);
603 static void de_run_comicchat(deark
*c
, de_module_params
*mparams
)
609 d
= de_malloc(c
, sizeof(lctx
));
610 d
->char_name
= ucstring_create(c
);
611 d
->tmpstr
= ucstring_create(c
);
612 d
->input_encoding
= de_get_input_encoding(c
, NULL
, DE_ENCODING_WINDOWS1252
);
613 d
->opt_applymasks
= de_get_ext_option_bool(c
, "comicchat:applymasks", 1);
615 de_dbg(c
, "header at %"I64_FMT
, pos
);
618 d
->fmt_code
= (UI
)de_getu16le_p(&pos
);
619 d
->major_ver
= (UI
)de_getu16le_p(&pos
);
620 if(d
->major_ver
==1) tstr
= "v1.0-2.1";
621 else if(d
->major_ver
==2) tstr
= "v2.5";
623 de_dbg(c
, "fmt ver: %u (%s)", d
->major_ver
, tstr
);
624 if(d
->major_ver
==2 && d
->fmt_code
==3) tstr
= " (BGB)";
626 de_dbg(c
, "fmt subtype: %u%s", d
->fmt_code
, tstr
);
627 de_dbg_indent(c
, -1);
629 d
->images_seen
= de_inthashtable_create(c
);
631 comicchat_read_chunk(c
, d
, pos
);
632 if(d
->stopflag
|| d
->last_chunklen
==0) goto done
;
633 pos
+= d
->last_chunklen
;
638 ucstring_destroy(d
->char_name
);
639 ucstring_destroy(d
->tmpstr
);
640 de_inthashtable_destroy(c
, d
->images_seen
);
645 static int de_identify_comicchat(deark
*c
)
647 u8 has_ext
, has_hdr
, has_endtag
;
650 n0
= (UI
)de_getu16be(0);
651 if(n0
!=0x8100 && n0
!=0x8181) return 0;
652 n2
= (UI
)de_getu16le(2);
653 n4
= (UI
)de_getu16le(4);
657 if(n4
==1 && (n2
==1 || n2
==2)) has_hdr
= 1;
660 if(n4
==2 && (n2
>=1 && n2
<=3)) has_hdr
= 1;
662 if(!has_hdr
) return 0;
664 has_endtag
= (de_getu16le(c
->infile
->len
- 2) == 0x0007);
665 has_ext
= (de_input_file_has_ext(c
, "avb") ||
666 de_input_file_has_ext(c
, "bgb"));
667 if(has_endtag
&& has_ext
) return 100;
668 if(has_endtag
|| has_ext
) return 55;
672 static void de_help_comicchat(deark
*c
)
674 de_msg(c
, "-opt comicchat:applymasks=0 : Write masks to separate files");
677 void de_module_comicchat(deark
*c
, struct deark_module_info
*mi
)
679 mi
->id
= "comicchat";
680 mi
->desc
= "Microsoft Comic Chat AVB or BGB";
681 mi
->run_fn
= de_run_comicchat
;
682 mi
->identify_fn
= de_identify_comicchat
;
683 mi
->help_fn
= de_help_comicchat
;