exe: Support PAK v1.6 self-extracting archives
[deark.git] / modules / fat.c
blob6d94bd0c3bf4e4f41d79dd942036a8c3c507dc04
1 // This file is part of Deark.
2 // Copyright (C) 2020 Jason Summers
3 // See the file COPYING for terms of use.
5 // FAT disk image
6 // LoadDskF/SaveDskF/SKF OS/2 disk image
8 #include <deark-private.h>
9 #include <deark-fmtutil.h>
10 DE_DECLARE_MODULE(de_module_fat);
11 DE_DECLARE_MODULE(de_module_loaddskf);
13 #define MAX_NESTING_LEVEL 16
15 struct member_data {
16 u8 fn_base[8];
17 u8 fn_ext[3];
18 u8 is_subdir;
19 u8 is_volume_label;
20 u8 is_special;
21 UI attribs;
22 UI ea_handle;
23 i64 fn_base_len, fn_ext_len;
24 i64 filesize;
25 i64 first_cluster;
26 de_ucstring *short_fn;
27 de_ucstring *long_fn;
28 struct de_timestamp mod_time;
31 struct dirctx {
32 u8 lfn_valid;
33 u8 first_seq_num;
34 u8 prev_seq_num;
35 u8 name_cksum;
36 i64 dir_entry_count;
37 i64 pending_lfn_bytesused;
38 #define LFN_CHARS_PER_FRAGMENT 13
39 #define LFN_MAX_FRAGMENTS 20
40 u8 pending_lfn[LFN_CHARS_PER_FRAGMENT*2*LFN_MAX_FRAGMENTS];
43 typedef struct localctx_struct {
44 de_encoding input_encoding;
45 u8 opt_check_root_dir;
46 u8 prescan_root_dir;
48 // TODO: Decide how to handle different variants of FAT.
49 #define FAT_SUBFMT_UNKNOWN 0
50 #define FAT_SUBFMT_PC 1
51 #define FAT_SUBFMT_ATARIST 2
52 int subfmt_req;
53 int subfmt;
54 #define FAT_PLATFORM_UNKNOWN 0
55 #define FAT_PLATFORM_PC 1
56 #define FAT_PLATFORM_ATARIST 2
57 int platform;
59 u8 num_fat_bits; // 12, 16, or 32. 0 if unknown.
60 u8 has_atarist_checksum;
61 i64 bytes_per_sector;
62 i64 sectors_per_cluster;
63 i64 bytes_per_cluster;
64 i64 num_sectors;
65 i64 data_region_sector;
66 i64 data_region_pos;
67 i64 num_data_region_clusters;
68 i64 num_rsvd_sectors;
69 i64 num_fats;
70 i64 num_sectors_per_fat;
71 i64 max_root_dir_entries16;
72 i64 root_dir_sector;
73 i64 num_cluster_identifiers;
74 struct de_strarray *curpath;
76 i64 num_fat_entries;
77 u32 *fat_nextcluster; // array[num_fat_entries]
78 u8 *cluster_used_flags; // array[num_fat_entries]
79 u8 *cluster_used_flags_saved; // array[num_fat_entries] (or NULL)
80 dbuf *ea_data; // NULL if not available
81 } lctx;
83 static void fat_save_cluster_use_flags(deark *c, lctx *d)
85 if(!d->cluster_used_flags) return;
86 if(!d->cluster_used_flags_saved) {
87 d->cluster_used_flags_saved = de_malloc(c, d->num_fat_entries);
89 de_memcpy(d->cluster_used_flags_saved, d->cluster_used_flags,
90 (size_t)d->num_fat_entries);
93 static void fat_restore_cluster_use_flags(deark *c, lctx *d)
95 if(!d->cluster_used_flags_saved || !d->cluster_used_flags) return;
96 de_memcpy(d->cluster_used_flags, d->cluster_used_flags_saved,
97 (size_t)d->num_fat_entries);
100 static i64 sectornum_to_offset(deark *c, lctx *d, i64 secnum)
102 return secnum * d->bytes_per_sector;
105 static int is_good_clusternum(lctx *d, i64 cnum)
107 if(cnum<2) return 0;
108 if(cnum >= d->num_cluster_identifiers) return 0;
109 return 1;
112 static i64 clusternum_to_offset(deark *c, lctx *d, i64 cnum)
114 return d->data_region_pos + (cnum-2) * d->bytes_per_cluster;
117 static void dbg_timestamp(deark *c, struct de_timestamp *ts, const char *name)
119 char timestamp_buf[64];
121 de_timestamp_to_string(ts, timestamp_buf, sizeof(timestamp_buf), 0);
122 de_dbg(c, "%s: %s", name, timestamp_buf);
125 static i64 get_unpadded_len(const u8 *s, i64 len1)
127 i64 i;
128 i64 len = len1;
130 // Stop at NUL, I guess.
131 for(i=0; i<len1; i++) {
132 if(s[i]==0x00) {
133 len = i;
134 break;
138 for(i=len; i>0; i--) {
139 if(s[i-1]!=' ') {
140 return i;
143 return 0;
146 static int extract_file_lowlevel(deark *c, lctx *d, struct member_data *md, dbuf *outf)
148 int retval = 0;
149 i64 cur_cluster;
150 i64 nbytes_remaining;
152 cur_cluster = md->first_cluster;
153 if(md->is_subdir) {
154 nbytes_remaining = 0;
156 else {
157 nbytes_remaining = md->filesize;
160 while(1) {
161 i64 dpos;
162 i64 nbytes_to_copy;
164 if(nbytes_remaining <= 0) break;
165 if(!is_good_clusternum(d, cur_cluster)) break;
166 if(d->cluster_used_flags[cur_cluster]) break;
167 d->cluster_used_flags[cur_cluster] = 1;
168 if(c->debug_level>=3) de_dbg3(c, "cluster: %d", (int)cur_cluster);
169 dpos = clusternum_to_offset(c, d, cur_cluster);
170 nbytes_to_copy = de_min_int(d->bytes_per_cluster, nbytes_remaining);
171 dbuf_copy(c->infile, dpos, nbytes_to_copy, outf);
172 nbytes_remaining -= nbytes_to_copy;
173 cur_cluster = (i64)d->fat_nextcluster[cur_cluster];
176 if(nbytes_remaining>0) {
177 goto done;
180 retval = 1;
181 done:
182 return retval;
185 static void do_extract_file(deark *c, lctx *d, struct member_data *md)
187 dbuf *outf = NULL;
188 de_finfo *fi = NULL;
189 de_ucstring *fullfn = NULL;
191 if(!md->is_subdir) {
192 if(md->filesize > d->num_data_region_clusters * d->bytes_per_cluster) {
193 de_err(c, "%s: Bad file size", ucstring_getpsz_d(md->short_fn));
194 goto done;
198 fi = de_finfo_create(c);
199 fullfn = ucstring_create(c);
200 de_strarray_make_path(d->curpath, fullfn, DE_MPFLAG_NOTRAILINGSLASH);
201 de_finfo_set_name_from_ucstring(c, fi, fullfn, DE_SNFLAG_FULLPATH);
202 fi->original_filename_flag = 1;
203 if(md->is_subdir) {
204 fi->is_directory = 1;
206 else if(md->is_volume_label) {
207 fi->is_volume_label = 1;
209 if(md->mod_time.is_valid) {
210 fi->timestamp[DE_TIMESTAMPIDX_MODIFY] = md->mod_time;
213 outf = dbuf_create_output_file(c, NULL, fi, 0);
215 if(!extract_file_lowlevel(c, d, md, outf)) {
216 de_err(c, "%s: File extraction failed", ucstring_getpsz_d(md->short_fn));
217 goto done;
220 done:
221 dbuf_close(outf);
222 ucstring_destroy(fullfn);
223 de_finfo_destroy(c, fi);
226 static void do_subdir(deark *c, lctx *d, struct member_data *md, int nesting_level);
228 static void do_vfat_entry(deark *c, lctx *d, struct dirctx *dctx, i64 pos1, u8 seq_num_raw)
230 u8 seq_num;
231 u8 fn_cksum;
232 int is_first_entry = 0;
233 i64 startpos_in_lfn;
235 if(seq_num_raw==0xe5) {
236 de_dbg(c, "[deleted VFAT entry]");
237 dctx->lfn_valid = 0;
238 goto done;
241 de_dbg(c, "seq number: 0x%02x", (UI)seq_num_raw);
243 seq_num = seq_num_raw & 0xbf;
245 if(seq_num<1 || seq_num>LFN_MAX_FRAGMENTS) {
246 de_warn(c, "Bad VFAT sequence number (%u)", (UI)seq_num);
247 dctx->lfn_valid = 0;
248 goto done;
251 if(seq_num_raw & 0x40) {
252 is_first_entry = 1;
253 de_zeromem(dctx->pending_lfn, sizeof(dctx->pending_lfn));
254 dctx->first_seq_num = seq_num;
255 dctx->lfn_valid = 1;
257 else {
258 if(!dctx->lfn_valid || (seq_num+1 != dctx->prev_seq_num)) {
259 de_dbg(c, "[stray VFAT entry]");
260 dctx->lfn_valid = 0;
261 goto done;
264 dctx->prev_seq_num = seq_num;
266 startpos_in_lfn = LFN_CHARS_PER_FRAGMENT*2*((i64)seq_num-1);
268 de_read(&dctx->pending_lfn[startpos_in_lfn+ 0], pos1+ 1, 10); // 5 chars
269 fn_cksum = de_getbyte(pos1+13);
270 de_read(&dctx->pending_lfn[startpos_in_lfn+10], pos1+14, 12); // 6 more chars
271 de_read(&dctx->pending_lfn[startpos_in_lfn+22], pos1+28, 4); // 2 more chars
272 de_dbg(c, "filename checksum (reported): 0x%02x", (UI)fn_cksum);
273 if(!is_first_entry) {
274 if(fn_cksum != dctx->name_cksum) {
275 de_dbg(c, "[inconsistent VFAT checksums]");
276 dctx->lfn_valid = 0;
279 dctx->name_cksum = fn_cksum;
281 done:
285 static void vfat_cksum_update(const u8 *buf, size_t buflen, u8 *cksum)
287 size_t i;
289 for(i=0; i<buflen; i++) {
290 *cksum = (((*cksum) & 1) << 7) + ((*cksum) >> 1) + buf[i];
294 // If the long file name seems valid, sets it in md->long_fn for later use.
295 static void handle_vfat_lfn(deark *c, lctx *d, struct dirctx *dctx,
296 struct member_data *md)
298 u8 cksum_calc = 0;
299 i64 max_len_in_ucs2_chars;
300 i64 len_in_ucs2_chars = 0;
301 i64 i;
303 if(!dctx->lfn_valid) goto done;
304 if(dctx->prev_seq_num != 1) goto done;
305 if(md->long_fn) goto done;
307 vfat_cksum_update(md->fn_base, 8, &cksum_calc);
308 vfat_cksum_update(md->fn_ext, 3, &cksum_calc);
309 de_dbg(c, "filename checksum (calculated): 0x%02x", (UI)cksum_calc);
310 if(cksum_calc != dctx->name_cksum) goto done;
312 max_len_in_ucs2_chars = LFN_CHARS_PER_FRAGMENT * (i64)dctx->first_seq_num;
313 if(max_len_in_ucs2_chars > (i64)(sizeof(dctx->pending_lfn)/2)) goto done;
314 for(i=0; i<max_len_in_ucs2_chars; i++) {
315 if(dctx->pending_lfn[i*2]==0x00 && dctx->pending_lfn[i*2+1]==0x00) break;
316 if(dctx->pending_lfn[i*2]==0xff && dctx->pending_lfn[i*2+1]==0xff) break;
317 len_in_ucs2_chars++;
320 md->long_fn = ucstring_create(c);
321 ucstring_append_bytes(md->long_fn, dctx->pending_lfn, len_in_ucs2_chars*2,
322 0, DE_ENCODING_UTF16LE);
323 de_dbg(c, "long filename: \"%s\"", ucstring_getpsz_d(md->long_fn));
325 done:
329 // Reads from md->fn_base* and md->fn_ext*, writes to md->short_fn
330 static void decode_short_filename(deark *c, lctx *d, struct member_data *md)
332 if(md->fn_base_len>0) {
333 ucstring_append_bytes(md->short_fn, md->fn_base, md->fn_base_len, 0, d->input_encoding);
335 else {
336 ucstring_append_char(md->short_fn, '_');
338 if(md->fn_ext_len>0) {
339 ucstring_append_char(md->short_fn, '.');
340 ucstring_append_bytes(md->short_fn, md->fn_ext, md->fn_ext_len, 0, d->input_encoding);
344 static void decode_volume_label_name(deark *c, lctx *d, struct member_data *md)
346 if(md->fn_ext_len>0) {
347 ucstring_append_bytes(md->short_fn, md->fn_base, 8, 0, d->input_encoding);
348 ucstring_append_bytes(md->short_fn, md->fn_ext, md->fn_ext_len, 0, d->input_encoding);
350 else {
351 ucstring_append_bytes(md->short_fn, md->fn_base, md->fn_base_len, 0, d->input_encoding);
355 // md is that of the file whose EA data is being requested.
356 // Uses md->ea_handle.
357 static void do_fat_eadata_item(deark *c, lctx *d, struct member_data *md)
359 de_module_params *mparams = NULL;
361 if(!d->ea_data) goto done;
362 if(md->ea_handle==0) goto done;
363 mparams = de_malloc(c, sizeof(de_module_params));
364 mparams->in_params.input_encoding = d->input_encoding;
365 mparams->in_params.flags = 0x1;
366 mparams->in_params.uint1 = (u32)md->ea_handle;
367 de_dbg(c, "reading OS/2 extended attributes");
368 de_dbg_indent(c, 1);
369 // TODO: Better filenames for icons that may be extracted.
370 // The ea_data module will use the filename contained in the EA data stream,
371 // but it would be better to use the "fullfn" that will be constructed in
372 // do_extract_file(). Some refactoring will be needed.
373 de_run_module_by_id_on_slice(c, "ea_data", mparams, d->ea_data, 0, d->ea_data->len);
374 de_dbg_indent(c, -1);
376 done:
377 de_free(c, mparams);
380 // md is that of the "EA DATA" file itself.
381 static void do_fat_eadata(deark *c, lctx *d, struct member_data *md)
383 int ret;
385 if(d->ea_data) goto done;
386 d->ea_data = dbuf_create_membuf(c, 0, 0);
387 dbuf_set_length_limit(d->ea_data, c->infile->len);
388 ret = extract_file_lowlevel(c, d, md, d->ea_data);
389 if(!ret) {
390 dbuf_close(d->ea_data);
391 d->ea_data = NULL;
392 goto done;
394 de_dbg(c, "[read EA data, len=%"I64_FMT"]", d->ea_data->len);
395 done:
399 // Returns 0 if this is the end-of-directory marker.
400 static int do_dir_entry(deark *c, lctx *d, struct dirctx *dctx,
401 i64 pos1, int nesting_level, int scanmode)
403 u8 firstbyte;
404 i64 ddate, dtime;
405 int retval = 0;
406 int is_deleted = 0;
407 int need_curpath_pop = 0;
408 de_ucstring *descr = NULL;
409 struct member_data *md = NULL;
411 md = de_malloc(c, sizeof(struct member_data));
413 de_dbg(c, "dir entry at %"I64_FMT, pos1);
414 de_dbg_indent(c, 1);
416 de_read(md->fn_base, pos1+0, 8);
417 de_read(md->fn_ext, pos1+8, 3);
418 firstbyte = md->fn_base[0];
420 if(firstbyte==0x00) {
421 de_dbg(c, "[end of dir marker]");
422 goto done;
424 retval = 1;
426 md->attribs = (UI)de_getbyte(pos1+11);
427 descr = ucstring_create(c);
428 de_describe_dos_attribs(c, md->attribs, descr, 0x1);
429 de_dbg(c, "attribs: 0x%02x (%s)", md->attribs, ucstring_getpsz_d(descr));
430 if((md->attribs & 0x3f)==0x0f) {
431 do_vfat_entry(c, d, dctx, pos1, firstbyte);
432 goto done;
435 if((md->attribs & 0x18) == 0x00) {
436 ; // Normal file
438 else if((md->attribs & 0x18) == 0x08) {
439 md->is_volume_label = 1;
441 else if((md->attribs & 0x18) == 0x10) {
442 md->is_subdir = 1;
444 else {
445 de_warn(c, "Invalid directory entry");
446 md->is_special = 1;
447 dctx->lfn_valid = 0;
448 goto done;
451 if(dctx->lfn_valid) {
452 handle_vfat_lfn(c, d, dctx, md);
453 dctx->lfn_valid = 0;
456 if(firstbyte==0xe5) {
457 de_dbg(c, "[deleted]");
458 is_deleted = 1;
459 md->fn_base[0] = '?';
461 else if(firstbyte==0x05) {
462 md->fn_base[0] = 0xe5;
465 md->fn_base_len = get_unpadded_len(md->fn_base, 8);
466 md->fn_ext_len = get_unpadded_len(md->fn_ext, 3);
468 if(md->is_subdir && md->fn_base_len>=1 && md->fn_base[0]=='.') {
469 // special "." and ".." dirs
470 md->is_special = 1;
473 md->short_fn = ucstring_create(c);
474 if(md->is_volume_label) {
475 decode_volume_label_name(c, d, md);
477 else {
478 decode_short_filename(c, d, md);
481 de_dbg(c, "filename: \"%s\"", ucstring_getpsz_d(md->short_fn));
483 if(ucstring_isnonempty(md->long_fn)) {
484 de_strarray_push(d->curpath, md->long_fn);
486 else {
487 de_strarray_push(d->curpath, md->short_fn);
489 need_curpath_pop = 1;
491 if(!scanmode && d->num_fat_bits<32) {
492 md->ea_handle = (UI)de_getu16le(pos1+20);
493 if(md->ea_handle) {
494 de_dbg(c, "EA handle (if OS/2): %u", md->ea_handle);
498 dtime = de_getu16le(pos1+22);
499 ddate = de_getu16le(pos1+24);
500 de_dos_datetime_to_timestamp(&md->mod_time, ddate, dtime);
501 dbg_timestamp(c, &md->mod_time, "mod time");
503 // TODO: This is wrong for FAT32.
504 md->first_cluster = de_getu16le(pos1+26);
505 de_dbg(c, "first cluster: %"I64_FMT, md->first_cluster);
507 md->filesize = de_getu32le(pos1+28);
508 de_dbg(c, "file size: %"I64_FMT, md->filesize);
509 if(md->is_volume_label) md->filesize = 0;
511 // (Done reading dir entry)
513 if(is_deleted) goto done;
515 if(scanmode) {
516 if(!md->is_subdir && !md->is_special && (md->attribs&0x04) &&
517 md->fn_base_len==7 && md->fn_ext_len==3 &&
518 !de_memcmp(md->fn_base, "EA DATA", 7) &&
519 !de_memcmp(md->fn_ext, " SF", 3) )
521 do_fat_eadata(c, d, md);
522 goto done;
524 de_dbg2(c, "[scan mode - not extracting]");
525 goto done;
528 if(md->ea_handle!=0 && d->ea_data) {
529 do_fat_eadata_item(c, d, md);
532 if(!md->is_subdir && !md->is_special) {
533 do_extract_file(c, d, md);
535 else if(md->is_subdir && !md->is_special) {
536 do_extract_file(c, d, md);
537 do_subdir(c, d, md, nesting_level+1);
540 done:
541 ucstring_destroy(descr);
542 if(md) {
543 ucstring_destroy(md->short_fn);
544 ucstring_destroy(md->long_fn);
546 if(need_curpath_pop) {
547 de_strarray_pop(d->curpath);
549 de_dbg_indent(c, -1);
550 return retval;
553 // Process a contiguous block of directory entries
554 // Returns 0 if an end-of-dir marker was found.
555 static int do_dir_entries(deark *c, lctx *d, struct dirctx *dctx,
556 i64 pos1, i64 len, int nesting_level, int scanmode)
558 i64 num_entries;
559 i64 i;
560 int retval = 0;
562 num_entries = len/32;
563 de_dbg(c, "num entries: %"I64_FMT, num_entries);
565 for(i=0; i<num_entries; i++) {
566 if(!do_dir_entry(c, d, dctx, pos1+32*i, nesting_level, scanmode)) {
567 goto done;
569 dctx->dir_entry_count++;
572 retval = 1;
573 done:
574 return retval;
577 static void destroy_dirctx(deark *c, struct dirctx *dctx)
579 if(!dctx) return;
580 de_free(c, dctx);
583 static void do_subdir(deark *c, lctx *d, struct member_data *md, int nesting_level)
585 int saved_indent_level;
586 i64 cur_cluster_num;
587 i64 cur_cluster_pos;
588 struct dirctx *dctx = NULL;
590 de_dbg_indent_save(c, &saved_indent_level);
592 if(nesting_level >= MAX_NESTING_LEVEL) {
593 de_err(c, "Directories nested too deeply");
594 goto done;
597 dctx = de_malloc(c, sizeof(struct dirctx));
599 cur_cluster_num = md->first_cluster;
600 if(!is_good_clusternum(d, cur_cluster_num)) {
601 de_err(c, "Bad subdirectory entry");
602 goto done;
604 cur_cluster_pos = clusternum_to_offset(c, d, cur_cluster_num);
605 de_dbg(c, "subdir starting at %"I64_FMT, cur_cluster_pos);
606 de_dbg_indent(c, 1);
608 while(1) {
609 if(!is_good_clusternum(d, cur_cluster_num)) {
610 break;
612 cur_cluster_pos = clusternum_to_offset(c, d, cur_cluster_num);
613 de_dbg(c, "[subdir cluster %"I64_FMT" at %"I64_FMT"]", cur_cluster_num, cur_cluster_pos);
615 if(d->cluster_used_flags[cur_cluster_num]) {
616 goto done;
618 d->cluster_used_flags[cur_cluster_num] = 1;
620 if(!do_dir_entries(c, d, dctx, cur_cluster_pos, d->bytes_per_cluster, nesting_level, 0)) {
621 break;
624 cur_cluster_num = d->fat_nextcluster[cur_cluster_num];
627 done:
628 destroy_dirctx(c, dctx);
629 de_dbg_indent_restore(c, saved_indent_level);
632 static void do_root_dir(deark *c, lctx *d)
634 i64 pos1;
635 struct dirctx *dctx = NULL;
637 dctx = de_malloc(c, sizeof(struct dirctx));
638 pos1 = sectornum_to_offset(c, d, d->root_dir_sector);
639 de_dbg(c, "dir at %"I64_FMT, pos1);
640 de_dbg_indent(c, 1);
641 if(pos1<d->bytes_per_sector) goto done;
642 if(d->prescan_root_dir) {
643 de_dbg(c, "[scanning root dir]");
644 // This feature causes us to intentionally read some clusters more than once,
645 // so we have to work around our protections against doing that.
646 fat_save_cluster_use_flags(c, d);
647 de_dbg_indent(c, 1);
648 (void)do_dir_entries(c, d, dctx, pos1, d->max_root_dir_entries16 * 32, 0, 1);
649 de_dbg_indent(c, -1);
650 fat_restore_cluster_use_flags(c, d);
651 de_dbg(c, "[done scanning root dir]");
653 (void)do_dir_entries(c, d, dctx, pos1, d->max_root_dir_entries16 * 32, 0, 0);
654 done:
655 destroy_dirctx(c, dctx);
656 de_dbg_indent(c, -1);
659 static int root_dir_seems_valid(deark *c, lctx *d)
661 i64 pos1;
662 i64 max_entries_to_check;
663 i64 i;
664 i64 entrycount = 0;
665 i64 errcount = 0;
667 if(d->num_fat_bits==32) return 1;
669 if(d->max_root_dir_entries16<=0) return 0;
670 pos1 = sectornum_to_offset(c, d, d->root_dir_sector);
671 if(pos1 + d->max_root_dir_entries16 * 32 > c->infile->len) {
672 return 0;
675 max_entries_to_check = de_max_int(d->max_root_dir_entries16, 10);
676 for(i=0; i<max_entries_to_check; i++) {
677 i64 entrypos;
678 u8 firstbyte;
679 u8 attribs;
681 entrypos = pos1 + 32*i;
682 firstbyte = de_getbyte(entrypos);
683 if(firstbyte==0x00) break;
684 if(firstbyte==0xe5) continue; // Don't validate deleted entries
685 entrycount++;
686 attribs = de_getbyte(entrypos+11);
687 if(attribs & 0xc0) {
688 errcount++;
690 else if((attribs & 0x3f) == 0x0f) {
691 ; // LFN; OK
693 else if((attribs & 0x18)==0x18) {
694 errcount++; // dir + vol.label not valid
697 // TODO: It's really lame to only validate the attribs field, when there's
698 // so much more we could be doing. But it's a hard problem. We don't want
699 // to be too sensitive to minor errors.
702 if(errcount>1 || (errcount==1 && entrycount<=1)) {
703 return 0;
705 return 1;
708 static void do_atarist_boot_checksum(deark *c, lctx *d, i64 pos1)
710 UI ck;
712 ck = de_calccrc_oneshot(c->infile, pos1, 512, DE_CRCOBJ_SUM_U16BE);
713 ck &= 0xffff;
714 de_dbg(c, "Atari ST checksum: 0x%04x", ck);
715 if(ck==0x1234) {
716 d->has_atarist_checksum = 1;
720 static void do_oem_name(deark *c, lctx *d, i64 pos, i64 len)
722 struct de_stringreaderdata *srd;
723 i64 i;
725 srd = dbuf_read_string(c->infile, pos, len, len, 0, DE_ENCODING_ASCII);
727 // Require printable ASCII.
728 for(i=0; i<len; i++) {
729 if(srd->sz[i]<32 || srd->sz[i]>126) {
730 goto done;
734 de_dbg(c, "OEM name: \"%s\"", ucstring_getpsz_d(srd->str));
736 done:
737 de_destroy_stringreaderdata(c, srd);
740 static int do_boot_sector(deark *c, lctx *d, i64 pos1)
742 i64 pos;
743 i64 num_data_region_sectors;
744 i64 num_root_dir_sectors;
745 i64 num_sectors_per_fat16;
746 i64 num_sectors_per_fat32 = 0;
747 i64 num_sectors16;
748 i64 num_sectors32 = 0;
749 i64 num_sectors_per_track;
750 i64 num_heads;
751 i64 num_sectors_per_cylinder;
752 i64 num_cylinders = 0;
753 i64 jmpinstrlen;
754 u8 b;
755 u8 cksum_sig[2];
756 int retval = 0;
757 char tmpbuf[16];
759 de_dbg(c, "boot sector at %"I64_FMT, pos1);
760 de_dbg_indent(c, 1);
762 // BIOS parameter block
763 jmpinstrlen = (d->subfmt==FAT_SUBFMT_ATARIST)?2:3;
764 de_dbg(c, "jump instr: %s",
765 de_render_hexbytes_from_dbuf(c->infile, pos1, jmpinstrlen, tmpbuf, sizeof(tmpbuf)));
767 if(d->subfmt==FAT_SUBFMT_ATARIST) {
768 do_oem_name(c, d, pos1+2, 6);
769 de_dbg(c, "serial num: %s",
770 de_render_hexbytes_from_dbuf(c->infile, pos1+8, 3, tmpbuf, sizeof(tmpbuf)));
772 else {
773 do_oem_name(c, d, pos1+3, 8);
776 pos = pos1+11;
777 d->bytes_per_sector = de_getu16le_p(&pos);
778 de_dbg(c, "bytes per sector: %d", (int)d->bytes_per_sector);
779 d->sectors_per_cluster = (i64)de_getbyte_p(&pos);
780 de_dbg(c, "sectors per cluster: %d", (int)d->sectors_per_cluster);
781 d->num_rsvd_sectors = de_getu16le_p(&pos);
783 de_dbg(c, "reserved sectors: %d", (int)d->num_rsvd_sectors);
784 if(d->num_rsvd_sectors==0) {
785 // This happens on some Atari ST disks. Don't know why.
786 d->num_rsvd_sectors = 1;
789 d->num_fats = (i64)de_getbyte_p(&pos);
790 de_dbg(c, "number of FATs: %d", (int)d->num_fats);
792 // This is expected to be 0 for FAT32.
793 d->max_root_dir_entries16 = de_getu16le_p(&pos);
794 de_dbg(c, "max number of root dir entries (if FAT12/16): %d", (int)d->max_root_dir_entries16);
796 num_sectors16 = de_getu16le_p(&pos);
797 de_dbg(c, "number of sectors (old 16-bit field): %d", (int)num_sectors16);
798 b = de_getbyte_p(&pos);
799 de_dbg(c, "media descriptor: 0x%02x", (UI)b);
800 num_sectors_per_fat16 = de_getu16le_p(&pos);
801 de_dbg(c, "sectors per FAT (if FAT12/16): %d", (int)num_sectors_per_fat16);
803 num_sectors_per_track = de_getu16le_p(&pos);
804 de_dbg(c, "sectors per track: %d", (int)num_sectors_per_track);
805 num_heads = de_getu16le_p(&pos);
806 de_dbg(c, "number of heads: %d", (int)num_heads);
808 pos = pos1+0x1fe;
809 de_read(cksum_sig, pos, 2);
810 de_dbg(c, "boot sector signature: %s",
811 de_render_hexbytes_from_mem(cksum_sig, 2, tmpbuf, sizeof(tmpbuf)));
813 do_atarist_boot_checksum(c, d, pos1);
814 if(d->has_atarist_checksum) {
815 d->platform = FAT_PLATFORM_ATARIST;
816 de_dbg(c, "[This is probably a bootable Atari ST disk.]");
818 else if(cksum_sig[0]==0x55 && cksum_sig[1]==0xaa) {
819 d->platform = FAT_PLATFORM_PC;
820 de_dbg(c, "[Disk has PC-compatible boot code.]");
823 if(num_sectors16==0) {
824 num_sectors32 = de_getu32le(pos1+32);
825 de_dbg(c, "num sectors (new 32-bit field): %"I64_FMT, num_sectors32);
828 if(num_sectors_per_fat16==0) {
829 num_sectors_per_fat32 = de_getu32le(pos1+36);
830 de_dbg(c, "sectors per FAT (if FAT32): %u", (UI)num_sectors_per_fat32);
833 if(num_sectors_per_fat16==0) {
834 d->num_sectors_per_fat = num_sectors_per_fat32;
836 else {
837 d->num_sectors_per_fat = num_sectors_per_fat16;
840 if(num_sectors16==0) {
841 d->num_sectors = num_sectors32;
843 else {
844 d->num_sectors = num_sectors16;
847 num_sectors_per_cylinder = num_heads * num_sectors_per_track;
848 if(num_sectors_per_cylinder > 0) {
849 num_cylinders = de_pad_to_n(d->num_sectors, num_sectors_per_cylinder) / num_sectors_per_cylinder;
851 de_dbg(c, "number of cylinders (calculated): %"I64_FMT, num_cylinders);
853 if(d->sectors_per_cluster<1) goto done;
854 if(d->bytes_per_sector<32) goto done;
855 d->bytes_per_cluster = d->bytes_per_sector * d->sectors_per_cluster;
856 d->root_dir_sector = d->num_rsvd_sectors + d->num_sectors_per_fat * d->num_fats;
857 de_dbg(c, "root dir pos (calculated): %"I64_FMT" (sector %"I64_FMT")",
858 sectornum_to_offset(c, d, d->root_dir_sector), d->root_dir_sector);
860 // num_root_dir_sectors is expected to be 0 for FAT32.
861 num_root_dir_sectors = (d->max_root_dir_entries16*32 + d->bytes_per_sector - 1)/d->bytes_per_sector;
863 num_data_region_sectors = d->num_sectors - (d->root_dir_sector + num_root_dir_sectors);
864 if(num_data_region_sectors<0) goto done;
865 d->num_data_region_clusters = num_data_region_sectors / d->sectors_per_cluster;
866 de_dbg(c, "number of clusters (calculated): %"I64_FMT, d->num_data_region_clusters);
868 d->data_region_sector = d->root_dir_sector + num_root_dir_sectors;
869 d->data_region_pos = d->data_region_sector * d->bytes_per_sector;
870 de_dbg(c, "data region pos (calculated): %"I64_FMT" (sector %"I64_FMT")", d->data_region_pos,
871 d->data_region_sector);
873 // (The first cluster is numbered "2".)
874 d->num_cluster_identifiers = d->num_data_region_clusters + 2;
876 if(d->num_data_region_clusters < 4085) {
877 d->num_fat_bits = 12;
879 else if(d->num_data_region_clusters < 65525) {
880 d->num_fat_bits = 16;
882 else {
883 d->num_fat_bits = 32;
886 de_dbg(c, "bits per cluster id (calculated): %u", (UI)d->num_fat_bits);
888 retval = 1;
890 done:
891 if(!retval) {
892 de_err(c, "Invalid or unsupported boot sector");
894 de_dbg_indent(c, -1);
895 return retval;
898 static int do_read_fat(deark *c, lctx *d)
900 i64 pos1;
901 i64 pos;
902 i64 fat_idx_to_read = 0;
903 int retval = 0;
904 i64 i;
906 pos1 = sectornum_to_offset(c, d, d->num_rsvd_sectors + fat_idx_to_read*d->num_sectors_per_fat);
907 de_dbg(c, "FAT#%d at %"I64_FMT, (int)fat_idx_to_read, pos1);
908 de_dbg_indent(c, 1);
910 if(d->num_cluster_identifiers > (i64)(DE_MAX_SANE_OBJECT_SIZE/sizeof(u32))) goto done;
911 d->num_fat_entries = d->num_cluster_identifiers;
912 d->fat_nextcluster = de_mallocarray(c, d->num_fat_entries, sizeof(u32));
913 d->cluster_used_flags = de_malloc(c, d->num_fat_entries);
915 pos = pos1;
916 if(d->num_fat_bits==12) {
917 for(i=0; i<d->num_fat_entries+1; i+=2) {
918 UI val;
920 val = (UI)dbuf_getint_ext(c->infile, pos, 3, 1, 0);
921 pos += 3;
922 if(i < d->num_fat_entries) {
923 d->fat_nextcluster[i] = (u32)(val & 0xfff);
925 if(i+1 < d->num_fat_entries) {
926 d->fat_nextcluster[i+1] = (u32)(val >> 12);
930 else if(d->num_fat_bits==16) {
931 for(i=0; i<d->num_fat_entries; i++) {
932 d->fat_nextcluster[i] = (u32)de_getu16le_p(&pos);
935 else {
936 de_err(c, "This type of FAT is not supported");
937 goto done;
940 if(c->debug_level>=3) {
941 for(i=0; i<d->num_fat_entries; i++) {
942 de_dbg3(c, "fat[%"I64_FMT"]: %"I64_FMT, i, (i64)d->fat_nextcluster[i]);
946 retval = 1;
947 done:
948 de_dbg_indent(c, -1);
949 return retval;
952 static void de_run_fat(deark *c, de_module_params *mparams)
954 lctx *d = NULL;
955 const char *s;
956 int got_root_dir = 0;
957 de_encoding default_encoding = DE_ENCODING_CP437_G;
959 if(mparams) {
960 // out_params.flags:
961 // 0x1 = No valid FAT directory structure found
962 mparams->out_params.flags = 0;
965 d = de_malloc(c, sizeof(lctx));
967 d->prescan_root_dir = (u8)de_get_ext_option_bool(c, "fat:scanroot", 1);
968 d->opt_check_root_dir = (u8)de_get_ext_option_bool(c, "fat:checkroot", 1);
969 s = de_get_ext_option(c, "fat:subfmt");
970 if(s) {
971 if(!de_strcmp(s, "pc")) {
972 d->subfmt_req = FAT_SUBFMT_PC;
974 else if(!de_strcmp(s, "atarist")) {
975 d->subfmt_req = FAT_SUBFMT_ATARIST;
978 d->subfmt = d->subfmt_req;
979 if(d->subfmt==FAT_SUBFMT_ATARIST) {
980 default_encoding = DE_ENCODING_ATARIST;
983 d->input_encoding = de_get_input_encoding(c, mparams, default_encoding);
985 // TODO: Detect MBR?
986 if(!do_boot_sector(c, d, 0)) goto done;
987 if(d->num_fat_bits==0) goto done;
989 switch(d->platform) {
990 case FAT_PLATFORM_PC:
991 de_declare_fmtf(c, "FAT%d - PC", d->num_fat_bits);
992 break;
993 case FAT_PLATFORM_ATARIST:
994 de_declare_fmtf(c, "FAT%d - Atari ST", d->num_fat_bits);
995 break;
996 default:
997 de_declare_fmtf(c, "FAT%d - Unknown platform", d->num_fat_bits);
998 break;
1001 if(!do_read_fat(c, d)) goto done;
1003 if(d->opt_check_root_dir) {
1004 if(!root_dir_seems_valid(c, d)) {
1005 de_warn(c, "This file does not appear to contain a valid FAT "
1006 "directory structure. (\"-opt fat:checkroot=0\" to try anyway)");
1007 goto done;
1011 d->curpath = de_strarray_create(c, MAX_NESTING_LEVEL+10);
1012 got_root_dir = 1;
1013 do_root_dir(c, d);
1015 done:
1016 if(!got_root_dir) {
1017 // Inform the parent module that we failed to do anything.
1018 if(mparams) {
1019 mparams->out_params.flags |= 0x1;
1023 if(d) {
1024 de_free(c, d->fat_nextcluster);
1025 de_free(c, d->cluster_used_flags);
1026 de_free(c, d->cluster_used_flags_saved);
1027 if(d->curpath) de_strarray_destroy(d->curpath);
1028 if(d->ea_data) dbuf_close(d->ea_data);
1029 de_free(c, d);
1033 static int de_identify_fat(deark *c)
1035 i64 bytes_per_sector;
1036 i64 max_root_dir_entries;
1037 i64 num_rsvd_sectors;
1038 int confidence = 0;
1039 int has_pc_sig;
1040 int has_ext;
1041 u8 sectors_per_cluster;
1042 u8 num_fats;
1043 u8 media_descr;
1044 u8 b[32];
1046 // TODO: This needs a lot of work.
1047 // It's good enough for most FAT12 floppy disk images.
1049 de_read(b, 0, sizeof(b));
1050 bytes_per_sector = de_getu16le_direct(&b[11]);
1051 sectors_per_cluster = b[13];
1052 num_rsvd_sectors = de_getu16le_direct(&b[14]);
1053 num_fats = b[16];
1054 max_root_dir_entries = de_getu16le_direct(&b[17]);
1055 media_descr = b[21];
1057 if(bytes_per_sector!=512) return 0;
1058 switch(sectors_per_cluster) {
1059 case 1: case 2: case 4: case 8:
1060 case 16: case 32: case 64: case 128:
1061 break;
1062 default:
1063 return 0;
1065 if(num_fats!=1 && num_fats!=2) return 0;
1066 if(media_descr<0xe5 && media_descr!=0) return 0; // Media descriptor
1068 confidence = 1;
1069 if(b[0]==0xeb && b[2]==0x90) confidence += 2;
1070 else if(b[0]==0xe9) confidence += 1;
1071 else if(b[0]==0x60) confidence += 1;
1072 has_pc_sig = (de_getu16be(510)==0x55aa);
1073 if(has_pc_sig) confidence += 2;
1074 if(num_fats==2) confidence += 1;
1075 if(media_descr>=0xe5) confidence += 1;
1076 if(num_rsvd_sectors==1) confidence += 1;
1077 if(max_root_dir_entries==112 || max_root_dir_entries==224) confidence += 2;
1079 has_ext = de_input_file_has_ext(c, "ima") ||
1080 de_input_file_has_ext(c, "img") ||
1081 de_input_file_has_ext(c, "st");
1083 if(confidence>=6) return (has_ext?100:80);
1084 else if(confidence>=4) return (has_ext?60:9);
1085 else return 0;
1088 static void de_help_fat(deark *c)
1090 de_msg(c, "-opt fat:checkroot=0 : Read the directory structure, even if it "
1091 "seems invalid");
1092 de_msg(c, "-opt fat:scanroot=0 : Do not scan the root directory to look for "
1093 "special files");
1096 void de_module_fat(deark *c, struct deark_module_info *mi)
1098 mi->id = "fat";
1099 mi->desc = "FAT disk image";
1100 mi->run_fn = de_run_fat;
1101 mi->identify_fn = de_identify_fat;
1102 mi->help_fn = de_help_fat;
1105 ///////////////////////// LoadDskF/SaveDskF format (OS/2-centric)
1107 struct skf_ctx {
1108 int to_raw;
1109 int new_fmt;
1110 int is_compressed;
1111 u32 checksum_reported;
1112 u32 checksum_calc;
1113 i64 hdr_size;
1114 i64 expected_dcmpr_size; // 0 if unknown
1115 i64 padded_size; // 0 if unknown
1116 struct de_crcobj *crco;
1119 // Get, report, and compare calculated checksum
1120 static void loaddskf_finish_checksum(deark *c, struct skf_ctx *d)
1122 d->checksum_calc = de_crcobj_getval(d->crco);
1123 de_dbg(c, "checksum (calculated): 0x%08x", (UI)d->checksum_calc);
1124 if(d->checksum_calc != d->checksum_reported) {
1125 de_warn(c, "Checksum mismatch (reported 0x%08x, calculated 0x%08x). "
1126 "Something may have gone wrong.", (UI)d->checksum_reported,
1127 (UI)d->checksum_calc);
1131 static void loaddskf_calc_checksum_noncmpr(deark *c, struct skf_ctx *d)
1133 i64 nbytes_to_checksum;
1135 nbytes_to_checksum = c->infile->len - d->hdr_size;
1136 if(d->expected_dcmpr_size && d->expected_dcmpr_size < nbytes_to_checksum) {
1137 nbytes_to_checksum = d->expected_dcmpr_size;
1140 de_crcobj_addslice(d->crco, c->infile, d->hdr_size, nbytes_to_checksum);
1143 static void loaddskf_pad_ima_file(deark *c, struct skf_ctx *d, dbuf *outf)
1145 i64 num_padding_bytes;
1146 u8 padding_value;
1148 dbuf_flush(outf);
1149 if(d->padded_size<=0) goto done;
1150 num_padding_bytes = d->padded_size - outf->len;
1151 if(num_padding_bytes<=0) goto done;
1152 de_dbg(c, "[adding padding]");
1154 // TODO: Does it matter what we pad with? Possibilities include 0x00, 0xe5, 0xf6.
1155 padding_value = 0x00;
1156 dbuf_write_run(outf, padding_value, num_padding_bytes);
1157 done:
1161 static void loaddskf_convert_noncmpr_to_ima(deark *c, struct skf_ctx *d)
1163 dbuf *outf = NULL;
1165 outf = dbuf_create_output_file(c, "ima", NULL, 0);
1166 de_dbg(c, "[copying]");
1167 dbuf_copy(c->infile, d->hdr_size, c->infile->len - d->hdr_size, outf);
1168 loaddskf_pad_ima_file(c, d, outf);
1169 dbuf_close(outf);
1172 static void loaddskf_decode_as_fat(deark *c, struct skf_ctx *d)
1174 i64 dlen;
1176 dlen = de_max_int(c->infile->len - d->hdr_size, d->padded_size);
1177 de_dbg(c, "decoding as FAT, pos=%"I64_FMT", len=%"I64_FMT, d->hdr_size, dlen);
1178 if(dlen<=0) goto done;
1180 de_dbg_indent(c, 1);
1181 de_run_module_by_id_on_slice(c, "fat", NULL, c->infile, d->hdr_size, dlen);
1182 de_dbg_indent(c, -1);
1183 done:
1187 static void loaddskf_decompress(deark *c, struct skf_ctx *d)
1189 struct de_dfilter_in_params dcmpri;
1190 struct de_dfilter_out_params dcmpro;
1191 struct de_dfilter_results dres;
1192 dbuf *outf = NULL;
1194 if(d->to_raw) {
1195 outf = dbuf_create_output_file(c, "ima", NULL, 0);
1196 dbuf_enable_wbuffer(outf);
1198 else {
1199 outf = dbuf_create_output_file(c, "unc.dsk", NULL, 0);
1200 dbuf_enable_wbuffer(outf);
1201 dbuf_write(outf, (const u8*)"\xaa\x59", 2);
1202 dbuf_copy(c->infile, 2, d->hdr_size-2, outf);
1205 de_dfilter_init_objects(c, &dcmpri, &dcmpro, &dres);
1206 dcmpri.f = c->infile;
1207 dcmpri.pos = d->hdr_size;
1208 dcmpri.len = c->infile->len - dcmpri.pos;
1209 dcmpro.f = outf;
1211 dbuf_set_writelistener(outf, de_writelistener_for_crc, (void*)d->crco);
1213 de_dbg(c, "[decompressing]");
1214 fmtutil_ibmlzw_codectype1(c, &dcmpri, &dcmpro, &dres, NULL);
1215 dbuf_flush(dcmpro.f);
1216 if(dres.errcode) {
1217 de_err(c, "Decompression failed: %s", de_dfilter_get_errmsg(c, &dres));
1218 goto done;
1221 d->checksum_calc = de_crcobj_getval(d->crco);
1222 loaddskf_finish_checksum(c, d);
1224 if(d->to_raw) {
1225 loaddskf_pad_ima_file(c, d, outf);
1227 // TODO: If !d->to_raw, maybe we should still ensure it decompressed to
1228 // the expected size.
1230 done:
1231 dbuf_close(outf);
1234 static int loaddskf_read_header(deark *c, struct skf_ctx *d)
1236 i64 bytes_per_sector;
1237 i64 num_sectors_per_track;
1238 i64 num_cylinders;
1239 i64 num_heads;
1240 i64 num_sectors_in_image;
1241 int retval = 0;
1243 de_dbg(c, "header");
1244 de_dbg_indent(c, 1);
1246 bytes_per_sector = de_getu16le(4);
1247 de_dbg(c, "bytes per sector: %u", (UI)bytes_per_sector);
1248 d->checksum_reported = (u32)de_getu32le(20); // TODO: What is this?
1249 de_dbg(c, "checksum (reported): 0x%08x", (UI)d->checksum_reported);
1250 num_cylinders = de_getu16le(24);
1251 de_dbg(c, "cylinders: %u", (UI)num_cylinders);
1252 num_heads = de_getu16le(26);
1253 de_dbg(c, "number of heads: %u", (UI)num_heads);
1254 num_sectors_per_track = de_getu16le(28);
1255 de_dbg(c, "sectors per track: %u", (UI)num_sectors_per_track);
1256 num_sectors_in_image = de_getu16le(34);
1257 de_dbg(c, "num sectors in image: %u", (UI)num_sectors_in_image);
1259 d->hdr_size = de_getu16le(38);
1260 if(d->hdr_size==0) d->hdr_size = 512;
1261 de_dbg(c, "header size: %"I64_FMT, d->hdr_size);
1262 if(d->hdr_size<40 || d->hdr_size>c->infile->len) {
1263 goto done;
1266 retval = 1;
1268 if(num_cylinders<20 || num_cylinders>200 ||
1269 num_heads<1 || num_heads>2 ||
1270 num_sectors_per_track<8 || num_sectors_per_track>200 ||
1271 bytes_per_sector<128 || bytes_per_sector>2048)
1273 de_warn(c, "Unexpected disk geometry. Something may have failed.");
1274 goto done;
1277 d->expected_dcmpr_size = num_sectors_in_image * bytes_per_sector;
1278 d->padded_size = num_cylinders * num_heads * num_sectors_per_track * bytes_per_sector;
1279 de_dbg(c, "expected uncmpr image size: %"I64_FMT", padded=%"I64_FMT,
1280 d->expected_dcmpr_size, d->padded_size);
1282 retval = 1;
1284 done:
1285 if(!retval) {
1286 de_err(c, "Failed to parse LoadDskF file");
1288 de_dbg_indent(c, -1);
1289 return retval;
1292 static void de_run_loaddskf(deark *c, de_module_params *mparams)
1294 struct skf_ctx *d = NULL;
1295 const char *subfmt_name;
1296 UI sig;
1298 d = de_malloc(c, sizeof(struct skf_ctx));
1299 d->to_raw = de_get_ext_option_bool(c, "loaddskf:toraw", 0);
1300 d->crco = de_crcobj_create(c, DE_CRCOBJ_SUM_U16LE);
1302 sig = (UI)de_getu16be(0);
1303 switch(sig) {
1304 case 0xaa58:
1305 subfmt_name = "old";
1306 break;
1307 case 0xaa59:
1308 subfmt_name = "new";
1309 d->new_fmt = 1;
1310 break;
1311 case 0xaa5a:
1312 subfmt_name = "new, compressed";
1313 d->new_fmt = 1;
1314 d->is_compressed = 1;
1315 break;
1316 default:
1317 de_err(c, "Not a LoadDskF file");
1318 goto done;
1321 de_declare_fmtf(c, "LoadDskF (%s)", subfmt_name);
1322 if(!loaddskf_read_header(c, d)) goto done;
1323 if(d->is_compressed) {
1324 loaddskf_decompress(c, d);
1326 else {
1327 loaddskf_calc_checksum_noncmpr(c, d);
1328 loaddskf_finish_checksum(c, d);
1329 if(d->to_raw) {
1330 loaddskf_convert_noncmpr_to_ima(c, d);
1332 else {
1333 loaddskf_decode_as_fat(c, d);
1337 done:
1338 if(d) {
1339 de_crcobj_destroy(d->crco);
1340 de_free(c, d);
1344 static int de_identify_loaddskf(deark *c)
1346 UI sig;
1348 sig = (UI)de_getu16be(0);
1349 if(sig==0xaa58 || sig==0xaa59 || sig==0xaa5a) {
1350 if((UI)de_getu16be(2)==0xf000) {
1351 return 100;
1353 return 9;
1355 return 0;
1358 static void de_help_loaddskf(deark *c)
1360 de_msg(c, "-opt loaddskf:toraw : Convert to raw FAT/IMA format");
1363 void de_module_loaddskf(deark *c, struct deark_module_info *mi)
1365 mi->id = "loaddskf";
1366 mi->desc = "LoadDskF/SaveDskF disk image";
1367 mi->run_fn = de_run_loaddskf;
1368 mi->identify_fn = de_identify_loaddskf;
1369 mi->help_fn = de_help_loaddskf;