fnt: Improved error handling, etc.
[deark.git] / modules / crush.c
blob6a3329ecc317d8b0a99f3e571d5dd47f0cca8d68
1 // This file is part of Deark.
2 // Copyright (C) 2020 Jason Summers
3 // See the file COPYING for terms of use.
5 // CRUSH archive (PocketWare)
6 // Format is documented (CRUSH18.ZIP/MANUAL.DOC), though not in full detail.
8 #include <deark-private.h>
9 #include <deark-fmtutil.h>
10 #include <deark-fmtutil-arch.h>
11 DE_DECLARE_MODULE(de_module_crush);
13 #define CRUSH_HEADER_LEN 26
14 #define CRUSH_DIRENTRY_LEN 24
16 struct crush_member_data {
17 UI path_num; // 0 = no path, 1 = paths[0], 2 = paths[1], ...
20 typedef struct localctx_struct {
21 struct de_arch_localctx_struct *da;
22 UI ver_maj, ver_min;
23 int is_cri;
24 i64 num_paths;
25 i64 dir_segment_pos;
26 int paths_segment_len_known;
27 i64 paths_segment_pos;
28 i64 paths_segment_len;
29 de_ucstring **paths; // array[num_paths]
30 } lctx;
32 // Squash slashes, ensure not empty.
33 static void fixup_filename(de_ucstring *s)
35 i64 i;
37 for(i=0; i<s->len; i++) {
38 if(s->str[i]=='/') {
39 s->str[i] = '_';
43 if(ucstring_isempty(s)) {
44 ucstring_append_char(s, '_');
48 static int do_read_paths(deark *c, lctx *d)
50 i64 pos;
51 i64 i;
52 int retval = 0;
53 int saved_indent_level;
55 de_dbg_indent_save(c, &saved_indent_level);
56 if(d->num_paths > 255) {
57 // Number of paths can't be >255 in the known versions of the format,
58 // because paths are indexed by a 1-byte int.
59 d->num_paths = 255;
62 if(d->num_paths <= 0) {
63 d->paths_segment_len = 0;
64 d->paths_segment_len_known = 1;
65 retval = 1;
66 goto done;
69 d->paths = de_mallocarray(c, d->num_paths, sizeof(de_ucstring*));
70 for(i=0; i<d->num_paths; i++) {
71 d->paths[i] = ucstring_create(c);
74 de_dbg(c, "paths at %"I64_FMT, d->paths_segment_pos);
75 de_dbg_indent(c, 1);
77 pos = d->paths_segment_pos;
78 for(i=0; i<d->num_paths; i++) {
79 int ret;
80 i64 foundpos = 0;
81 i64 path_len;
83 if(pos >= c->infile->len) goto done;
84 ret = dbuf_search_byte(c->infile, 0x00, pos, c->infile->len-pos, &foundpos);
85 if(!ret) goto done;
86 path_len = foundpos - pos;
87 if(path_len > 260) goto done;
88 dbuf_read_to_ucstring(c->infile, pos, path_len, d->paths[i], 0, d->da->input_encoding);
89 de_dbg(c, "path #%d: \"%s\"", (int)(i+1), ucstring_getpsz_d(d->paths[i]));
90 de_arch_fixup_path(d->paths[i], 0x1);
91 pos = foundpos + 1;
93 d->paths_segment_len = pos - d->paths_segment_pos;
94 d->paths_segment_len_known = 1;
95 retval = 1;
96 done:
97 if(!retval) {
98 de_warn(c, "Could not read path table");
100 de_dbg_indent_restore(c, saved_indent_level);
101 return retval;
104 static void do_comment(deark *c, lctx *d)
106 i64 pos;
107 i64 len;
108 i64 avail_len;
109 i64 foundpos;
110 int ret;
111 de_ucstring *s = NULL;
113 if(!d->paths_segment_len_known) goto done;
114 pos = d->paths_segment_pos + d->paths_segment_len;
115 avail_len = c->infile->len - pos;
116 if(avail_len<=1) goto done;
118 // Find the terminating NUL
119 ret = dbuf_search_byte(c->infile, 0x00, pos, c->infile->len-pos, &foundpos);
120 if(!ret) goto done;
121 len = foundpos - pos;
122 if(len<1 || len>2048) goto done;
124 de_dbg(c, "comment at %"I64_FMT, pos);
125 s = ucstring_create(c);
126 dbuf_read_to_ucstring(c->infile, pos, len, s, 0,
127 DE_EXTENC_MAKE(d->da->input_encoding, DE_ENCSUBTYPE_HYBRID));
128 de_dbg_indent(c, 1);
129 de_dbg(c, "archive comment: \"%s\"", ucstring_getpsz_d(s));
130 de_dbg_indent(c, -1);
132 done:
133 ucstring_destroy(s);
136 static void make_fullfilename(deark *c, lctx *d, struct de_arch_member_data *md)
138 struct crush_member_data *mdc = (struct crush_member_data*)md->userdata;
140 if(mdc->path_num>0 && mdc->path_num<=d->num_paths &&
141 d->paths && ucstring_isnonempty(d->paths[mdc->path_num-1]))
143 ucstring_append_ucstring(md->filename, d->paths[mdc->path_num-1]);
146 ucstring_append_ucstring(md->filename, md->tmpfn_base);
149 static void uncmpr_decompressor_fn(struct de_arch_member_data *md)
151 fmtutil_decompress_uncompressed(md->c, md->dcmpri, md->dcmpro, md->dres, 0);
154 // Uses and updates d->file_data_curpos
155 static int do_member_file(deark *c, lctx *d, i64 idx, i64 pos1)
157 int retval = 0;
158 struct de_arch_member_data *md = NULL;
159 struct crush_member_data *mdc = NULL;
160 de_ucstring *descr = NULL;
161 i64 pos;
162 int saved_indent_level;
164 de_dbg_indent_save(c, &saved_indent_level);
165 md = de_arch_create_md(c, d->da);
166 mdc = de_malloc(c, sizeof(struct crush_member_data));
167 md->userdata = (void*)mdc;
168 md->tmpfn_base = ucstring_create(c);
170 de_dbg(c, "member file[%d]", (int)idx);
171 de_dbg_indent(c, 1);
172 de_dbg(c, "dir entry at %"I64_FMT, pos1);
173 de_dbg_indent(c, 1);
174 pos = pos1;
176 mdc->path_num = (UI)de_getbyte_p(&pos);
177 de_dbg(c, "path num: %u", mdc->path_num);
179 de_arch_read_field_dos_attr_p(md, &pos);
181 de_arch_read_field_dttm_p(d->da, &md->fi->timestamp[DE_TIMESTAMPIDX_MODIFY], "mod",
182 DE_ARCH_TSTYPE_DOS_TD, &pos);
184 de_arch_read_field_orig_len_p(md, &pos);
185 md->cmpr_len = md->orig_len;
187 dbuf_read_to_ucstring(c->infile, pos, 12, md->tmpfn_base, DE_CONVFLAG_STOP_AT_NUL,
188 d->da->input_encoding);
189 de_dbg(c, "filename: \"%s\"", ucstring_getpsz_d(md->tmpfn_base));
190 fixup_filename(md->tmpfn_base);
191 make_fullfilename(c, d, md);
192 md->set_name_flags |= DE_SNFLAG_FULLPATH;
193 de_dbg_indent(c, -1);
195 // If this is just an index (.CRI) file, there's nothing to extract.
196 // We can't tell the difference between an index file, and an archive file
197 // containing only zero-length members, so extract zero-length files just
198 // in case.
199 if(d->is_cri && md->cmpr_len!=0) {
200 retval = 1;
201 goto done;
204 de_dbg(c, "file data at %"I64_FMT, d->da->cmpr_data_curpos);
206 if(d->da->cmpr_data_curpos + md->cmpr_len > d->dir_segment_pos) {
207 de_err(c, "Malformed CRU archive");
208 goto done;
211 md->cmpr_pos = d->da->cmpr_data_curpos;
212 d->da->cmpr_data_curpos += md->cmpr_len;
213 retval = 1;
215 if((md->dos_attribs & 0x18) != 0x00) {
216 // I don't know if subdirs or volume labels can be in these archives.
217 de_warn(c, "%s: Not a regular file", ucstring_getpsz_d(md->filename));
220 md->dfn = uncmpr_decompressor_fn;
221 de_arch_extract_member_file(md);
223 done:
224 ucstring_destroy(descr);
225 if(mdc) {
226 de_free(c, mdc);
228 de_arch_destroy_md(c, md);
229 de_dbg_indent_restore(c, saved_indent_level);
230 return retval;
233 static void do_read_dir_and_extract_files(deark *c, lctx *d)
235 i64 i;
236 int saved_indent_level;
238 de_dbg_indent_save(c, &saved_indent_level);
240 if(d->da->num_members<1) {
241 goto done;
243 de_dbg(c, "directory at %"I64_FMT, d->dir_segment_pos);
244 de_dbg_indent(c, 1);
246 d->da->cmpr_data_curpos = CRUSH_HEADER_LEN;
248 for(i=0; i<d->da->num_members; i++) {
249 if(!do_member_file(c, d, i, d->dir_segment_pos + CRUSH_DIRENTRY_LEN * i)) {
250 goto done;
254 done:
255 de_dbg_indent_restore(c, saved_indent_level);
258 static int do_archive_header(deark *c, lctx *d)
260 i64 pos1 = 0;
261 u8 b;
263 de_dbg(c, "archive header at %"I64_FMT, pos1);
264 de_dbg_indent(c, 1);
265 b = de_getbyte(pos1+7);
266 if(b>='0' && b<='9') d->ver_maj = (UI)(b-'0');
267 b = (UI)de_getbyte(pos1+9);
268 if(b>='0' && b<='9') d->ver_min = (UI)(b-'0');
269 de_dbg(c, "version: %u.%u", d->ver_maj, d->ver_min);
271 d->num_paths = de_getu16le(pos1+16);
272 de_dbg(c, "num paths %"I64_FMT, d->num_paths);
273 d->da->num_members = de_getu16le(pos1+18);
274 de_dbg(c, "num files %"I64_FMT, d->da->num_members);
275 d->dir_segment_pos = de_getu32le(pos1+22);
276 de_dbg(c, "directory pos: %"I64_FMT, d->dir_segment_pos);
278 de_dbg_indent(c, -1);
279 return 1;
282 static void de_run_crush(deark *c, de_module_params *mparams)
284 lctx *d = NULL;
286 d = de_malloc(c, sizeof(lctx));
287 d->da = de_arch_create_lctx(c);
288 d->da->userdata = (void*)d;
289 d->da->is_le = 1;
291 d->da->input_encoding = de_get_input_encoding(c, NULL, DE_ENCODING_CP437);
293 if(!do_archive_header(c, d)) goto done;
294 d->is_cri = (d->dir_segment_pos == CRUSH_HEADER_LEN);
295 de_declare_fmtf(c, "CRUSH %s", d->is_cri ? "index" : "archive");
297 d->paths_segment_pos = d->dir_segment_pos + (CRUSH_DIRENTRY_LEN * d->da->num_members);
299 (void)do_read_paths(c, d);
300 do_comment(c, d);
301 do_read_dir_and_extract_files(c, d);
303 done:
304 if(d) {
305 if(d->paths) {
306 i64 i;
308 for(i=0; i<d->num_paths; i++) {
309 ucstring_destroy(d->paths[i]);
312 de_arch_destroy_lctx(c, d->da);
313 de_free(c, d);
317 static int de_identify_crush(deark *c)
319 u8 buf[14];
321 de_read(buf, 0, 14);
322 if(!de_memcmp(buf, "CRUSH v", 7) &&
323 buf[10]==0x0a && buf[11]==0x1a && buf[12]==0x00)
325 return 100;
328 return 0;
331 void de_module_crush(deark *c, struct deark_module_info *mi)
333 mi->id = "crush";
334 mi->desc = "CRUSH archive";
335 mi->run_fn = de_run_crush;
336 mi->identify_fn = de_identify_crush;