fnt: Improved error handling, etc.
[deark.git] / modules / packdir.c
blob7f0c58a708ded60ec7975a02126a1bf4d1b63032
1 // This file is part of Deark.
2 // Copyright (C) 2019 Jason Summers
3 // See the file COPYING for terms of use.
5 // PackDir compressed archive format
7 #include <deark-config.h>
8 #include <deark-private.h>
9 #include <deark-fmtutil.h>
11 DE_DECLARE_MODULE(de_module_packdir);
13 #define MAX_NESTING_LEVEL 32
15 struct pdctx_object {
16 u32 object_type;
17 u8 is_dir;
18 i64 num_children; // valid if is_dir
19 i64 orig_len; // valid if !is_dir
20 i64 cmpr_len;
21 int is_compressed;
22 de_ucstring *name;
23 struct de_riscos_file_attrs rfa;
26 struct pdctx_struct {
27 unsigned int lzw_maxbits;
28 struct de_strarray *curpath;
31 static int do_packdir_header(deark *c, struct pdctx_struct *d)
33 unsigned int maxbits_raw;
34 i64 pos = 0;
35 int retval = 0;
37 de_dbg(c, "header at %"I64_FMT, pos);
38 de_dbg_indent(c, 1);
39 pos += 5; // signature
40 maxbits_raw = (unsigned int)de_getu32le_p(&pos);
41 d->lzw_maxbits = maxbits_raw + 12;
42 de_dbg(c, "lzw maxbits: %u (+12=%u)", maxbits_raw, d->lzw_maxbits);
43 if(d->lzw_maxbits>16) {
44 de_err(c, "Unsupported \"maxbits\" value: %u", d->lzw_maxbits);
45 goto done;
47 retval = 1;
48 done:
49 de_dbg_indent(c, -1);
50 return retval;
53 static void decompress_zoo_lzd(deark *c, struct de_dfilter_in_params *dcmpri,
54 struct de_dfilter_out_params *dcmpro, struct de_dfilter_results *dres, int maxbits)
56 struct de_lzw_params delzwp;
58 de_zeromem(&delzwp, sizeof(struct de_lzw_params));
59 delzwp.fmt = DE_LZWFMT_ZOOLZD;
60 delzwp.max_code_size = (unsigned int)maxbits;
61 fmtutil_decompress_lzw(c, dcmpri, dcmpro, dres, &delzwp);
64 static void do_packdir_file_compressed(deark *c, struct pdctx_struct *d,
65 struct pdctx_object *md, i64 pos, dbuf *outf)
67 struct de_dfilter_in_params dcmpri;
68 struct de_dfilter_out_params dcmpro;
69 struct de_dfilter_results dres;
71 de_dfilter_init_objects(c, &dcmpri, &dcmpro, &dres);
72 dcmpri.f = c->infile;
73 dcmpri.pos = pos;
74 dcmpri.len = md->cmpr_len;
75 dcmpro.f = outf;
76 dcmpro.len_known = 1;
77 dcmpro.expected_len = md->orig_len;
79 decompress_zoo_lzd(c, &dcmpri, &dcmpro, &dres, d->lzw_maxbits);
80 dbuf_flush(outf);
82 if(dres.errcode) {
83 de_err(c, "%s: %s", ucstring_getpsz_d(md->name), de_dfilter_get_errmsg(c, &dres));
85 else if(outf->len != md->orig_len) {
86 de_err(c, "%s: Expected %"I64_FMT" decompressed bytes, got %"I64_FMT,
87 ucstring_getpsz_d(md->name), md->orig_len, outf->len);
91 static void do_packdir_extract_file(deark *c, struct pdctx_struct *d,
92 struct pdctx_object *md, i64 pos)
94 dbuf *outf = NULL;
95 de_finfo *fi = NULL;
96 de_ucstring *fullfn = NULL;
98 de_dbg(c, "%"I64_FMT" bytes of %scompressed data at %"I64_FMT,
99 md->cmpr_len, (md->is_compressed?"":"un"), pos);
101 if(pos + md->cmpr_len > c->infile->len) {
102 de_err(c, "Unexpected EOF");
103 goto done;
106 fi = de_finfo_create(c);
108 fullfn = ucstring_create(c);
109 if(md->is_dir) {
110 fi->is_directory = 1;
111 de_strarray_make_path(d->curpath, fullfn, DE_MPFLAG_NOTRAILINGSLASH);
113 else {
114 de_strarray_make_path(d->curpath, fullfn, 0);
115 ucstring_append_ucstring(fullfn, md->name);
118 fmtutil_riscos_append_type_to_filename(c, fi, fullfn, &md->rfa, md->is_dir, 0);
119 de_finfo_set_name_from_ucstring(c, fi, fullfn, DE_SNFLAG_FULLPATH);
120 fi->original_filename_flag = 1;
122 fi->timestamp[DE_TIMESTAMPIDX_MODIFY] = md->rfa.mod_time;
124 fi->has_riscos_data = 1;
125 fi->riscos_attribs = md->rfa.attribs;
126 fi->load_addr = md->rfa.load_addr;
127 fi->exec_addr = md->rfa.exec_addr;
129 outf = dbuf_create_output_file(c, NULL, fi, 0);
131 if(md->is_compressed) {
132 dbuf_enable_wbuffer(outf);
133 do_packdir_file_compressed(c, d, md, pos, outf);
135 else {
136 dbuf_copy(c->infile, pos, md->cmpr_len, outf);
139 done:
140 dbuf_close(outf);
141 de_finfo_destroy(c, fi);
142 ucstring_destroy(fullfn);
145 // The name of the root object is usually something ugly like
146 // "RAM::RamDisc0.$.MyProg". Try to make it nicer by only using the last part
147 // of it.
148 static void convert_root_name(deark *c, struct pdctx_struct *d,
149 de_ucstring *nsrc, de_ucstring *ndst)
151 i64 k;
153 for(k=0; k<nsrc->len; k++) {
154 i32 ch = nsrc->str[k];
155 if(ch=='.' || ch==':') {
156 ucstring_empty(ndst);
158 else {
159 ucstring_append_char(ndst, ch);
164 // Process and object, and all its descendants.
165 // Returns 0 on fatal error.
166 static int do_packdir_object(deark *c, struct pdctx_struct *d, i64 pos1,
167 int level, i64 *bytes_consumed1)
169 int saved_indent_level;
170 i64 foundpos = 0;
171 i64 pos = pos1;
172 i64 name_len;
173 i64 length_raw;
174 struct pdctx_object *md = NULL;
175 int retval = 0;
176 int need_dirname_pop = 0;
178 de_dbg_indent_save(c, &saved_indent_level);
180 if(level >= MAX_NESTING_LEVEL) {
181 goto done;
184 md = de_malloc(c, sizeof(struct pdctx_object));
185 de_dbg(c, "object at %"I64_FMT, pos1);
186 de_dbg_indent(c, 1);
187 *bytes_consumed1 = 0;
189 if(!dbuf_search_byte(c->infile, 0x00, pos, 128, &foundpos)) {
190 goto done;
192 name_len = foundpos - pos1;
193 md->name = ucstring_create(c);
194 dbuf_read_to_ucstring(c->infile, pos, name_len, md->name, 0x0, DE_ENCODING_RISCOS);
195 de_dbg(c, "name: \"%s\"", ucstring_getpsz_d(md->name));
196 pos += name_len + 1;
198 fmtutil_riscos_read_load_exec(c, c->infile, &md->rfa, pos);
199 pos += 8;
201 length_raw = de_getu32le_p(&pos);
203 fmtutil_riscos_read_attribs_field(c, c->infile, &md->rfa, pos, 0);
204 pos += 4;
206 if(level==0) {
207 md->object_type = 1;
209 else {
210 md->object_type = (u32)de_getu32le_p(&pos);
211 de_dbg(c, "type: %u", (unsigned int)md->object_type);
214 if(md->object_type==0) {
215 ; // regular file
217 else if(md->object_type==1) {
218 md->is_dir = 1;
220 else {
221 goto done; // unknown type
224 if(md->is_dir) {
225 i64 bytes_consumed2 = 0;
226 i64 i;
227 int ret;
229 md->num_children = length_raw;
230 de_dbg(c, "number of dir entries: %"I64_FMT, md->num_children);
232 if(level<=0) {
233 de_ucstring *tmpstr = ucstring_create(c);
234 convert_root_name(c, d, md->name, tmpstr);
235 de_strarray_push(d->curpath, tmpstr);
236 ucstring_destroy(tmpstr);
238 else {
239 de_strarray_push(d->curpath, md->name);
240 need_dirname_pop = 1;
243 md->is_compressed = 0;
244 md->orig_len = 0;
245 md->cmpr_len = 0;
246 do_packdir_extract_file(c, d, md, pos);
248 for(i=0; i<md->num_children; i++) {
249 if(pos >= c->infile->len) goto done;
250 ret = do_packdir_object(c, d, pos, level+1, &bytes_consumed2);
251 if((!ret) || bytes_consumed2<1) goto done;
252 pos += bytes_consumed2;
255 else {
256 md->orig_len = length_raw;
257 de_dbg(c, "original len: %"I64_FMT, md->orig_len);
259 md->cmpr_len = de_getu32le_p(&pos);
260 if(md->cmpr_len==0xffffffffLL) {
261 // uncompressed
262 md->cmpr_len = md->orig_len;
264 else {
265 md->is_compressed = 1;
267 de_dbg(c, "is compressed: %d", md->is_compressed);
268 if(md->is_compressed) {
269 de_dbg(c, "cmpr len: %"I64_FMT, md->cmpr_len);
272 do_packdir_extract_file(c, d, md, pos);
274 pos += md->cmpr_len;
277 *bytes_consumed1 = pos - pos1;
278 retval = 1;
280 done:
281 if(!retval && c->error_count==0) {
282 de_err(c, "Can't parse object at %"I64_FMT, pos1);
284 if(md) {
285 ucstring_destroy(md->name);
286 de_free(c, md);
288 if(need_dirname_pop) {
289 de_strarray_pop(d->curpath);
291 de_dbg_indent_restore(c, saved_indent_level);
292 return retval;
295 static void de_run_packdir(deark *c, de_module_params *mparams)
297 struct pdctx_struct *d = NULL;
298 i64 bytes_consumed;
300 d = de_malloc(c, sizeof(struct pdctx_struct));
302 if(!do_packdir_header(c, d)) goto done;
303 d->curpath = de_strarray_create(c, MAX_NESTING_LEVEL+10);
304 do_packdir_object(c, d, 9, 0, &bytes_consumed);
306 done:
307 if(d) {
308 de_strarray_destroy(d->curpath);
309 de_free(c, d);
313 static int de_identify_packdir(deark *c)
315 i64 n;
317 if(dbuf_memcmp(c->infile, 0, "PACK\0", 5)) return 0;
318 n = de_getu32le(5);
319 if(n<=4) return 100; // maxbits = 12...16
320 if(n<=8) return 10; // Dunno what the "maxbits" limit is.
321 return 0; // Could be Git pack format
324 void de_module_packdir(deark *c, struct deark_module_info *mi)
326 mi->id = "packdir";
327 mi->desc = "PackDir compressed archive format";
328 mi->run_fn = de_run_packdir;
329 mi->identify_fn = de_identify_packdir;