1 // This file is part of Deark.
2 // Copyright (C) 2020 Jason Summers
3 // See the file COPYING for terms of use.
5 // LBR - uncompressed CP/M archive format
6 // Squeeze compressed file
7 // Crunch v1 compressed file
8 // CRLZH compressed file
10 // LZWCOM compressed file
12 #include <deark-private.h>
13 #include <deark-fmtutil.h>
14 DE_DECLARE_MODULE(de_module_lbr
);
15 DE_DECLARE_MODULE(de_module_squeeze
);
16 DE_DECLARE_MODULE(de_module_crunch
);
17 DE_DECLARE_MODULE(de_module_crlzh
);
18 DE_DECLARE_MODULE(de_module_zsq
);
19 DE_DECLARE_MODULE(de_module_lzwcom
);
21 #define LBR_DIRENT_SIZE 32
22 #define LBR_SECTOR_SIZE 128
33 i64 len_in_bytes_withpadding
;
34 i64 len_in_bytes_nopadding
;
36 struct de_timestamp create_timestamp
;
37 struct de_timestamp change_timestamp
;
40 typedef struct localctx_struct
{
41 de_encoding input_encoding
;
43 struct de_crcobj
*crco
;
46 static void our_writelistener_cb(dbuf
*f
, void *userdata
, const u8
*buf
, i64 buf_len
)
48 struct de_crcobj
*crco
= (struct de_crcobj
*)userdata
;
49 de_crcobj_addbuf(crco
, buf
, buf_len
);
52 static void do_extract_member(deark
*c
, lctx
*d
, struct member_data
*md
)
57 fi
= de_finfo_create(c
);
63 de_finfo_set_name_from_ucstring(c
, fi
, md
->fn
, 0);
64 fi
->original_filename_flag
= 1;
67 if(md
->create_timestamp
.is_valid
) {
68 fi
->timestamp
[DE_TIMESTAMPIDX_CREATE
] = md
->create_timestamp
;
70 if(md
->change_timestamp
.is_valid
) {
71 fi
->timestamp
[DE_TIMESTAMPIDX_MODIFY
] = md
->change_timestamp
;
74 outf
= dbuf_create_output_file(c
, NULL
, fi
, 0x0);
76 de_crcobj_reset(d
->crco
);
78 de_crcobj_addslice(d
->crco
, c
->infile
, md
->pos_in_bytes
, 16);
79 de_crcobj_addzeroes(d
->crco
, 2); // The 2-byte CRC field
80 de_crcobj_addslice(d
->crco
, c
->infile
, md
->pos_in_bytes
+18, md
->len_in_bytes_withpadding
-18);
83 dbuf_set_writelistener(outf
, our_writelistener_cb
, (void*)d
->crco
);
84 dbuf_copy(c
->infile
, md
->pos_in_bytes
, md
->len_in_bytes_nopadding
, outf
);
85 // CRC calculation includes padding bytes:
86 de_crcobj_addslice(d
->crco
, c
->infile
,
87 md
->pos_in_bytes
+ md
->len_in_bytes_nopadding
,
88 md
->len_in_bytes_withpadding
- md
->len_in_bytes_nopadding
);
90 md
->crc_calc
= de_crcobj_getval(d
->crco
);
91 de_dbg(c
, "crc (calculated): 0x%04x", (UI
)md
->crc_calc
);
93 de_finfo_destroy(c
, fi
);
97 static void read_8_3_filename(deark
*c
, lctx
*d
, struct member_data
*md
, i64 pos
)
99 de_ucstring
*ext
= NULL
;
101 dbuf_read_to_ucstring(c
->infile
, pos
, 8, md
->fn
, 0, d
->input_encoding
);
102 ucstring_strip_trailing_spaces(md
->fn
);
104 ucstring_append_char(md
->fn
, '_');
107 ext
= ucstring_create(c
);
108 dbuf_read_to_ucstring(c
->infile
, pos
+8, 3, ext
, 0, d
->input_encoding
);
109 ucstring_strip_trailing_spaces(ext
);
111 ucstring_append_char(md
->fn
, '.');
112 ucstring_append_ucstring(md
->fn
, ext
);
115 ucstring_destroy(ext
);
118 static void handle_timestamp(deark
*c
, lctx
*d
, i64 date_raw
, i64 time_raw
,
119 struct de_timestamp
*ts
, const char *name
)
122 char timestamp_buf
[64];
125 de_dbg(c
, "%s: [not set]", name
);
129 // Day 0 is Dec 31, 1977 (or it would be, if 0 weren't reserved).
130 // Difference from Unix time (Jan 1, 1970) =
131 // 365 days in 1970, 1971, 1973, 1974, 1975
132 // + 366 days in 1972, 1976
133 // + 364 days in 1977.
134 ut
= 86400 * (date_raw
+ (365*5 + 366*2 + 364));
136 // Time of day is in DOS format.
137 ut
+= 3600*(time_raw
>>11); // hours
138 ut
+= 60*(time_raw
&0x07e0)>>5; // minutes
139 ut
+= 2*(time_raw
&0x001f); // seconds
140 de_unix_time_to_timestamp(ut
, ts
, 0);
141 de_timestamp_to_string(ts
, timestamp_buf
, sizeof(timestamp_buf
), 0);
142 de_dbg(c
, "%s: %s", name
, timestamp_buf
);
145 static void on_bad_dir(deark
*c
)
147 de_err(c
, "Bad directory. This is probably not an LBR file.");
150 // Returns nonzero if we can continue.
151 // if is_dir, sets d->dir_len_in_bytes.
152 static int do_entry(deark
*c
, lctx
*d
, i64 pos1
, int is_dir
)
155 int saved_indent_level
;
156 struct member_data
*md
= NULL
;
157 i64 crdate
, chdate
, crtime
, chtime
;
159 de_dbg_indent_save(c
, &saved_indent_level
);
160 md
= de_malloc(c
, sizeof(struct member_data
));
163 de_dbg(c
, "%s entry at %"I64_FMT
, (md
->is_dir
?"dir":"file"), pos1
);
166 md
->status
= de_getbyte(pos1
);
167 de_dbg(c
, "status: 0x%02x", (UI
)md
->status
);
168 if(md
->is_dir
&& md
->status
!=0x00) {
172 if(md
->status
==0xff) { // unused entry - marks end of directory
175 if(md
->status
!=0x00) { // deleted entry (should be 0xfe)
176 de_dbg(c
, "[deleted]");
181 md
->fn
= ucstring_create(c
);
183 read_8_3_filename(c
, d
, md
, pos1
+1);
184 de_dbg(c
, "filename: \"%s\"", ucstring_getpsz_d(md
->fn
));
187 md
->pos_in_sectors
= de_getu16le(pos1
+12);
188 md
->pos_in_bytes
= md
->pos_in_sectors
* LBR_SECTOR_SIZE
;
189 de_dbg(c
, "data offset: %"I64_FMT
" (sector %"I64_FMT
")", md
->pos_in_bytes
, md
->pos_in_sectors
);
190 if(md
->is_dir
&& md
->pos_in_bytes
!=pos1
) {
195 md
->len_in_sectors
= de_getu16le(pos1
+14);
196 de_dbg(c
, "length in sectors: %"I64_FMT
, md
->len_in_sectors
);
198 md
->crc_reported
= (u32
)de_getu16le(pos1
+16);
199 de_dbg(c
, "crc (reported): 0x%04x", (UI
)md
->crc_reported
);
201 // 18-25: timestamps - TODO
202 crdate
= de_getu16le(pos1
+18);
203 chdate
= de_getu16le(pos1
+20);
204 crtime
= de_getu16le(pos1
+22);
205 chtime
= de_getu16le(pos1
+24);
206 handle_timestamp(c
, d
, crdate
, crtime
, &md
->create_timestamp
, "creation time");
207 handle_timestamp(c
, d
, chdate
, chtime
, &md
->change_timestamp
, "last changed time");
209 md
->pad_count
= de_getbyte(pos1
+26);
210 de_dbg(c
, "pad count: %u", (UI
)md
->pad_count
);
211 if(md
->pad_count
>=LBR_SECTOR_SIZE
|| md
->len_in_sectors
<1) {
215 md
->len_in_bytes_withpadding
= md
->len_in_sectors
*LBR_SECTOR_SIZE
;
216 md
->len_in_bytes_nopadding
= md
->len_in_bytes_withpadding
- (i64
)md
->pad_count
;
217 de_dbg(c
, "length in bytes: %"I64_FMT
, md
->len_in_bytes_nopadding
);
219 if(md
->pos_in_bytes
+ md
->len_in_bytes_nopadding
> c
->infile
->len
) {
220 de_err(c
, "Unexpected end of file");
228 d
->dir_len_in_bytes
= md
->len_in_bytes_nopadding
;
232 do_extract_member(c
, d
, md
);
236 ucstring_destroy(md
->fn
);
239 de_dbg_indent_restore(c
, saved_indent_level
);
243 static void de_run_lbr(deark
*c
, de_module_params
*mparams
)
248 d
= de_malloc(c
, sizeof(lctx
));
249 d
->input_encoding
= de_get_input_encoding(c
, NULL
, DE_ENCODING_ASCII
);
251 d
->crco
= de_crcobj_create(c
, DE_CRCOBJ_CRC16_CCITT
);
254 if(!do_entry(c
, d
, pos
, 1)) goto done
;
255 pos
+= LBR_DIRENT_SIZE
;
258 while(pos
+LBR_DIRENT_SIZE
<= c
->infile
->len
&&
259 pos
+LBR_DIRENT_SIZE
<= d
->dir_len_in_bytes
)
261 if(!do_entry(c
, d
, pos
, 0)) goto done
;
262 pos
+= LBR_DIRENT_SIZE
;
267 de_crcobj_destroy(d
->crco
);
272 static int de_identify_lbr(deark
*c
)
274 // TODO: Better detection is possible
275 if(!dbuf_memcmp(c
->infile
, 0, "\x00\x20\x20\x20\x20\x20\x20\x20\x20"
276 "\x20\x20\x20\x00\x00", 14))
281 void de_module_lbr(deark
*c
, struct deark_module_info
*mi
)
284 mi
->desc
= "LBR archive";
285 mi
->run_fn
= de_run_lbr
;
286 mi
->identify_fn
= de_identify_lbr
;
289 ///////////////////////////////////////////////
290 // Squeeze - CP/M compressed file format
292 // For Crunch/CRLZH(/Squeeze?) filename fields
293 struct crcr_filename_data
{
295 de_ucstring
*comment
;
299 static int crcr_read_filename_etc(deark
*c
, i64 pos1
, struct crcr_filename_data
*fnd
)
304 CRCRFNST_NEUTRAL
, CRCRFNST_FILENAME
, CRCRFNST_COMMENT
, CRCRFNST_DATE
306 enum crcrfnstate state
= CRCRFNST_FILENAME
;
308 int extension_char_count
= 0;
309 char attr_str
[4] = "...";
310 static const char attr_codes
[3] = {'R', 'S', 'A'};
313 // Note: Only ASCII can really be supported, because the characters are 7-bit.
314 // Normally, we'd use ucstring_append_bytes_ex() for something like this, but
315 // it's pointless here.
316 fnd
->fn
= ucstring_create(c
);
321 // Note: CFX limits this entire field to about 80 bytes.
322 if(pos
-pos1
> 300) goto done
;
323 if(pos
>= c
->infile
->len
) goto done
;
325 b1
= de_getbyte_p(&pos
);
332 state
= CRCRFNST_DATE
; // TODO: Figure this field out
334 else if(state
==CRCRFNST_FILENAME
&& b2
=='[') {
335 state
= CRCRFNST_COMMENT
;
337 else if(state
==CRCRFNST_FILENAME
&& extension_char_count
>=3) {
338 state
= CRCRFNST_NEUTRAL
;
340 else if(state
==CRCRFNST_FILENAME
) {
341 ucstring_append_char(fnd
->fn
, (de_rune
)b2
);
343 if(extension_char_count
<3 && (b1
& 0x80)) {
344 // The CP/M low-level directory structure uses the high bit of
345 // the file extension bytes to store attributes. Some Crunch/
346 // CRLZH files do the same thing.
347 // CP/M also uses the high bit of the *filename*, for less-common
348 // attributes, but that doesn't seem possible here, because all 8
349 // bytes are not always stored.
351 attr_str
[extension_char_count
] = attr_codes
[extension_char_count
];
353 extension_char_count
++;
356 if(b2
=='.') found_dot
= 1;
359 else if(state
==CRCRFNST_COMMENT
&& b2
==']') {
360 state
= CRCRFNST_NEUTRAL
;
362 else if(state
==CRCRFNST_COMMENT
) {
364 fnd
->comment
= ucstring_create(c
);
366 ucstring_append_char(fnd
->comment
, (de_rune
)b2
);
370 ucstring_strip_trailing_spaces(fnd
->fn
);
371 fnd
->size
= pos
- pos1
;
373 de_dbg(c
, "filename: \"%s\"", ucstring_getpsz_d(fnd
->fn
));
376 de_dbg(c
, "attribs: %s", attr_str
);
380 de_dbg(c
, "comment: \"%s\"", ucstring_getpsz_d(fnd
->comment
));
387 static void crcr_filename_data_freecontents(deark
*c
, struct crcr_filename_data
*fnd
)
389 ucstring_destroy(fnd
->fn
);
390 ucstring_destroy(fnd
->comment
);
395 de_encoding input_encoding
;
396 struct crcr_filename_data fnd
;
397 struct de_stringreaderdata
*sq2_timestamp_string
;
398 struct de_stringreaderdata
*sq2_comment
;
399 UI checksum_reported
;
402 struct de_timestamp timestamp
;
405 static void squeeze_writelistener_cb(dbuf
*f
, void *userdata
, const u8
*buf
, i64 buf_len
)
407 struct squeeze_ctx
*sqctx
= (struct squeeze_ctx
*)userdata
;
410 for(i
=0; i
<buf_len
; i
++) {
411 sqctx
->checksum_calc
+= buf
[i
];
415 static void do_sqeeze_timestamp(deark
*c
, struct squeeze_ctx
*sqctx
, i64 pos1
)
422 char timestamp_buf
[64];
424 if(c
->infile
->len
-pos1
< 8) return;
425 sig
= de_getu16le_p(&pos
);
426 if(sig
!= 0xff77) return;
427 dt_raw
= de_getu16le_p(&pos
);
428 tm_raw
= de_getu16le_p(&pos
);
429 cksum_reported
= (UI
)de_getu16le_p(&pos
);
430 cksum_calc
= ((UI
)sig
+ (UI
)dt_raw
+ (UI
)tm_raw
)&0xffff;
431 if(cksum_calc
!= cksum_reported
) return; // Presumably a false positive signature
433 de_dbg(c
, "timestamp at %"I64_FMT
, pos1
);
435 de_dos_datetime_to_timestamp(&sqctx
->timestamp
, dt_raw
, tm_raw
);
437 sqctx
->timestamp
.tzcode
= DE_TZCODE_LOCAL
;
438 de_timestamp_to_string(&sqctx
->timestamp
, timestamp_buf
, sizeof(timestamp_buf
), 0);
439 de_dbg(c
, "timestamp: %s", timestamp_buf
);
441 de_dbg(c
, "timestamp checksum (calculated): 0x%04x", cksum_calc
);
442 de_dbg(c
, "timestamp checksum (reported): 0x%04x", cksum_reported
);
443 de_dbg_indent(c
, -1);
446 static void read_squeeze_checksum(deark
*c
, struct squeeze_ctx
*sqctx
, i64 pos
)
448 sqctx
->checksum_reported
= (u32
)de_getu16le_p(&pos
);
449 de_dbg(c
, "checksum (reported): %u", (UI
)sqctx
->checksum_reported
);
452 static int read_squeeze_headers(deark
*c
, struct squeeze_ctx
*sqctx
, i64 pos1
)
457 read_squeeze_checksum(c
, sqctx
, pos
);
460 // I don't know the correct way to interpret the Squeeze filename field, if
461 // there even is such a way.
462 // Some Unsqueeze utilities accept it as-is, some truncate it after the third
463 // filename extension byte, some interpret it the same as Crunch format
464 // (including ignoring the high bit of every byte, for some reason).
465 // Doing it the Crunch way is probably safe.
466 if(!crcr_read_filename_etc(c
, pos
, &sqctx
->fnd
)) goto done
;
467 pos
+= sqctx
->fnd
.size
;
469 sqctx
->cmpr_data_pos
= pos
;
473 de_err(c
, "Malformed header");
478 static int read_sq2_headers(deark
*c
, struct squeeze_ctx
*sqctx
, i64 pos1
)
484 if(!crcr_read_filename_etc(c
, pos
, &sqctx
->fnd
)) goto done
;
485 pos
+= sqctx
->fnd
.size
;
487 sqctx
->sq2_timestamp_string
= dbuf_read_string(c
->infile
, pos
, 300, 300,
488 DE_CONVFLAG_STOP_AT_NUL
, sqctx
->input_encoding
);
489 if(!sqctx
->sq2_timestamp_string
->found_nul
) goto done
;
490 de_dbg(c
, "timestamp_string: \"%s\"", ucstring_getpsz_d(sqctx
->sq2_timestamp_string
->str
));
491 pos
+= sqctx
->sq2_timestamp_string
->bytes_consumed
;
493 sqctx
->sq2_comment
= dbuf_read_string(c
->infile
, pos
, 300, 300,
494 DE_CONVFLAG_STOP_AT_NUL
, sqctx
->input_encoding
);
495 if(!sqctx
->sq2_comment
->found_nul
) goto done
;
496 de_dbg(c
, "comment: \"%s\"", ucstring_getpsz_d(sqctx
->sq2_comment
->str
));
497 pos
+= sqctx
->sq2_comment
->bytes_consumed
;
499 b
= de_getbyte_p(&pos
);
500 if(b
!= 0x1a) goto done
;
502 read_squeeze_checksum(c
, sqctx
, pos
);
507 sqctx
->cmpr_data_pos
= pos
;
512 de_err(c
, "Malformed header");
517 static void de_run_squeeze(deark
*c
, de_module_params
*mparams
)
521 struct squeeze_ctx
*sqctx
= NULL
;
523 dbuf
*outf_tmp
= NULL
;
524 dbuf
*outf_final
= NULL
;
525 int saved_indent_level
;
526 struct de_dfilter_in_params dcmpri
;
527 struct de_dfilter_out_params dcmpro
;
528 struct de_dfilter_results dres
;
529 struct de_dcmpr_two_layer_params tlp
;
531 de_dbg_indent_save(c
, &saved_indent_level
);
532 sqctx
= de_malloc(c
, sizeof(struct squeeze_ctx
));
533 sqctx
->input_encoding
= de_get_input_encoding(c
, NULL
, DE_ENCODING_CP437
);
535 n
= de_getu16le_p(&pos
);
537 de_declare_fmt(c
, "Squeezed");
540 de_declare_fmt(c
, "Squeeze v2 (SQ2)");
544 de_dbg(c
, "Not a Squeezed file");
549 if(!read_sq2_headers(c
, sqctx
, pos
)) goto done
;
552 if(!read_squeeze_headers(c
, sqctx
, pos
)) goto done
;
555 pos
= sqctx
->cmpr_data_pos
;
557 fi
= de_finfo_create(c
);
558 de_finfo_set_name_from_ucstring(c
, fi
, sqctx
->fnd
.fn
, 0);
559 fi
->original_filename_flag
= 1;
561 de_dbg(c
, "squeeze-compressed data at %"I64_FMT
, pos
);
564 // We have to decompress the file before we can find the timestamp. That's
565 // why we decompress to a membuf.
566 outf_tmp
= dbuf_create_membuf(c
, 0, 0);
568 de_dfilter_init_objects(c
, &dcmpri
, &dcmpro
, &dres
);
569 dcmpri
.f
= c
->infile
;
571 dcmpri
.len
= c
->infile
->len
- pos
;
574 dbuf_set_writelistener(outf_tmp
, squeeze_writelistener_cb
, (void*)sqctx
);
576 de_zeromem(&tlp
, sizeof(struct de_dcmpr_two_layer_params
));
577 tlp
.codec1_type1
= fmtutil_huff_squeeze_codectype1
;
578 tlp
.codec2
= dfilter_rle90_codec
;
579 tlp
.dcmpri
= &dcmpri
;
580 tlp
.dcmpro
= &dcmpro
;
582 de_dfilter_decompress_two_layer(c
, &tlp
);
584 if(dres
.bytes_consumed_valid
) {
585 de_dbg(c
, "compressed data size: %"I64_FMT
", ends at %"I64_FMT
, dres
.bytes_consumed
,
586 dcmpri
.pos
+dres
.bytes_consumed
);
588 do_sqeeze_timestamp(c
, sqctx
, dcmpri
.pos
+dres
.bytes_consumed
);
589 if(sqctx
->timestamp
.is_valid
) {
590 fi
->timestamp
[DE_TIMESTAMPIDX_MODIFY
] = sqctx
->timestamp
;
594 outf_final
= dbuf_create_output_file(c
, NULL
, fi
, 0);
595 dbuf_copy(outf_tmp
, 0, outf_tmp
->len
, outf_final
);
598 de_err(c
, "Decompression failed: %s", de_dfilter_get_errmsg(c
, &dres
));
602 sqctx
->checksum_calc
&= 0xffff;
603 de_dbg(c
, "checksum (calculated): %u", (UI
)sqctx
->checksum_calc
);
604 if(sqctx
->checksum_calc
!= sqctx
->checksum_reported
) {
605 de_err(c
, "Checksum error. Decompression probably failed.");
611 crcr_filename_data_freecontents(c
, &sqctx
->fnd
);
612 de_destroy_stringreaderdata(c
, sqctx
->sq2_timestamp_string
);
613 de_destroy_stringreaderdata(c
, sqctx
->sq2_comment
);
616 dbuf_close(outf_final
);
617 dbuf_close(outf_tmp
);
618 de_finfo_destroy(c
, fi
);
619 de_dbg_indent_restore(c
, saved_indent_level
);
622 static int de_identify_squeeze(deark
*c
)
627 if(id
==0xff76) return 70;
628 if(id
==0xfffa) return 25; // SQ2
632 void de_module_squeeze(deark
*c
, struct deark_module_info
*mi
)
635 mi
->desc
= "Squeeze (CP/M)";
636 mi
->run_fn
= de_run_squeeze
;
637 mi
->identify_fn
= de_identify_squeeze
;
640 ///////////////////////////////////////////////
641 // Crunch - CP/M compressed file format
644 struct crcr_filename_data fnd
;
645 u8 fmtver
; // 1 or 2, 0 if unknown
647 UI checksum_reported
;
651 static void crunch_writelistener_cb(dbuf
*f
, void *userdata
, const u8
*buf
, i64 buf_len
)
653 struct crunch_ctx
*crunchctx
= (struct crunch_ctx
*)userdata
;
656 for(i
=0; i
<buf_len
; i
++) {
657 crunchctx
->checksum_calc
+= buf
[i
];
661 static void decompress_crunch_v1(deark
*c
, struct crunch_ctx
*crunchctx
, i64 pos1
)
666 struct de_dfilter_in_params dcmpri
;
667 struct de_dfilter_out_params dcmpro
;
668 struct de_dfilter_results dres
;
669 struct de_lzw_params delzwp
;
670 struct de_dcmpr_two_layer_params tlp
;
673 fi
= de_finfo_create(c
);
674 de_finfo_set_name_from_ucstring(c
, fi
, crunchctx
->fnd
.fn
, 0);
675 fi
->original_filename_flag
= 1;
677 outf
= dbuf_create_output_file(c
, NULL
, fi
, 0x0);
678 dbuf_set_writelistener(outf
, crunch_writelistener_cb
, (void*)crunchctx
);
680 de_dfilter_init_objects(c
, &dcmpri
, &dcmpro
, &dres
);
681 dcmpri
.f
= c
->infile
;
683 dcmpri
.len
= c
->infile
->len
- pos
;
686 de_zeromem(&delzwp
, sizeof(struct de_lzw_params
));
687 delzwp
.fmt
= DE_LZWFMT_ARC5
;
688 delzwp
.arc5_has_stop_code
= 1;
690 de_zeromem(&tlp
, sizeof(struct de_dcmpr_two_layer_params
));
691 tlp
.codec1_pushable
= dfilter_lzw_codec
;
692 tlp
.codec1_private_params
= (void*)&delzwp
;
693 tlp
.codec2
= dfilter_rle90_codec
;
694 tlp
.dcmpri
= &dcmpri
;
695 tlp
.dcmpro
= &dcmpro
;
697 de_dfilter_decompress_two_layer(c
, &tlp
);
700 de_err(c
, "Decompression failed: %s", de_dfilter_get_errmsg(c
, &dres
));
704 if(dres
.bytes_consumed_valid
) {
705 de_dbg(c
, "compressed data size: %"I64_FMT
", ends at %"I64_FMT
, dres
.bytes_consumed
,
706 dcmpri
.pos
+dres
.bytes_consumed
);
707 pos
+= dres
.bytes_consumed
;
709 if(crunchctx
->cksum_type
==0) {
710 crunchctx
->checksum_calc
&= 0xffff;
711 crunchctx
->checksum_reported
= (UI
)de_getu16le_p(&pos
);
712 de_dbg(c
, "checksum (calculated): %u", crunchctx
->checksum_calc
);
713 de_dbg(c
, "checksum (reported): %u", crunchctx
->checksum_reported
);
714 if(crunchctx
->checksum_calc
!= crunchctx
->checksum_reported
) {
715 de_err(c
, "Checksum error. Decompression probably failed.");
722 de_finfo_destroy(c
, fi
);
724 de_dbg_indent(c
, -1);
727 static void de_run_crunch(deark
*c
, de_module_params
*mparams
)
729 struct crunch_ctx
*crunchctx
= NULL
;
735 crunchctx
= de_malloc(c
, sizeof(struct crunch_ctx
));
738 if(!crcr_read_filename_etc(c
, pos
, &crunchctx
->fnd
)) goto done
;
739 pos
+= crunchctx
->fnd
.size
;
741 b
= de_getbyte_p(&pos
);
742 de_dbg(c
, "encoder version: 0x%02x", (UI
)b
);
744 fmtver_raw
= de_getbyte_p(&pos
);
745 if(fmtver_raw
>=0x10 && fmtver_raw
<=0x1f) {
746 crunchctx
->fmtver
= 1;
749 else if(fmtver_raw
>=0x20 && fmtver_raw
<=0x2f) {
750 crunchctx
->fmtver
= 2;
756 de_dbg(c
, "format version: 0x%02x (%s)", (UI
)fmtver_raw
, verstr
);
757 if(crunchctx
->fmtver
!=0) {
758 de_declare_fmtf(c
, "Crunch (v%d)", (int)crunchctx
->fmtver
);
761 crunchctx
->cksum_type
= de_getbyte_p(&pos
);
762 de_dbg(c
, "checksum type: 0x%02x (%s)", (UI
)crunchctx
->cksum_type
,
763 (crunchctx
->cksum_type
==0?"standard":"?"));
765 b
= de_getbyte_p(&pos
);
766 de_dbg(c
, "unused info byte: 0x%02x", (UI
)b
);
768 de_dbg(c
, "compressed data at %"I64_FMT
, pos
);
769 if(crunchctx
->fmtver
==1) {
770 decompress_crunch_v1(c
, crunchctx
, pos
);
773 // v2 is by far the most common version, but it's not easy to support.
774 // We support v1, only because it's easy.
775 de_err(c
, "This version of Crunch is not supported");
780 crcr_filename_data_freecontents(c
, &crunchctx
->fnd
);
781 de_free(c
, crunchctx
);
785 static int de_identify_crunch(deark
*c
)
790 if(id
==0xfe76) return 70;
794 void de_module_crunch(deark
*c
, struct deark_module_info
*mi
)
797 mi
->desc
= "Crunch (CP/M)";
798 mi
->run_fn
= de_run_crunch
;
799 mi
->identify_fn
= de_identify_crunch
;
802 ///////////////////////////////////////////////
803 // CRLZH - CP/M compressed file format
806 struct crcr_filename_data fnd
;
807 u8 fmtver
; // 1 or 2, 0 if unknown
809 UI checksum_reported
;
813 static void crlzh_writelistener_cb(dbuf
*f
, void *userdata
, const u8
*buf
, i64 buf_len
)
815 struct crlzh_ctx
*crlzhctx
= (struct crlzh_ctx
*)userdata
;
818 for(i
=0; i
<buf_len
; i
++) {
819 crlzhctx
->checksum_calc
+= buf
[i
];
823 static void decompress_crlzh(deark
*c
, struct crlzh_ctx
*crlzhctx
, i64 pos1
)
828 struct de_dfilter_in_params dcmpri
;
829 struct de_dfilter_out_params dcmpro
;
830 struct de_dfilter_results dres
;
831 struct de_lh1_params lh1p
;
834 fi
= de_finfo_create(c
);
835 de_finfo_set_name_from_ucstring(c
, fi
, crlzhctx
->fnd
.fn
, 0);
836 fi
->original_filename_flag
= 1;
838 outf
= dbuf_create_output_file(c
, NULL
, fi
, 0x0);
839 dbuf_set_writelistener(outf
, crlzh_writelistener_cb
, (void*)crlzhctx
);
841 de_dfilter_init_objects(c
, &dcmpri
, &dcmpro
, &dres
);
842 dcmpri
.f
= c
->infile
;
844 dcmpri
.len
= c
->infile
->len
- pos
;
847 de_zeromem(&lh1p
, sizeof(struct de_lh1_params
));
848 if(crlzhctx
->fmtver
==1) {
854 lh1p
.history_fill_val
= 0x20;
856 fmtutil_lh1_codectype1(c
, &dcmpri
, &dcmpro
, &dres
, (void*)&lh1p
);
859 de_err(c
, "Decompression failed: %s", de_dfilter_get_errmsg(c
, &dres
));
863 if(dres
.bytes_consumed_valid
) {
864 de_dbg(c
, "compressed data size: %"I64_FMT
", ends at %"I64_FMT
, dres
.bytes_consumed
,
865 dcmpri
.pos
+dres
.bytes_consumed
);
866 pos
+= dres
.bytes_consumed
;
868 if(crlzhctx
->cksum_type
==0) {
869 crlzhctx
->checksum_calc
&= 0xffff;
870 crlzhctx
->checksum_reported
= (UI
)de_getu16le_p(&pos
);
871 de_dbg(c
, "checksum (calculated): %u", crlzhctx
->checksum_calc
);
872 de_dbg(c
, "checksum (reported): %u", crlzhctx
->checksum_reported
);
873 if(crlzhctx
->checksum_calc
!= crlzhctx
->checksum_reported
) {
874 de_err(c
, "Checksum error. Decompression probably failed.");
881 de_finfo_destroy(c
, fi
);
883 de_dbg_indent(c
, -1);
886 static void de_run_crlzh(deark
*c
, de_module_params
*mparams
)
888 struct crlzh_ctx
*crlzhctx
= NULL
;
894 crlzhctx
= de_malloc(c
, sizeof(struct crlzh_ctx
));
897 if(!crcr_read_filename_etc(c
, pos
, &crlzhctx
->fnd
)) goto done
;
898 pos
+= crlzhctx
->fnd
.size
;
899 b
= de_getbyte_p(&pos
);
900 de_dbg(c
, "encoder version: 0x%02x", (UI
)b
);
902 fmtver_raw
= de_getbyte_p(&pos
);
903 if(fmtver_raw
<=0x1f) {
904 crlzhctx
->fmtver
= 1;
907 else if(fmtver_raw
>=0x20 && fmtver_raw
<=0x2f) {
908 // Note: Alternatives are ==0x20 (CFX), and >=0x20 (lbrate).
909 crlzhctx
->fmtver
= 2;
915 de_dbg(c
, "format version: 0x%02x (%s)", (UI
)fmtver_raw
, verstr
);
916 if(crlzhctx
->fmtver
!=0) {
917 de_declare_fmtf(c
, "CRLZH (v%d)", (int)crlzhctx
->fmtver
);
920 crlzhctx
->cksum_type
= de_getbyte_p(&pos
);
921 de_dbg(c
, "checksum type: 0x%02x (%s)", (UI
)crlzhctx
->cksum_type
,
922 (crlzhctx
->cksum_type
==0?"standard":"?"));
924 b
= de_getbyte_p(&pos
);
925 de_dbg(c
, "unused info byte: 0x%02x", (UI
)b
);
927 de_dbg(c
, "compressed data at %"I64_FMT
, pos
);
928 decompress_crlzh(c
, crlzhctx
, pos
);
932 crcr_filename_data_freecontents(c
, &crlzhctx
->fnd
);
933 de_free(c
, crlzhctx
);
937 static int de_identify_crlzh(deark
*c
)
942 if(id
==0xfd76) return 70;
946 void de_module_crlzh(deark
*c
, struct deark_module_info
*mi
)
949 mi
->desc
= "CRLZH (CP/M)";
950 mi
->run_fn
= de_run_crlzh
;
951 mi
->identify_fn
= de_identify_crlzh
;
954 ///////////////////////////////////////////////
956 // LZW compression utility by W. Chin, A. Kumar.
957 // Format used by v1.0, 1985-10-26.
959 #define CODE_WACK 0x5741434bU
962 de_encoding input_encoding
;
964 UI checksum_reported
;
966 struct de_timestamp timestamp
;
969 static void zsq_writelistener_cb(dbuf
*f
, void *userdata
, const u8
*buf
, i64 buf_len
)
971 struct zsq_ctx
*zsqctx
= (struct zsq_ctx
*)userdata
;
974 for(i
=0; i
<buf_len
; i
++) {
975 zsqctx
->checksum_calc
+= buf
[i
];
979 static void do_zsq_decompress(deark
*c
, struct zsq_ctx
*zsqctx
, i64 pos
, dbuf
*outf
)
981 struct de_dfilter_in_params dcmpri
;
982 struct de_dfilter_out_params dcmpro
;
983 struct de_dfilter_results dres
;
984 struct de_lzw_params delzwp
;
986 de_zeromem(&delzwp
, sizeof(struct de_lzw_params
));
987 delzwp
.fmt
= DE_LZWFMT_ARC5
;
989 de_dfilter_init_objects(c
, &dcmpri
, &dcmpro
, &dres
);
990 dcmpri
.f
= c
->infile
;
992 dcmpri
.len
= c
->infile
->len
- pos
;
995 dbuf_set_writelistener(outf
, zsq_writelistener_cb
, (void*)zsqctx
);
997 fmtutil_decompress_lzw(c
, &dcmpri
, &dcmpro
, &dres
, &delzwp
);
999 zsqctx
->checksum_calc
&= 0xffff;
1000 de_dbg(c
, "checksum (calculated): %u", (UI
)zsqctx
->checksum_calc
);
1001 if(zsqctx
->checksum_calc
!= zsqctx
->checksum_reported
) {
1002 de_err(c
, "Checksum error. Decompression probably failed.");
1006 static void zsq_read_timestamp(deark
*c
, struct zsq_ctx
*zsqctx
, i64 pos
)
1009 char timestamp_buf
[64];
1011 dt_raw
= de_getu16le(pos
);
1012 tm_raw
= de_getu16le(pos
+2);
1013 de_dos_datetime_to_timestamp(&zsqctx
->timestamp
, dt_raw
, tm_raw
);
1014 de_timestamp_to_string(&zsqctx
->timestamp
, timestamp_buf
, sizeof(timestamp_buf
), 0);
1015 de_dbg(c
, "timestamp: %s", timestamp_buf
);
1018 static void de_run_zsq(deark
*c
, de_module_params
*mparams
)
1020 struct zsq_ctx
*zsqctx
= NULL
;
1026 de_finfo
*fi
= NULL
;
1028 zsqctx
= de_malloc(c
, sizeof(struct zsq_ctx
));
1029 zsqctx
->input_encoding
= de_get_input_encoding(c
, NULL
, DE_ENCODING_CP437
);
1031 id
= (u32
)de_getu32be_p(&pos
);
1032 if(id
!= CODE_WACK
) {
1033 de_err(c
, "Not a ZSQ file");
1037 fi
= de_finfo_create(c
);
1039 zsqctx
->checksum_reported
= (u32
)de_getu16le_p(&pos
);
1040 de_dbg(c
, "checksum (reported): %u", (UI
)zsqctx
->checksum_reported
);
1042 hdr_len
= de_getu16le_p(&pos
);
1043 hdr_endpos
= pos
+ hdr_len
;
1044 if(hdr_endpos
> c
->infile
->len
) {
1045 de_err(c
, "Bad header length");
1049 zsq_read_timestamp(c
, zsqctx
, pos
);
1052 zsqctx
->fn
= ucstring_create(c
);
1053 dbuf_read_to_ucstring_n(c
->infile
, pos
, hdr_endpos
-pos
, 255, zsqctx
->fn
,
1054 DE_CONVFLAG_STOP_AT_NUL
, zsqctx
->input_encoding
);
1055 de_dbg(c
, "filename: \"%s\"", ucstring_getpsz_d(zsqctx
->fn
));
1057 de_finfo_set_name_from_ucstring(c
, fi
, zsqctx
->fn
, 0);
1058 fi
->original_filename_flag
= 1;
1061 de_dbg(c
, "compressed data at %"I64_FMT
, pos
);
1063 outf
= dbuf_create_output_file(c
, NULL
, fi
, 0);
1065 do_zsq_decompress(c
, zsqctx
, pos
, outf
);
1069 de_finfo_destroy(c
, fi
);
1071 ucstring_destroy(zsqctx
->fn
);
1076 static int de_identify_zsq(deark
*c
)
1078 if(de_getu32be(0)==CODE_WACK
) {
1084 void de_module_zsq(deark
*c
, struct deark_module_info
*mi
)
1087 mi
->desc
= "ZSQ (ZSQUSQ, LZW-compressed file)";
1088 mi
->run_fn
= de_run_zsq
;
1089 mi
->identify_fn
= de_identify_zsq
;
1092 // **************************************************************************
1094 // **************************************************************************
1097 int ver
; // 1, 2, or -1 if unknown
1098 struct de_crcobj
*crco
;
1101 static void lzwcom_detect_version(deark
*c
, struct lzwcom_ctx
*d
)
1103 u32 crc_reported
, crc_calc
;
1105 if(c
->infile
->len
< 1026) {
1110 de_crcobj_reset(d
->crco
);
1111 de_crcobj_addslice(d
->crco
, c
->infile
, 0, 1024);
1112 crc_calc
= de_crcobj_getval(d
->crco
); // Field only exists in v2 format
1113 crc_reported
= (u32
)de_getu16le(1024);
1114 if(crc_reported
==crc_calc
) {
1122 static void de_run_lzwcom(deark
*c
, de_module_params
*mparams
)
1124 struct lzwcom_ctx
*d
= NULL
;
1125 struct de_dfilter_ctx
*dfctx
= NULL
;
1127 struct de_dfilter_out_params dcmpro
;
1128 struct de_dfilter_results dres
;
1129 struct de_lzw_params delzwp
;
1134 d
= de_malloc(c
, sizeof(struct lzwcom_ctx
));
1136 d
->crco
= de_crcobj_create(c
, DE_CRCOBJ_CRC16_ARC
);
1138 s
= de_get_ext_option(c
, "lzwcom:version");
1140 d
->ver
= de_atoi(s
);
1142 if(d
->ver
>=2) d
->ver
= 2;
1143 else if(d
->ver
!=1) d
->ver
= -1;
1146 lzwcom_detect_version(c
, d
);
1149 de_declare_fmtf(c
, "LZWCOM v%d", d
->ver
);
1152 de_declare_fmt(c
, "LZWCOM (unknown version)");
1155 outf
= dbuf_create_output_file(c
, "unc", NULL
, 0);
1156 de_dfilter_init_objects(c
, NULL
, &dcmpro
, &dres
);
1159 de_zeromem(&delzwp
, sizeof(struct de_lzw_params
));
1160 delzwp
.fmt
= DE_LZWFMT_ARC5
;
1161 delzwp
.flags
|= DE_LZWFLAG_TOLERATETRAILINGJUNK
;
1162 dfctx
= de_dfilter_create(c
, dfilter_lzw_codec
, (void*)&delzwp
, &dcmpro
, &dres
);
1166 i64 block_pos
= pos
;
1168 if(dres
.errcode
) break;
1169 if(dfctx
->finished_flag
) break;
1170 if(pos
>= c
->infile
->len
) break;
1171 block_dlen
= de_min_int(1024, c
->infile
->len
- pos
);
1174 de_dbg(c
, "block at %"I64_FMT
", dlen=%"I64_FMT
, block_pos
, block_dlen
);
1177 de_dfilter_addslice(dfctx
, c
->infile
, pos
, block_dlen
);
1179 // Oddly, this format includes CRCs of the *compressed* bytes, instead of
1180 // of the decompressed bytes. So it doesn't detect incorrect decompression.
1182 de_crcobj_reset(d
->crco
);
1183 de_crcobj_addslice(d
->crco
, c
->infile
, pos
, block_dlen
);
1189 u32 crc_reported
, crc_calc
;
1191 if(c
->infile
->len
- pos
< 2) break;
1192 crc_calc
= de_crcobj_getval(d
->crco
);
1193 crc_reported
= (u32
)de_getu16le_p(&pos
);
1194 de_dbg_indent(c
, 1);
1195 de_dbg(c
, "crc (calculated): 0x%04x", (UI
)crc_calc
);
1196 de_dbg(c
, "crc (reported): 0x%04x", (UI
)crc_reported
);
1197 de_dbg_indent(c
,- 1);
1198 if(!errflag
&& crc_calc
!=crc_reported
) {
1199 de_warn(c
, "CRC check failed at %"I64_FMT
". This might not be an LZWCOM v2 file.", pos
-2);
1205 de_dfilter_finish(dfctx
);
1207 de_err(c
, "Decompression failed: %s", de_dfilter_get_errmsg(c
, &dres
));
1210 de_dfilter_destroy(dfctx
);
1213 de_crcobj_destroy(d
->crco
);
1218 static void de_help_lzwcom(deark
*c
)
1220 de_msg(c
, "-opt lzwcom:version=<1|2> : The format version");
1223 void de_module_lzwcom(deark
*c
, struct deark_module_info
*mi
)
1226 mi
->desc
= "LZWCOM compressed file";
1227 mi
->run_fn
= de_run_lzwcom
;
1228 mi
->identify_fn
= NULL
;
1229 mi
->help_fn
= de_help_lzwcom
;