New "videomaster" module
[deark.git] / modules / macbinary.c
blob1cc4047fcfd623354788a7161a7daa33193eb196
1 // This file is part of Deark.
2 // Copyright (C) 2018 Jason Summers
3 // See the file COPYING for terms of use.
5 // MacBinary
7 #include <deark-config.h>
8 #include <deark-private.h>
9 #include <deark-fmtutil.h>
10 DE_DECLARE_MODULE(de_module_macbinary);
12 typedef struct localctx_struct {
13 u8 extract_files;
14 u8 oldver;
15 u8 extver;
16 u8 extver_minneeded;
17 int is_v23;
18 i64 dfpos, rfpos;
19 i64 dflen, rflen;
20 struct de_stringreaderdata *filename_srd;
21 struct de_timestamp create_time;
22 struct de_timestamp mod_time;
23 } lctx;
25 struct fork_info {
26 u8 is_rsrc;
27 u8 extract_error_flag;
28 i64 pos;
31 struct extract_ctx {
32 lctx *d;
33 struct fork_info fki_data;
34 struct fork_info fki_rsrc;
37 static const char *fork_name(int is_rsrc, int capitalize)
39 if(is_rsrc) {
40 return capitalize?"Resource":"resource";
42 return capitalize?"Data":"data";
45 static void do_header(deark *c, lctx *d, struct de_advfile *advf)
47 u8 b;
48 i64 namelen;
49 i64 pos = 0;
50 i64 n, n2;
51 i64 mod_time_raw;
52 u32 crc_reported, crc_calc;
53 struct de_fourcc type4cc;
54 struct de_fourcc creator4cc;
55 char timestamp_buf[64];
57 d->oldver = de_getbyte_p(&pos);
58 de_dbg(c, "original version: %u", (unsigned int)d->oldver);
59 if(d->oldver!=0) {
60 de_warn(c, "Unsupported MacBinary version");
61 goto done;
64 d->extver = de_getbyte(122);
65 de_dbg(c, "extended version: %u", (unsigned int)d->extver);
66 if(d->extver==129 || d->extver==130) {
67 d->is_v23 = 1;
69 if(d->extver >= 129) {
70 d->extver_minneeded = de_getbyte(123);
71 de_dbg(c, "extended version, min needed: %u", (unsigned int)d->extver_minneeded);
74 namelen = (i64)de_getbyte_p(&pos);
75 if(namelen>=1 && namelen<=63) {
76 // Required to be 1-63 by MacBinary II spec.
77 // Original spec has no written requirements.
78 // Not supposed to be NUL terminated, but such files exist.
79 d->filename_srd = dbuf_read_string(c->infile, pos, namelen, namelen,
80 DE_CONVFLAG_STOP_AT_NUL, DE_ENCODING_MACROMAN);
81 de_dbg(c, "filename: \"%s\"", ucstring_getpsz(d->filename_srd->str));
83 else {
84 de_warn(c, "Bad MacBinary filename length (%d)", (int)namelen);
86 pos += 63;
88 de_dbg(c, "finder info:");
89 de_dbg_indent(c, 1);
91 dbuf_read_fourcc(c->infile, pos, &type4cc, 4, 0x0);
92 de_dbg(c, "type: '%s'", type4cc.id_dbgstr);
93 de_memcpy(advf->typecode, type4cc.bytes, 4);
94 advf->has_typecode = 1;
95 pos += 4;
96 dbuf_read_fourcc(c->infile, pos, &creator4cc, 4, 0x0);
97 de_dbg(c, "creator: '%s'", creator4cc.id_dbgstr);
98 de_memcpy(advf->creatorcode, creator4cc.bytes, 4);
99 advf->has_creatorcode = 1;
100 pos += 4;
102 advf->has_finderflags = 1;
103 if(d->is_v23) {
104 u8 fflags_hibyte;
106 fflags_hibyte = de_getbyte_p(&pos);
107 de_dbg(c, "finder flags (high byte): 0x%02x__", (unsigned int)fflags_hibyte);
108 pos++;
109 advf->finderflags = (u16)fflags_hibyte << 8;
111 else {
112 advf->finderflags = (u16)de_getu16be_p(&pos);
113 de_dbg(c, "finder flags: 0x%04x", (unsigned int)advf->finderflags);
116 n = de_geti16be_p(&pos);
117 n2 = de_geti16be_p(&pos);
118 de_dbg(c, "position in window: %d,%d", (int)n2, (int)n);
120 n = de_getu16be_p(&pos);
121 de_dbg(c, "window/folder id: %d", (int)n);
122 de_dbg_indent(c, -1);
124 b = de_getbyte_p(&pos);
125 de_dbg(c, "protected: 0x%02x", (unsigned int)b);
127 pos++;
129 d->dflen = de_getu32be_p(&pos);
130 de_dbg(c, "data fork len: %u", (unsigned int)d->dflen);
131 d->rflen = de_getu32be_p(&pos);
132 de_dbg(c, "resource fork len: %u", (unsigned int)d->rflen);
134 n = de_getu32be_p(&pos);
135 if(n==0) {
136 d->create_time.is_valid = 0;
137 de_strlcpy(timestamp_buf, "unknown", sizeof(timestamp_buf));
139 else {
140 de_mac_time_to_timestamp(n, &d->create_time);
141 d->create_time.tzcode = DE_TZCODE_LOCAL;
142 de_timestamp_to_string(&d->create_time, timestamp_buf, sizeof(timestamp_buf), 0);
144 de_dbg(c, "create date: %"I64_FMT" (%s)", n, timestamp_buf);
146 mod_time_raw = de_getu32be_p(&pos);
147 if(mod_time_raw==0) {
148 d->mod_time.is_valid = 0;
149 de_strlcpy(timestamp_buf, "unknown", sizeof(timestamp_buf));
151 else {
152 de_mac_time_to_timestamp(mod_time_raw, &d->mod_time);
153 d->mod_time.tzcode = DE_TZCODE_LOCAL;
154 de_timestamp_to_string(&d->mod_time, timestamp_buf, sizeof(timestamp_buf), 0);
156 de_dbg(c, "mod date: %"I64_FMT" (%s)", mod_time_raw, timestamp_buf);
158 pos += 2; // length of Get Info comment
160 if(d->is_v23) {
161 u8 fflags_lobyte;
163 fflags_lobyte = de_getbyte(pos);
164 de_dbg(c, "finder flags (low byte): 0x__%02x", (unsigned int)fflags_lobyte);
165 advf->finderflags |= (u16)fflags_lobyte;
167 pos += 1;
169 pos += 14; // unused
170 pos += 4; // unpacked total length
172 if(d->is_v23) {
173 n = de_getu16be(pos);
174 de_dbg(c, "length of secondary header: %u", (unsigned int)n);
176 pos += 2;
178 pos += 1; // version number, already read
179 pos += 1; // version number, already read
181 crc_reported = (u32)de_getu16be_p(&pos);
182 if(d->is_v23 || crc_reported!=0) {
183 struct de_crcobj *crco;
185 de_dbg(c, "crc of header (reported%s): 0x%04x",
186 (d->is_v23)?"":", hypothetical", (unsigned int)crc_reported);
187 crco = de_crcobj_create(c, DE_CRCOBJ_CRC16_CCITT);
188 de_crcobj_addslice(crco, c->infile, 0, 124);
189 crc_calc = de_crcobj_getval(crco);
190 de_crcobj_destroy(crco);
191 de_dbg(c, "crc of header (calculated): 0x%04x", (unsigned int)crc_calc);
193 if(d->is_v23 && crc_reported!=0 && crc_calc!=crc_reported) {
194 de_warn(c, "MacBinary header CRC check failed");
198 pos += 2; // Reserved for computer type and OS ID
199 done:
203 // If a fork is going to be extracted, call this to set up some things.
204 // Caller must first set advfki->fork_len, among other things.
205 // Sets fki->extract_error_flag if there was a problem that would prevent the
206 // fork from being extracted.
207 static void do_prepare_one_fork(deark *c, lctx *d, struct de_advfile_forkinfo *advfki,
208 struct fork_info *fki)
210 de_dbg(c, "%s fork at %"I64_FMT", len=%"I64_FMT, fork_name(fki->is_rsrc, 0),
211 fki->pos, advfki->fork_len);
213 if(fki->pos+advfki->fork_len>c->infile->len) {
214 de_err(c, "%s fork at %"I64_FMT" goes beyond end of file.",
215 fork_name(fki->is_rsrc, 1), fki->pos);
216 if(fki->pos+advfki->fork_len > c->infile->len+1024) {
217 fki->extract_error_flag = 1;
218 goto done;
222 done:
226 static int my_advfile_cbfn(deark *c, struct de_advfile *advf,
227 struct de_advfile_cbparams *afp)
229 struct extract_ctx *ectx = (struct extract_ctx*)advf->userdata;
231 if(afp->whattodo == DE_ADVFILE_WRITEMAIN) {
232 dbuf_copy(c->infile, ectx->fki_data.pos, advf->mainfork.fork_len, afp->outf);
234 else if(afp->whattodo == DE_ADVFILE_WRITERSRC) {
235 dbuf_copy(c->infile, ectx->fki_rsrc.pos, advf->rsrcfork.fork_len, afp->outf);
237 return 1;
240 static void run_macbinary_internal(deark *c, lctx *d)
242 i64 pos = 128;
243 struct de_advfile *advf = NULL;
244 struct extract_ctx *ectx = NULL;
246 ectx = de_malloc(c, sizeof(struct extract_ctx));
247 advf = de_advfile_create(c);
249 do_header(c, d, advf);
250 if(d->filename_srd && ucstring_isnonempty(d->filename_srd->str)) {
251 ucstring_append_ucstring(advf->filename, d->filename_srd->str);
252 advf->original_filename_flag = 1;
254 if(d->filename_srd) {
255 de_advfile_set_orig_filename(advf, d->filename_srd->sz,
256 d->filename_srd->sz_strlen);
258 advf->mainfork.fi->timestamp[DE_TIMESTAMPIDX_MODIFY] = d->mod_time;
259 advf->mainfork.fi->timestamp[DE_TIMESTAMPIDX_CREATE] = d->create_time;
261 if(d->dflen>0) {
262 d->dfpos = pos;
263 ectx->fki_data.pos = d->dfpos;
264 advf->mainfork.fork_len = d->dflen;
266 if(d->extract_files) {
267 do_prepare_one_fork(c, d, &advf->mainfork, &ectx->fki_data);
268 if(!ectx->fki_data.extract_error_flag) {
269 advf->mainfork.fork_exists = 1;
273 pos += de_pad_to_n(d->dflen, 128);
276 if(d->rflen>0) {
277 d->rfpos = pos;
279 ectx->fki_rsrc.is_rsrc = 1;
280 ectx->fki_rsrc.pos = d->rfpos;
281 advf->rsrcfork.fork_len = d->rflen;
283 if(d->extract_files) {
284 do_prepare_one_fork(c, d, &advf->rsrcfork, &ectx->fki_rsrc);
285 if(!ectx->fki_rsrc.extract_error_flag) {
286 advf->rsrcfork.fork_exists = 1;
291 if(d->extract_files) {
292 advf->userdata = (void*)ectx;
293 advf->writefork_cbfn = my_advfile_cbfn;
294 de_advfile_run(advf);
297 de_advfile_destroy(advf);
298 de_free(c, ectx);
301 static void de_run_macbinary(deark *c, de_module_params *mparams)
303 lctx *d = NULL;
305 d = de_malloc(c, sizeof(lctx));
307 d->extract_files = 1;
308 if(de_havemodcode(c, mparams, 'D')) {
309 d->extract_files = 0;
312 run_macbinary_internal(c, d);
314 if(mparams) {
315 mparams->out_params.uint1 = (u32)d->dfpos;
316 mparams->out_params.uint2 = (u32)d->dflen;
317 mparams->out_params.uint3 = (u32)d->rfpos;
318 mparams->out_params.uint4 = (u32)d->rflen;
320 if(mparams->out_params.fi) {
321 // If caller created out_params.fi for us, save the mod time to it.
322 mparams->out_params.fi->timestamp[DE_TIMESTAMPIDX_MODIFY] = d->mod_time;
324 // If caller created .fi->name_other, copy the filename to it.
325 if(d->filename_srd && d->filename_srd->str->len>0 && mparams->out_params.fi->name_other) {
326 ucstring_append_ucstring(mparams->out_params.fi->name_other, d->filename_srd->str);
331 if(d) {
332 de_destroy_stringreaderdata(c, d->filename_srd);
333 de_free(c, d);
337 // Detecting MacBinary format is important, but also very difficult.
338 // Note: This must be coordinated with the macpaint detection routine.
339 // It should never set the confidence to 100, because macpaint and
340 // maybe other formats need to be able to have higher confidence.
341 static int de_identify_macbinary(deark *c)
343 int conf = 0;
344 int k;
345 int has_sig;
346 int is_v23 = 0; // v2 or v3
347 int good_file_len = 0;
348 int good_cc = 0;
349 int bad_crc = 0;
350 i64 n;
351 i64 dflen, rflen;
352 i64 min_expected_len;
353 u32 crc_reported, crc_calc;
354 u8 b[128];
356 // "old" version number is always 0.
357 b[0] = de_getbyte(0);
358 if(b[0]!=0) goto done;
360 // filename length
361 b[1] = de_getbyte(1);
362 if(b[1]<1 || b[1]>63) goto done;
364 de_read(&b[2], 2, sizeof(b)-2);
366 if(b[2]==0) goto done; // First filename byte
367 if(b[74]!=0) goto done;
368 if(b[82]!=0) goto done;
370 // Extended version number
371 if(b[122]==129 && b[123]==129) {
372 // v2
373 is_v23 = 1;
375 else if(b[122]==130 && (b[123]==129 || b[123]==130)) {
376 // v3
377 is_v23 = 1;
379 // else v1.
380 // Some v1 files have garbage in the last 29 bytes of the file,
381 // so we can't assume the extended version number is 0.
383 // Ver.III signature, but possibly used in some files that have earlier
384 // version numbers.
385 has_sig = !de_memcmp(&b[102], (const void*)"mBIN", 4);
386 if(has_sig) {
387 conf = 90;
388 goto done;
391 // Check if filename characters are sensible
392 for(k=0; k<(int)b[1]; k++) {
393 if(b[2+k]>0 && b[2+k]<32) goto done;
396 // File type code. Expect ASCII.
397 good_cc = 1;
398 for(k=65; k<=68; k++) {
399 if(b[k]<32 || b[k]>127) good_cc = 0;
402 dflen = de_getu32be_direct(&b[83]);
403 rflen = de_getu32be_direct(&b[87]);
405 crc_reported = (u32)de_getu16be_direct(&b[124]);
407 // Check the file size.
409 // Resource forks that go beyond the end of file are too common to
410 // disallow.
411 if(128 + dflen > c->infile->len) goto done;
412 if(128 + rflen + dflen > c->infile->len + 4096) goto done;
414 if(rflen>0) {
415 min_expected_len = 128 + de_pad_to_n(dflen, 128) + rflen;
417 else {
418 min_expected_len = 128 + dflen;
421 // The file size really should be exactly min_expected_len, or that
422 // number padded to the next multiple of 128. But I'm not bold
423 // enough to require it.
424 if((c->infile->len == min_expected_len) ||
425 (c->infile->len == de_pad_to_n(min_expected_len, 128)))
427 good_file_len = 1;
430 if(is_v23) {
431 // Most MacBinary II specific checks go here
433 if(!de_is_all_zeroes(&b[102], 14)) {
434 if(!good_file_len) goto done;
437 // Secondary header length. We don't support this.
438 n = de_getu16be_direct(&b[120]);
439 if(n!=0) goto done;
441 else {
442 // Most Original MacBinary format checks go here
444 // An empty file is not illegal, but we need as many checks as possible
445 // that won't be passed by all 0 bytes.
446 if(dflen==0 && rflen==0) goto done;
448 // Unused fields in this version should be all 0, though we'll allow
449 // nonzero values in some cases.
450 if(!de_is_all_zeroes(&b[99], 25)) {
451 if(!good_file_len) goto done;
455 if(crc_reported!=0 || is_v23) {
456 struct de_crcobj *crco;
458 crco = de_crcobj_create(c, DE_CRCOBJ_CRC16_CCITT);
459 de_crcobj_addbuf(crco, b, 124);
460 crc_calc = de_crcobj_getval(crco);
461 de_crcobj_destroy(crco);
462 if(crc_calc!=crc_reported && is_v23 && crc_reported!=0) {
463 bad_crc = 1;
467 if(is_v23 && good_file_len && good_cc) {
468 if(bad_crc) {
469 conf = 19;
471 else {
472 conf = 90;
475 else if(bad_crc) {
476 goto done;
478 else if(is_v23) {
479 conf = 49;
481 else if(good_cc) {
482 conf = 29;
484 else {
485 conf = 19;
488 done:
489 if(conf>0) {
490 c->detection_data->is_macbinary = 1;
492 return conf;
495 void de_module_macbinary(deark *c, struct deark_module_info *mi)
497 mi->id = "macbinary";
498 mi->desc = "MacBinary";
499 mi->run_fn = de_run_macbinary;
500 mi->identify_fn = de_identify_macbinary;
501 mi->flags = DE_MODFLAG_SHAREDDETECTION;