1 // This file is part of Deark.
2 // Copyright (C) 2024 Jason Summers
3 // See the file COPYING for terms of use.
5 // RIFF Multimedia Movie / RMMP / .MMM
7 #include <deark-private.h>
8 #include <deark-fmtutil.h>
9 DE_DECLARE_MODULE(de_module_mmm
);
11 #define CODE_CFTC 0x43465443U
12 #define CODE_CLUT 0x434c5554U
13 #define CODE_CURS 0x43555253U
14 #define CODE_DIB 0x44494220U
15 #define CODE_McNm 0x4d634e6dU
16 #define CODE_RIFF 0x52494646U
17 #define CODE_RMMP 0x524d4d50U
18 #define CODE_SCVW 0x53435657U
19 #define CODE_STR 0x53545220U
20 #define CODE_STXT 0x53545854U
21 #define CODE_VWAC 0x56574143U
22 #define CODE_VWCF 0x56574346U
23 #define CODE_VWCR 0x56574352U
24 #define CODE_VWFM 0x5657464dU
25 #define CODE_VWLB 0x56574c42U
26 #define CODE_VWSC 0x56575343U
27 #define CODE_VWTL 0x5657544cU
28 #define CODE_VWtc 0x56577463U
29 #define CODE_Ver_ 0x5665722eU // "Ver."
30 #define CODE_cftc 0x63667463U
31 #define CODE_clut 0x636c7574U
32 #define CODE_dib 0x64696220U
33 #define CODE_mcnm 0x6d636e6dU
34 #define CODE_snd 0x736e6420U // Not sure if 'SND' exists
35 #define CODE_str 0x73747220U
36 #define CODE_scvw 0x73637677U
37 #define CODE_stxt 0x73747874U
38 #define CODE_ver 0x76657220U // "ver "
39 #define CODE_vwac 0x76776163U
40 #define CODE_vwcf 0x76776366U
41 #define CODE_vwcr 0x76776372U
42 #define CODE_vwfm 0x7677666dU
43 #define CODE_vwlb 0x76776c62U
44 #define CODE_vwsc 0x76777363U
45 #define CODE_vwtc 0x76777463U
46 #define CODE_vwtl 0x7677746cU
49 int pass
; // 100=reading alt. palette file
52 u8 force_pal
; // Set if mmm:palid option was used
53 int pal_id_to_use
; // Valid if mmm:palid option was used
54 de_encoding input_encoding
;
57 u8 suppress_dib_pass2
;
63 de_ucstring
*tmp_namestr
;
68 static void mmm_default_pal256(deark
*c
, struct mmm_ctx
*d
)
71 static const u8 v
[6] = {0, 48, 100, 152, 204, 252};
73 de_warn(c
, "Using a default palette. Colors might be wrong.");
75 // Note: This sets pal[40] incorrectly, but it will be fixed later.
79 d
->pal
[40 + i
*36 + j
*6 + k
] = DE_MAKE_RGB(v
[i
], v
[j
], v
[k
]);
84 for(i
=0; i
<=10; i
++) {
85 d
->pal
[i
] = DE_MAKE_GRAY(i
*24);
93 d
->pal
[11+i
] = DE_MAKE_RGB(0, 0, t
);
94 d
->pal
[21+i
] = DE_MAKE_RGB(0, t
, 0);
95 d
->pal
[31+i
] = DE_MAKE_RGB(t
, 0, 0);
101 static void mmm_allowbad_note(deark
*c
)
103 de_info(c
, "Note: Use \"-opt mmm:allowbad\" to extract anyway.");
106 static void mmm_default_pal16(deark
*c
, struct mmm_ctx
*d
)
108 de_make_grayscale_palette(d
->pal
, 16, 0);
111 // TODO: Figure out if there is a default 16-color palette.
112 if(d
->opt_allowbad
) {
113 de_warn(c
, "No palette found. Colors will be wrong.");
116 de_err(c
, "No palette found");
117 mmm_allowbad_note(c
);
122 // Read rsrc id to d->tmp_rsrcid
123 static void mmm_read_rsrcid_p(deark
*c
, struct mmm_ctx
*d
, struct de_iffctx
*ictx
,
124 i64
*ppos
, const char *name
)
126 // Note: I can't rule out the possibility that this is a 16-bit field (like
127 // in macrsrc format), followed by a 16-bit field that is always either 0
128 // or 0xffff. I haven't found any resources for which it would make a
129 // difference if we read it as 16-bit.
130 d
->tmp_rsrcid
= (int)dbuf_geti32le_p(ictx
->f
, ppos
);
131 de_dbg(c
, "%s: %d", (name
? name
: "rsrc id"), d
->tmp_rsrcid
);
134 // Read chunk name to d->tmp_namestr
135 static void mmm_read_name_p(deark
*c
, struct mmm_ctx
*d
, struct de_iffctx
*ictx
,
136 i64
*ppos
, const char *name
)
141 ucstring_empty(d
->tmp_namestr
);
142 namelen
= (i64
)dbuf_getbyte(ictx
->f
, pos
);
144 dbuf_read_to_ucstring(ictx
->f
, pos
+1, namelen
, d
->tmp_namestr
, 0, d
->input_encoding
);
145 de_dbg(c
, "%s: \"%s\"", (name
? name
: "name"), ucstring_getpsz_d(d
->tmp_namestr
));
147 *ppos
+= de_pad_to_2(1+namelen
);
150 static void do_mmm_mcnm(deark
*c
, struct mmm_ctx
*d
, struct de_iffctx
*ictx
,
155 mmm_read_name_p(c
, d
, ictx
, &pos
, "movie name");
158 static void do_mmm_ver(deark
*c
, struct mmm_ctx
*d
, struct de_iffctx
*ictx
,
163 ver
= (UI
)dbuf_getu32le(ictx
->f
, dpos1
);
164 de_dbg(c
, "version: %u", ver
);
167 static void do_mmm_cftc(deark
*c
, struct mmm_ctx
*d
, struct de_iffctx
*ictx
,
173 struct de_fourcc tmp4cc
;
175 if(dlen
<4) goto done
;
176 nentries
= (dlen
-4)/16;
179 for(i
=0; i
<nentries
; i
++) {
183 de_zeromem(&tmp4cc
, sizeof(struct de_fourcc
));
184 dbuf_read_fourcc(ictx
->f
, pos
, &tmp4cc
, 4, 0);
186 if(tmp4cc
.id
==0) break;
187 chk_dlen
= dbuf_getu32le_p(ictx
->f
, &pos
);
188 chk_id
= (int)dbuf_geti32le_p(ictx
->f
, &pos
);
189 chk_pos
= dbuf_getu32le_p(ictx
->f
, &pos
);
190 de_dbg(c
, "toc entry '%s' id=%d pos=%"I64_FMT
" dlen=%"I64_FMT
,
191 tmp4cc
.id_sanitized_sz
, chk_id
, chk_pos
, chk_dlen
);
198 // TODO?: This duplicates some code in the macrsrc module.
199 // What we probably ought to do is generate a macrsrc file containing all the
200 // cursor and icon resources, and any other suitable resources. But that's
201 // easier said than done. Macrsrc is a complex format, and some things may
202 // have been lost in the translation to MMM format.
203 static void do_mmm_CURS(deark
*c
, struct mmm_ctx
*d
, struct de_iffctx
*ictx
, i64 pos1
, i64 len
)
206 de_bitmap
*img_fg
= NULL
;
207 de_bitmap
*img_mask
= NULL
;
210 mmm_read_rsrcid_p(c
, d
, ictx
, &pos
, NULL
);
211 mmm_read_name_p(c
, d
, ictx
, &pos
, NULL
);
212 if(pos1
+len
-pos
< 68) goto done
;
214 fi
= de_finfo_create(c
);
216 img_fg
= de_bitmap_create(c
, 16, 16, 2);
217 img_mask
= de_bitmap_create(c
, 16, 16, 1);
219 de_dbg(c
, "foreground at %"I64_FMT
, pos
);
220 de_convert_image_bilevel(c
->infile
, pos
, 2, img_fg
, DE_CVTF_WHITEISZERO
);
222 de_dbg(c
, "mask at %"I64_FMT
, pos
);
223 de_convert_image_bilevel(c
->infile
, pos
, 2, img_mask
, 0);
225 de_bitmap_apply_mask(img_fg
, img_mask
, 0);
227 fi
->hotspot_y
= (int)de_geti16be_p(&pos
);
228 fi
->hotspot_x
= (int)de_geti16be_p(&pos
);
230 de_dbg(c
, "hotspot: (%d,%d)", fi
->hotspot_x
, fi
->hotspot_y
);
232 if(!c
->filenames_from_file
) ucstring_empty(d
->tmp_namestr
);
233 if(ucstring_isnonempty(d
->tmp_namestr
)) {
234 ucstring_append_char(d
->tmp_namestr
, '.');
236 ucstring_append_sz(d
->tmp_namestr
, "CURS", DE_ENCODING_LATIN1
);
237 de_finfo_set_name_from_ucstring(c
, fi
, d
->tmp_namestr
, 0);
238 de_bitmap_write_to_file_finfo(img_fg
, fi
, DE_CREATEFLAG_OPT_IMAGE
);
241 de_bitmap_destroy(img_fg
);
242 de_bitmap_destroy(img_mask
);
243 de_finfo_destroy(c
, fi
);
246 static void do_mmm_dib(deark
*c
, struct mmm_ctx
*d
, struct de_iffctx
*ictx
, i64 pos1
, i64 len
)
260 struct de_bmpinfo bi
;
263 mmm_read_rsrcid_p(c
, d
, ictx
, &pos
, "dib id");
264 mmm_read_name_p(c
, d
, ictx
, &pos
, NULL
);
267 infosize
= dbuf_getu32le(ictx
->f
, i_infohdr_pos
);
268 if(infosize
<40 || infosize
>124) goto done
;
269 i_bits_pos
= i_infohdr_pos
+ infosize
;
270 i_bits_size
= pos
+len
-i_bits_pos
;
272 ret
= fmtutil_get_bmpinfo(c
, ictx
->f
, &bi
, i_infohdr_pos
, infosize
, 0);
274 if(bi
.num_colors
> 256) goto done
;
281 i_rowspan
= de_pad_to_n(bi
.bitcount
*bi
.width
, 16) / 8;
284 i_rowspan
= bi
.rowspan
;
287 if(bi
.compression_field
==0) {
288 o_bits_size
= bi
.foreground_size
;
291 o_bits_size
= i_bits_size
;
295 if(bi
.bitcount
==4 && !d
->have_pal
) {
296 mmm_default_pal16(c
, d
);
298 if(bi
.bitcount
==8 && !d
->have_pal
) {
299 mmm_default_pal256(c
, d
);
301 if(d
->errflag
) goto done
;
303 outf
= dbuf_create_output_file(c
, "bmp", NULL
, 0);
306 fmtutil_generate_bmpfileheader(c
, outf
, &bi
,
307 14 + infosize
+ bi
.num_colors
*4 + o_bits_size
);
310 dbuf_copy(ictx
->f
, i_infohdr_pos
, 20, outf
);
312 // She biSizeImage field may be wrong, so zero it out.
313 dbuf_write_zeroes(outf
, 4);
316 dbuf_copy(ictx
->f
, i_infohdr_pos
+20, 4, outf
);
318 dbuf_copy(ictx
->f
, i_infohdr_pos
+24, infosize
-24, outf
);
324 dbuf_writebyte(outf
, DE_COLOR_B(d
->pal1
[k
]));
325 dbuf_writebyte(outf
, DE_COLOR_G(d
->pal1
[k
]));
326 dbuf_writebyte(outf
, DE_COLOR_R(d
->pal1
[k
]));
327 dbuf_writebyte(outf
, 0);
330 else if(bi
.bitcount
<=8) {
331 if(bi
.num_colors
>256) goto done
;
332 for(k
=0; k
<bi
.num_colors
; k
++) {
333 dbuf_writebyte(outf
, DE_COLOR_B(d
->pal
[k
]));
334 dbuf_writebyte(outf
, DE_COLOR_G(d
->pal
[k
]));
335 dbuf_writebyte(outf
, DE_COLOR_R(d
->pal
[k
]));
336 dbuf_writebyte(outf
, 0);
342 for(j
=0; j
<bi
.height
; j
++) {
343 dbuf_copy(ictx
->f
, i_bits_pos
+(bi
.height
-1-j
)*i_rowspan
, bi
.rowspan
, outf
);
347 dbuf_copy(ictx
->f
, i_bits_pos
, i_bits_size
, outf
);
354 static void do_mmm_clut(deark
*c
, struct mmm_ctx
*d
, struct de_iffctx
*ictx
,
362 u8 keep_this_pal
= 0;
365 mmm_read_rsrcid_p(c
, d
, ictx
, &pos
, "pal id");
366 rsrc_id
= d
->tmp_rsrcid
;
367 mmm_read_name_p(c
, d
, ictx
, &pos
, NULL
);
369 num_entries
= (pos1
+len
-pos
)/6;
371 de_dbg(c
, "palette at %"I64_FMT
", %u entries", pos
, (UI
)num_entries
);
373 if(num_entries
!=16 && num_entries
!=256) {
374 de_warn(c
, "Unsupported type of palette");
378 is_correct_pass
= (d
->pass
==1 && !d
->use_alt_palfile
) ||
379 (d
->pass
==100 && d
->use_alt_palfile
);
382 if(rsrc_id
==d
->pal_id_to_use
&& is_correct_pass
) {
386 else if(!d
->have_pal
&& is_correct_pass
) {
391 for(i
=0; i
<num_entries
; i
++) {
397 idx
= num_entries
-1-i
;
400 samp
[k
] = (UI
)dbuf_getu16be_p(ictx
->f
, &pos
);
401 samp
[k
] = (UI
)de_sample_nbit_to_8bit(16, samp
[k
]);
403 clr
= DE_MAKE_RGB(samp
[0], samp
[1], samp
[2]);
404 de_dbg_pal_entry(c
, idx
, clr
);
410 de_dbg_indent(c
, -1);
419 static void do_mmm_snd(deark
*c
, struct mmm_ctx
*d
, struct de_iffctx
*ictx
, i64 pos1
, i64 len
)
432 fi
= de_finfo_create(c
);
433 mmm_read_rsrcid_p(c
, d
, ictx
, &pos
, NULL
);
435 mmm_read_name_p(c
, d
, ictx
, &pos
, NULL
);
436 if(ucstring_isnonempty(d
->tmp_namestr
) && c
->filenames_from_file
) {
437 de_finfo_set_name_from_ucstring(c
, fi
, d
->tmp_namestr
, 0);
440 // Note: The code in this function is probably wrong. Don't use it for
441 // anything important.
443 if(c
->debug_level
>=3) {
444 de_dbg_hexdump(c
, ictx
->f
, pos
, 96, 96, "sndhdr", 0);
447 n
= (UI
)dbuf_getu16le_p(ictx
->f
, &pos
);
448 if(n
!= 0x0200) goto done
;
450 sr_code
= (UI
)dbuf_getbyte_p(ictx
->f
, &pos
);
451 if(sr_code
==0x2b) sample_rate
= 11025;
452 else if(sr_code
==0x56) sample_rate
= 22050;
453 else sample_rate
= sr_code
* 256;
454 de_dbg(c
, "sample rate info: 0x%02x (using %"I64_FMT
")", sr_code
, sample_rate
);
455 if(sample_rate
<40) goto done
;
457 x
= dbuf_getbyte_p(ictx
->f
, &pos
);
459 // Usually the header is 36 bytes, but sometimes 78.
460 if(x
==0xff) pos
+= 42;
463 alen
= pos1
+len
-apos
;
464 if(alen
<=0) goto done
;
466 outf
= dbuf_create_output_file(c
, "wav", fi
, 0);
467 dbuf_writeu32be(outf
, CODE_RIFF
);
469 dbuf_writeu32le(outf
, alen
+36+pad
);
470 dbuf_write(outf
, (const u8
*)"WAVEfmt \x10\0\0\0\x01\0\x01\0", 16);
471 dbuf_writeu32le(outf
, sample_rate
);
472 dbuf_writeu32le(outf
, sample_rate
);
473 dbuf_write(outf
, (const u8
*)"\x01\0\x08\0" "data", 8);
474 dbuf_writeu32le(outf
, alen
);
475 dbuf_copy(ictx
->f
, apos
, alen
, outf
);
476 dbuf_write_zeroes(outf
, pad
);
481 de_warn(c
, "Unsupported type of audio resource");
484 de_finfo_destroy(c
, fi
);
487 static int my_mmm_chunk_handler(struct de_iffctx
*ictx
)
490 struct mmm_ctx
*d
= (struct mmm_ctx
*)ictx
->userdata
;
493 dpos
= ictx
->chunkctx
->dpos
;
494 dlen
= ictx
->chunkctx
->dlen
;
496 switch(ictx
->chunkctx
->chunk4cc
.id
) {
498 ictx
->is_std_container
= 1;
502 if(ictx
->level
!= 1) goto done
;
506 switch(ictx
->chunkctx
->chunk4cc
.id
) {
510 do_mmm_ver(c
, d
, ictx
, dpos
, dlen
);
516 do_mmm_cftc(c
, d
, ictx
, dpos
, dlen
);
521 if(d
->pass
==1 || d
->pass
==100) {
522 do_mmm_clut(c
, d
, ictx
, dpos
, dlen
);
530 else if(d
->pass
==2 && !d
->suppress_dib_pass2
) {
531 do_mmm_dib(c
, d
, ictx
, dpos
, dlen
);
536 do_mmm_snd(c
, d
, ictx
, dpos
, dlen
);
541 do_mmm_CURS(c
, d
, ictx
, dpos
, dlen
);
547 do_mmm_mcnm(c
, d
, ictx
, dpos
, dlen
);
552 // Make sure the default behavior only happens in one of the passes
553 // (e.g. hexdump if the debug level is set high enough).
559 if(d
->errflag
) return 0;
563 static int looks_like_a_4cc_b(const u8
*buf
)
568 if(buf
[i
]<32 || buf
[i
]>126) return 0;
573 // A few files are seen to be malformed in the neighborhood of the VWCF chunk,
574 // having two extra bytes after it that shouldn't be there.
575 // This is a quick hack to try to handle such files.
576 // (But this is evidence that we really ought to be relying on the table
577 // of contents, instead of reading the file sequentially.)
578 static int my_mmm_handle_nonchunk_data_fn(struct de_iffctx
*ictx
,
583 dbuf_read(ictx
->f
, buf
, pos
, sizeof(buf
));
585 if(buf
[0]<65 || buf
[1]<65) {
586 if(looks_like_a_4cc_b(&buf
[2])) {
588 de_dbg(ictx
->c
, "[%"I64_FMT
" non-RIFF bytes at %"I64_FMT
"]", *plen
, pos
);
596 static int my_preprocess_mmm_chunk_fn(struct de_iffctx
*ictx
)
599 struct mmmnames_struct
{
603 static const struct mmmnames_struct mmmnames
[] = {
604 { CODE_CFTC
, CODE_cftc
, "table of contents" },
605 { CODE_CLUT
, CODE_clut
, "palette" },
606 { CODE_DIB
, CODE_dib
, "bitmap" },
607 { CODE_McNm
, CODE_mcnm
, "movie name" },
608 { CODE_SCVW
, CODE_scvw
, "director score" },
609 { CODE_snd
, CODE_snd
, "sound resource" },
610 { CODE_STR
, CODE_str
, "string table" },
611 { CODE_STXT
, CODE_stxt
, "styled text" },
612 { CODE_Ver_
, CODE_ver
, "converter version" },
613 { CODE_VWAC
, CODE_vwac
, "script commands" },
614 { CODE_VWCF
, CODE_vwcf
, "movie config" },
615 { CODE_VWCR
, CODE_vwcr
, "cast record array" },
616 { CODE_VWFM
, CODE_vwfm
, "font mapping" },
617 { CODE_VWLB
, CODE_vwlb
, "label list" },
618 { CODE_VWSC
, CODE_vwsc
, "movie score" },
619 { CODE_VWtc
, CODE_vwtc
, "timecode" },
620 { CODE_VWTL
, CODE_vwtl
, "pixel pattern tile" }
621 // Other known chunks: VWFI vwfi VWCI vwci
622 // crsr CURS FOND fwst
623 // icl4 icl8 ICN# ics# ics4 ics8
624 // NFNT pict vers XCMD XCOD XFCN
627 for(k
=0; k
<DE_ARRAYCOUNT(mmmnames
); k
++) {
628 if((ictx
->chunkctx
->chunk4cc
.id
== mmmnames
[k
].id1
) ||
629 (ictx
->chunkctx
->chunk4cc
.id
== mmmnames
[k
].id2
))
631 ictx
->chunkctx
->chunk_name
= mmmnames
[k
].name
;
638 static void setup_ictx_for_mmm(deark
*c
, struct mmm_ctx
*d
, struct de_iffctx
*ictx
)
640 ictx
->userdata
= (void*)d
;
642 ictx
->handle_chunk_fn
= my_mmm_chunk_handler
;
643 ictx
->handle_nonchunk_data_fn
= my_mmm_handle_nonchunk_data_fn
;
644 ictx
->preprocess_chunk_fn
= my_preprocess_mmm_chunk_fn
;
647 static int read_alt_palette_file(deark
*c
, struct mmm_ctx
*d
)
649 dbuf
*palfile
= NULL
;
650 struct de_iffctx
*ictx
= NULL
;
654 palfn
= de_get_ext_option(c
, "file2");
659 d
->use_alt_palfile
= 1;
660 palfile
= dbuf_open_input_file(c
, palfn
);
666 ictx
= fmtutil_create_iff_decoder(c
);
667 setup_ictx_for_mmm(c
, d
, ictx
);
671 de_dbg(c
, "reading alt pal file");
673 fmtutil_read_iff_format(ictx
, 0, palfile
->len
);
674 de_dbg_indent(c
, -1);
678 de_err(c
, "Palette %d not found", d
->pal_id_to_use
);
681 de_err(c
, "No palette found");
688 fmtutil_destroy_iff_decoder(ictx
);
691 // (hack) Reset some things that this function isn't supposed to change.
699 static void de_run_mmm(deark
*c
, de_module_params
*mparams
)
701 struct mmm_ctx
*d
= NULL
;
702 struct de_iffctx
*ictx
= NULL
;
704 int saved_indent_level
;
706 de_dbg_indent_save(c
, &saved_indent_level
);
707 d
= de_malloc(c
, sizeof(struct mmm_ctx
));
708 d
->input_encoding
= de_get_input_encoding(c
, mparams
, DE_ENCODING_ASCII
);
709 d
->opt_allowbad
= (u8
)de_get_ext_option_bool(c
, "mmm:allowbad", 0);
710 s
= de_get_ext_option(c
, "mmm:palid");
712 d
->pal_id_to_use
= de_atoi(s
);
716 d
->tmp_namestr
= ucstring_create(c
);
718 d
->pal1
[0] = DE_STOCKCOLOR_BLACK
;
719 d
->pal1
[1] = DE_STOCKCOLOR_WHITE
;
721 if(!read_alt_palette_file(c
, d
)) goto done
;
723 ictx
= fmtutil_create_iff_decoder(c
);
724 setup_ictx_for_mmm(c
, d
, ictx
);
728 de_dbg(c
, "pass %d", d
->pass
);
730 fmtutil_read_iff_format(ictx
, 0, c
->infile
->len
);
731 de_dbg_indent(c
, -1);
732 if(d
->errflag
) goto done
;
734 if(d
->force_pal
&& !d
->have_pal
&& !d
->opt_allowbad
) {
735 de_err(c
, "Palette %d not found", d
->pal_id_to_use
);
739 if(d
->clut_count
>1 && d
->dib_count
>0 && !d
->opt_allowbad
&& !d
->force_pal
) {
740 de_err(c
, "Multiple palettes found (not supported, "
741 "or try \"-opt mmm:palid=...\").");
742 mmm_allowbad_note(c
);
743 d
->suppress_dib_pass2
= 1;
747 de_dbg(c
, "pass %d", d
->pass
);
749 fmtutil_read_iff_format(ictx
, 0, c
->infile
->len
);
750 de_dbg_indent(c
, -1);
753 fmtutil_destroy_iff_decoder(ictx
);
755 de_dbg(c
, "dib count: %d", d
->dib_count
);
756 de_dbg(c
, "clut count: %d", d
->clut_count
);
757 ucstring_destroy(d
->tmp_namestr
);
760 de_dbg_indent_restore(c
, saved_indent_level
);
763 static int de_identify_mmm(deark
*c
)
765 if((u32
)de_getu32be(0)!=CODE_RIFF
) return 0;
766 if((u32
)de_getu32be(8)!=CODE_RMMP
) return 0;
770 static void de_help_mmm(deark
*c
)
772 de_msg(c
, "-file2 <file.mmm> : File to read the palette from");
773 de_msg(c
, "-opt mmm:palid=<id> : Use this palette");
774 de_msg(c
, "-opt mmm:allowbad : Keep going after certain errors");
776 // file2 with palid: Use that id in file2 if it exists, otherwise fatal error.
777 // file2 w/o palid: Use the first palette in file2 if it exists, otherwise fatal error.
778 // palid w/o file2: Use that id if it exists, otherwise fatal error.
779 // neither: (not explained here)
782 void de_module_mmm(deark
*c
, struct deark_module_info
*mi
)
785 mi
->desc
= "RIFF Multimedia Movie";
786 mi
->run_fn
= de_run_mmm
;
787 mi
->identify_fn
= de_identify_mmm
;
788 mi
->help_fn
= de_help_mmm
;