bmp: Rewrote the RLE decompressor
[deark.git] / modules / os2pack.c
blob209190a34bd632bff43f65fab4291637f5f40a72
1 // This file is part of Deark.
2 // Copyright (C) 2024 Jason Summers
3 // See the file COPYING for terms of use.
5 // OS/2 PACK, and PACK2 (FTCOMP)
7 #include <deark-private.h>
8 #include <deark-fmtutil.h>
9 #include <deark-fmtutil-arch.h>
10 DE_DECLARE_MODULE(de_module_os2pack);
11 DE_DECLARE_MODULE(de_module_os2pack2);
13 #define OS2PACK_MINHEADERLEN 10
15 static UI os2pack_is_member_at(dbuf *f, i64 pos)
17 UI sig;
19 sig = (UI)dbuf_getu32be(f, pos);
20 return (sig==0xa596feffU || sig==0xa596ffffU ||
21 sig==0xa5960014U || sig==0xa596140aU) ? 1 : 0;
24 static UI os2pack2_is_member_at(dbuf *f, i64 pos)
26 UI sig;
28 sig = (UI)dbuf_getu32be(f, pos);
29 if(sig!=0xa596fdffU) return 0;
30 if(dbuf_memcmp(f, pos+24, (const void*)"FTCOMP", 6)) return 0;
31 return 1;
34 static UI os2pack12_is_member_at(de_arch_lctx *d, i64 pos)
36 if(d->fmtcode==0xfffd) {
37 return os2pack2_is_member_at(d->inf, pos);
39 return os2pack_is_member_at(d->inf, pos);
42 static void os2pack_decompressor_fn(struct de_arch_member_data *md)
44 fmtutil_ibmlzw_codectype1(md->c, md->dcmpri, md->dcmpro, md->dres, NULL);
47 static void os2pack2_read_cmpr_method(deark *c, i64 pos, i64 len)
49 struct de_fourcc cmpr4cc;
51 if(len<8) return;
52 dbuf_read_fourcc(c->infile, pos+4, &cmpr4cc, 4, 0x0);
53 de_dbg(c, "cmpr meth: '%s'", cmpr4cc.id_dbgstr); // Usually "fT19"
56 static void do_os2pack1_ea(deark *c, de_arch_lctx *d, struct de_arch_member_data *md,
57 i64 ea_pos, i64 ea_len)
59 int saved_indent_level;
60 dbuf *attr_data = NULL;
61 de_module_params *mparams = NULL;
62 struct de_dfilter_in_params dcmpri;
63 struct de_dfilter_out_params dcmpro;
64 struct de_dfilter_results dres;
66 de_dbg_indent_save(c, &saved_indent_level);
67 de_dbg(c, "ext. attr. at %"I64_FMT, ea_pos);
68 de_dbg_indent(c, 1);
69 attr_data = dbuf_create_membuf(c, 0, 0);
70 dbuf_set_length_limit(attr_data, 1024*1024);
72 de_dfilter_init_objects(c, &dcmpri, &dcmpro, &dres);
73 dcmpri.f = c->infile;
74 dcmpri.pos = ea_pos;
75 dcmpri.len = ea_len;
76 dcmpro.f = attr_data;
77 fmtutil_ibmlzw_codectype1(c, &dcmpri, &dcmpro, &dres, NULL);
78 dbuf_flush(attr_data);
79 if(dres.errcode) {
80 de_warn(c, "Failed to decompress ext. attr. data");
81 goto done;
83 de_dbg(c, "decompressed len: %"I64_FMT, attr_data->len);
85 mparams = de_malloc(c, sizeof(de_module_params));
86 mparams->in_params.codes = "R";
87 if(ucstring_isnonempty(md->filename)) {
88 mparams->in_params.str1 = md->filename;
89 mparams->in_params.flags |= 0x8;
91 de_run_module_by_id_on_slice(c, "ea_data", mparams, attr_data, 0, attr_data->len);
93 done:
94 dbuf_close(attr_data);
95 de_free(c, mparams);
96 de_dbg_indent_restore(c, saved_indent_level);
99 static void do_os2pack12_member(deark *c, de_arch_lctx *d, struct de_arch_member_data *md)
101 i64 pos;
102 i64 fnlen;
103 i64 ea_pos = 0;
104 i64 ea_len = 0;
105 i64 member_endpos;
106 i64 unk2, unk3, unk4;
107 UI attribs;
108 u8 flag_unsupp = 0;
109 int saved_indent_level;
111 de_dbg_indent_save(c, &saved_indent_level);
112 de_dbg(c, "member at %"I64_FMT, md->member_hdr_pos);
113 de_dbg_indent(c, 1);
115 pos = md->member_hdr_pos + 4;
117 de_arch_read_field_dttm_p(d, &md->fi->timestamp[DE_TIMESTAMPIDX_MODIFY], "mod",
118 DE_ARCH_TSTYPE_DOS_DT, &pos);
120 // Apparently a file attributes field, but don't know if it's 1, 2, or 4 bytes.
121 attribs = (UI)de_getbyte_p(&pos);
122 de_arch_handle_field_dos_attr(md, attribs);
124 if(d->fmtcode==0x1400 || d->fmtcode==0x0a14) {
125 pos += 1;
127 else if(d->fmtcode==0xffff) {
128 pos += 6; // ?
130 else {
131 pos += 3;
132 ea_pos = de_getu32le_p(&pos);
133 de_dbg(c, "ext. attr. pos: %"I64_FMT, ea_pos);
135 de_arch_read_field_orig_len_p(md, &pos);
136 // TODO: Figure out why some files have 1 here, and others have the original
137 // file size.
138 if(d->fmtcode==0xfffe && md->orig_len==1) {
139 md->orig_len = 0;
140 md->orig_len_known = 0;
143 md->next_member_pos = de_getu32le_p(&pos);
144 de_dbg(c, "next member pos: %"I64_FMT, md->next_member_pos);
145 if(md->next_member_pos!=0) {
146 md->next_member_exists = 1;
150 if(d->fmtcode==0xfffd) {
151 pos += 7; // "FTCOMP\0"
153 unk2 = de_getu16le_p(&pos);
154 de_dbg(c, "unk2: %u", (UI)unk2);
156 unk3 = de_getu16le_p(&pos);
157 de_dbg(c, "unk3: %u", (UI)unk3);
159 unk4 = de_getu32le_p(&pos);
160 de_dbg(c, "unk4: %"I64_FMT, unk4);
163 if(d->fmtcode==0x1400 || d->fmtcode==0x0a14) {
164 fnlen = 13;
166 else if(d->fmtcode==0xffff) {
167 i64 foundpos = 0;
169 if(dbuf_search_byte(c->infile, 0, pos, 260, &foundpos)) {
170 fnlen = foundpos + 1 - pos;
172 else {
173 d->need_errmsg = 1;
174 goto done;
177 else {
178 fnlen = de_getu16le_p(&pos);
181 dbuf_read_to_ucstring_n(c->infile, pos, fnlen, 512, md->filename,
182 DE_CONVFLAG_STOP_AT_NUL, d->input_encoding);
183 de_dbg(c, "filename: \"%s\"", ucstring_getpsz_d(md->filename));
184 pos += fnlen;
185 de_arch_fixup_path(md->filename, 0);
186 md->set_name_flags |= DE_SNFLAG_FULLPATH;
188 md->cmpr_pos = pos;
190 if(md->next_member_exists) {
191 member_endpos = md->next_member_pos;
193 else {
194 member_endpos = c->infile->len;
197 if(member_endpos > c->infile->len) {
198 d->fatalerrflag = 1;
199 d->need_errmsg = 1;
200 goto done;
203 md->cmpr_len = member_endpos - md->cmpr_pos;
204 // if ea_pos is set, adjust cmpr_len downward
205 if(ea_pos != 0) {
206 if(ea_pos<md->cmpr_pos || ea_pos>member_endpos) {
207 d->fatalerrflag = 1;
208 d->need_errmsg = 1;
209 goto done;
211 md->cmpr_len = ea_pos - md->cmpr_pos;
212 ea_len = member_endpos - ea_pos;
215 if(md->cmpr_len<0) {
216 d->fatalerrflag = 1;
217 d->need_errmsg = 1;
218 goto done;
221 if(ea_len>0) {
222 de_dbg(c, "cmpr ext. attr. at %"I64_FMT", len=%"I64_FMT, ea_pos, ea_len);
223 de_dbg_indent(c, 1);
224 if(d->fmtcode==0xfffd) {
225 os2pack2_read_cmpr_method(c, ea_pos, ea_len);
227 de_dbg_indent(c, -1);
230 de_dbg(c, "cmpr data at %"I64_FMT", len=%"I64_FMT, md->cmpr_pos, md->cmpr_len);
232 de_dbg_indent(c, 1);
233 if(d->fmtcode==0xfffd) {
234 // Most likely, the compressed data is considered to start after the
235 // filename field.
236 // It seems to have a compression header that we can peek at.
237 os2pack2_read_cmpr_method(c, md->cmpr_pos, md->cmpr_len);
240 if(d->fmtcode==0x1400 || d->fmtcode==0x0a14 || d->fmtcode==0xffff ||
241 d->fmtcode==0xfffe)
243 md->dfn = os2pack_decompressor_fn;
244 de_arch_extract_member_file(md);
245 if(ea_pos>0 && ea_len>0) {
246 do_os2pack1_ea(c, d, md, ea_pos, ea_len);
250 de_dbg_indent(c, -1);
252 // TODO: There may be a few more bytes at the end of a member, purpose unknown.
253 // If so, we should adjust md->cmpr_len or ea_len accordingly.
255 done:
256 if(flag_unsupp) {
257 de_err(c, "Unsupported member file format");
259 de_dbg_indent_restore(c, saved_indent_level);
262 static void do_run_os2pack12(deark *c, de_module_params *mparams, UI ver)
264 de_arch_lctx *d = NULL;
265 struct de_arch_member_data *md = NULL;
266 i64 pos;
267 const char *pname = "PACK";
269 d = de_arch_create_lctx(c);
270 d->is_le = 1;
272 if(ver==2) {
273 d->fmtcode = 0xfffd;
275 else {
276 d->fmtcode = (UI)de_getu16le(2);
277 if(d->fmtcode!=0x1400 && d->fmtcode!=0x0a14 && d->fmtcode!=0xffff &&
278 d->fmtcode!=0xfffe)
280 d->need_errmsg = 1;
281 goto done;
285 if(d->fmtcode==0xfffd) {
286 pname = "PACK2";
287 de_declare_fmt(c, "OS/2 PACK2 archive");
289 else {
290 de_declare_fmtf(c, "OS/2 PACK archive (type 0x%04x)", d->fmtcode);
293 // TODO: What encoding to use?
294 d->input_encoding = de_get_input_encoding(c, NULL, DE_ENCODING_WINDOWS1252);
296 pos = 0;
298 while(1) {
299 if(pos+OS2PACK_MINHEADERLEN > c->infile->len) {
300 d->need_errmsg = 1;
301 goto done;
304 if(!os2pack12_is_member_at(d, pos)) {
305 d->need_errmsg = 1;
306 goto done;
309 if(md) {
310 de_arch_destroy_md(c, md);
311 md = NULL;
313 md = de_arch_create_md(c, d);
314 md->member_hdr_pos = pos;
316 do_os2pack12_member(c, d, md);
318 if(d->fatalerrflag) goto done;
319 if(!md->next_member_exists) goto done;
320 if(md->next_member_pos <= pos) {
321 d->need_errmsg = 1;
322 goto done;
324 pos = md->next_member_pos;
327 done:
328 if(md) {
329 de_arch_destroy_md(c, md);
330 md = NULL;
332 if(d) {
333 if(d->need_errmsg) {
334 de_err(c, "Bad or unsupported OS/2 %s archive", pname);
336 de_arch_destroy_lctx(c, d);
340 static void de_run_os2pack(deark *c, de_module_params *mparams)
342 do_run_os2pack12(c, mparams, 1);
345 static void de_run_os2pack2(deark *c, de_module_params *mparams)
347 do_run_os2pack12(c, mparams, 2);
350 static int de_identify_os2pack(deark *c)
352 return (os2pack_is_member_at(c->infile, 0)) ? 100 : 0;
355 void de_module_os2pack(deark *c, struct deark_module_info *mi)
357 mi->id = "os2pack";
358 mi->desc = "OS/2 PACK archive";
359 mi->run_fn = de_run_os2pack;
360 mi->identify_fn = de_identify_os2pack;
363 static int de_identify_os2pack2(deark *c)
365 return (os2pack2_is_member_at(c->infile, 0)) ? 100 : 0;
368 void de_module_os2pack2(deark *c, struct deark_module_info *mi)
370 mi->id = "os2pack2";
371 mi->desc = "OS/2 PACK2 archive";
372 mi->run_fn = de_run_os2pack2;
373 mi->flags |= DE_MODFLAG_WARNPARSEONLY;
374 mi->identify_fn = de_identify_os2pack2;