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
;
26 int paths_segment_len_known
;
27 i64 paths_segment_pos
;
28 i64 paths_segment_len
;
29 de_ucstring
**paths
; // array[num_paths]
32 // Squash slashes, ensure not empty.
33 static void fixup_filename(de_ucstring
*s
)
37 for(i
=0; i
<s
->len
; i
++) {
43 if(ucstring_isempty(s
)) {
44 ucstring_append_char(s
, '_');
48 static int do_read_paths(deark
*c
, lctx
*d
)
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.
62 if(d
->num_paths
<= 0) {
63 d
->paths_segment_len
= 0;
64 d
->paths_segment_len_known
= 1;
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
);
77 pos
= d
->paths_segment_pos
;
78 for(i
=0; i
<d
->num_paths
; i
++) {
83 if(pos
>= c
->infile
->len
) goto done
;
84 ret
= dbuf_search_byte(c
->infile
, 0x00, pos
, c
->infile
->len
-pos
, &foundpos
);
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);
93 d
->paths_segment_len
= pos
- d
->paths_segment_pos
;
94 d
->paths_segment_len_known
= 1;
98 de_warn(c
, "Could not read path table");
100 de_dbg_indent_restore(c
, saved_indent_level
);
104 static void do_comment(deark
*c
, lctx
*d
)
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
);
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
));
129 de_dbg(c
, "archive comment: \"%s\"", ucstring_getpsz_d(s
));
130 de_dbg_indent(c
, -1);
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
)
158 struct de_arch_member_data
*md
= NULL
;
159 struct crush_member_data
*mdc
= NULL
;
160 de_ucstring
*descr
= NULL
;
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
);
172 de_dbg(c
, "dir entry at %"I64_FMT
, 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
199 if(d
->is_cri
&& md
->cmpr_len
!=0) {
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");
211 md
->cmpr_pos
= d
->da
->cmpr_data_curpos
;
212 d
->da
->cmpr_data_curpos
+= md
->cmpr_len
;
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
);
224 ucstring_destroy(descr
);
228 de_arch_destroy_md(c
, md
);
229 de_dbg_indent_restore(c
, saved_indent_level
);
233 static void do_read_dir_and_extract_files(deark
*c
, lctx
*d
)
236 int saved_indent_level
;
238 de_dbg_indent_save(c
, &saved_indent_level
);
240 if(d
->da
->num_members
<1) {
243 de_dbg(c
, "directory at %"I64_FMT
, d
->dir_segment_pos
);
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
)) {
255 de_dbg_indent_restore(c
, saved_indent_level
);
258 static int do_archive_header(deark
*c
, lctx
*d
)
263 de_dbg(c
, "archive header at %"I64_FMT
, pos1
);
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);
282 static void de_run_crush(deark
*c
, de_module_params
*mparams
)
286 d
= de_malloc(c
, sizeof(lctx
));
287 d
->da
= de_arch_create_lctx(c
);
288 d
->da
->userdata
= (void*)d
;
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
);
301 do_read_dir_and_extract_files(c
, d
);
308 for(i
=0; i
<d
->num_paths
; i
++) {
309 ucstring_destroy(d
->paths
[i
]);
312 de_arch_destroy_lctx(c
, d
->da
);
317 static int de_identify_crush(deark
*c
)
322 if(!de_memcmp(buf
, "CRUSH v", 7) &&
323 buf
[10]==0x0a && buf
[11]==0x1a && buf
[12]==0x00)
331 void de_module_crush(deark
*c
, struct deark_module_info
*mi
)
334 mi
->desc
= "CRUSH archive";
335 mi
->run_fn
= de_run_crush
;
336 mi
->identify_fn
= de_identify_crush
;