1 // This file is part of Deark.
2 // Copyright (C) 2020 Jason Summers
3 // See the file COPYING for terms of use.
5 // Amiga disk image (ADF)
7 #include <deark-private.h>
8 DE_DECLARE_MODULE(de_module_amiga_adf
);
10 #define ADF_T_HEADER 2
15 #define ADF_ST_USERDIR 2
16 #define ADF_ST_FILE (-3)
18 #define MAX_ADF_BLOCKS 3520
19 #define MAX_NESTING_LEVEL 16
21 struct block_ptrs_tbl
{
23 i64 blocks_tbl_capacity
;
24 i64
*blocks_tbl
; // array[blocks_tbl_capacity]
37 struct de_timestamp mod_time
;
41 i64 next_block_to_read
;
43 struct block_ptrs_tbl tmpbpt
; // reused for each extension block
46 typedef struct localctx_struct
{
55 u8
*block_used_flags
; // array[num_blocks]
56 struct de_strarray
*curpath
;
59 static i64
blocknum_to_offset(lctx
*d
, i64 blknum
)
61 return d
->bsize
* blknum
;
64 static void on_adf_error(deark
*c
, lctx
*d
, int code
)
66 de_err(c
, "ADF decode error (%d)", code
);
69 // Remember which blocks have been processed, to prevent infinite loops.
70 // In failure, reports an error and returns 0.
71 static int claim_block(deark
*c
, lctx
*d
, i64 blknum
)
73 if(blknum
<0 || blknum
>=d
->num_blocks
) {
74 de_err(c
, "Bad block number: %"I64_FMT
, blknum
);
77 if(!d
->block_used_flags
) {
78 d
->block_used_flags
= de_malloc(c
, d
->num_blocks
);
80 if(d
->block_used_flags
[blknum
]) {
81 de_err(c
, "Attempt to reuse block #%"I64_FMT
, blknum
);
84 d
->block_used_flags
[blknum
] = 1;
88 // Reads a file data block.
89 // On success, returns nonzero and sets md->next_block_to_read.
90 static int do_file_ofs_data_block(deark
*c
, lctx
*d
, struct member_data
*md
,
91 i64 seq_num_expected
, i64 blknum
)
99 int dbg
= (c
->debug_level
>=2);
100 int saved_indent_level
;
102 de_dbg_indent_save(c
, &saved_indent_level
);
103 pos1
= blocknum_to_offset(d
, blknum
);
104 if(dbg
) de_dbg(c
, "data block: blk#%"I64_FMT
" (%"I64_FMT
"), seq=%d", blknum
, pos1
,
105 (int)seq_num_expected
);
108 if(!claim_block(c
, d
, blknum
)) {
113 blocktype
= (int)de_geti32be_p(&pos
);
114 if(dbg
) de_dbg(c
, "block type: %d", blocktype
);
115 if(blocktype
!=ADF_T_DATA
) {
116 de_err(c
, "%s: Bad block type in data block %d (%d, expected %d)",
117 ucstring_getpsz_d(md
->fn
), (int)seq_num_expected
,
118 blocktype
, (int)ADF_T_DATA
);
122 n
= de_getu32be_p(&pos
);
123 if(dbg
) de_dbg(c
, "header_key: %u", (UI
)n
);
124 if(n
!=md
->header_blknum
) {
125 on_adf_error(c
, d
, 12);
129 n
= de_getu32be_p(&pos
);
130 if(dbg
) de_dbg(c
, "seq_num: %u", (UI
)n
);
131 if(n
!=seq_num_expected
) {
132 on_adf_error(c
, d
, 13);
135 data_size
= de_getu32be_p(&pos
);
136 if(dbg
) de_dbg(c
, "data size: %"I64_FMT
, data_size
);
137 if(data_size
> d
->bsize
-24) {
138 de_err(c
, "%s: Bad data size in data block %d (%"I64_FMT
", max=%"I64_FMT
")",
139 ucstring_getpsz_d(md
->fn
), (int)seq_num_expected
,
140 data_size
, (i64
)(d
->bsize
-24));
143 if(md
->nbytes_written
+ data_size
> md
->fsize
) {
144 on_adf_error(c
, d
, 15);
148 md
->next_block_to_read
= de_getu32be_p(&pos
);
149 if(dbg
) de_dbg(c
, "next data block: %"I64_FMT
, md
->next_block_to_read
);
152 dbuf_copy(c
->infile
, dpos
, data_size
, md
->outf
);
153 md
->nbytes_written
+= data_size
;
158 de_dbg_indent_restore(c
, saved_indent_level
);
162 static void read_ofs_timestamp(deark
*c
, i64 pos1
, struct de_timestamp
*ts
,
166 i64 days
, mins
, ticks
;
168 char timestamp_buf
[64];
171 days
= de_getu32be_p(&pos
);
172 mins
= de_getu32be_p(&pos
);
173 ticks
= de_getu32be_p(&pos
);
174 ut
= (6*365 + 2*366) * 86400; // 1970-01-01 to 1978-01-01
180 de_unix_time_to_timestamp(ut
, ts
, 0);
181 de_timestamp_set_subsec(ts
, (double)(ticks
%50) / 50.0);
182 de_timestamp_to_string(ts
, timestamp_buf
, sizeof(timestamp_buf
), 0);
185 de_strlcpy(timestamp_buf
, "none", sizeof(timestamp_buf
));
188 de_dbg(c
, "%s: [%"I64_FMT
",%"I64_FMT
",%"I64_FMT
"] (%s)",
189 name
, days
, mins
, ticks
, timestamp_buf
);
192 static void read_file_ofs_style(deark
*c
, lctx
*d
, struct member_data
*md
)
196 md
->next_block_to_read
= md
->first_data_block
;
201 if(md
->next_block_to_read
==0) break;
203 ret
= do_file_ofs_data_block(c
, d
, md
, seq_num
, md
->next_block_to_read
);
211 static void read_blocks_table(deark
*c
, lctx
*d
, i64 pos
, struct block_ptrs_tbl
*bpt
)
215 for(k
=0; k
<bpt
->blocks_tbl_capacity
; k
++) {
216 bpt
->blocks_tbl
[k
] = de_getu32be(pos
+ 4*k
);
217 if(c
->debug_level
>=2 && bpt
->blocks_tbl
[k
]!=0) {
218 de_dbg2(c
, "blktbl[%d]: %u", (int)k
, (UI
)bpt
->blocks_tbl
[k
]);
223 static int read_file_segment_from_blocks_tbl(deark
*c
, lctx
*d
, struct member_data
*md
)
225 i64 nbytes_left_to_copy
;
229 nbytes_left_to_copy
= md
->fsize
- md
->outf
->len
;
231 for(k
=0; k
<md
->tmpbpt
.high_seq
; k
++) {
236 if(nbytes_left_to_copy
<1) break;
238 blknum
= md
->tmpbpt
.blocks_tbl
[md
->tmpbpt
.blocks_tbl_capacity
-1-k
];
239 if(!claim_block(c
, d
, blknum
)) {
243 blkpos
= blocknum_to_offset(d
, blknum
);
244 nbytes_to_copy
= d
->bsize
;
246 // TODO: If we allow this, it might be better to call
247 // do_file_ofs_data_block(), somehow.
249 nbytes_to_copy
-= 24;
252 if(nbytes_to_copy
> nbytes_left_to_copy
) {
253 nbytes_to_copy
= nbytes_left_to_copy
;
255 dbuf_copy(c
->infile
, blkpos
, nbytes_to_copy
, md
->outf
);
256 nbytes_left_to_copy
-= nbytes_to_copy
;
264 static int read_file_segment_from_extension_block(deark
*c
, lctx
*d
, struct member_data
*md
,
265 i64 blknum
, i64
*pnextblock
)
270 int saved_indent_level
;
272 de_dbg_indent_save(c
, &saved_indent_level
);
274 pos1
= blocknum_to_offset(d
, blknum
);
275 de_dbg(c
, "file ext. block #%"I64_FMT
, blknum
);
278 if(!claim_block(c
, d
, blknum
)) {
282 blocktype
= (int)de_geti32be(pos1
);
283 de_dbg(c
, "block type: %d", blocktype
);
284 if(blocktype
!=ADF_T_LIST
) {
285 de_err(c
, "%s: Bad extension block type in (%d, expected %d)",
286 ucstring_getpsz_d(md
->fn
), blocktype
, (int)ADF_T_LIST
);
290 md
->tmpbpt
.high_seq
= de_getu32be(pos1
+8);
291 de_dbg(c
, "high_seq: %u", (UI
)md
->tmpbpt
.high_seq
);
292 read_blocks_table(c
, d
, pos1
+24, &md
->tmpbpt
);
294 if(!read_file_segment_from_blocks_tbl(c
, d
, md
)) {
298 *pnextblock
= de_getu32be(pos1
+d
->bsize
-8);
299 de_dbg(c
, "next ext. block: %"I64_FMT
, (i64
)(*pnextblock
));
304 de_dbg_indent_restore(c
, saved_indent_level
);
308 static void read_file_using_blocks_table(deark
*c
, lctx
*d
, struct member_data
*md
)
310 i64 cur_ext_header_blk
;
312 if(md
->tmpbpt
.high_seq
> md
->tmpbpt
.blocks_tbl_capacity
) {
313 on_adf_error(c
, d
, 30);
317 // Process the blocks table stored in the main file header
318 if(!read_file_segment_from_blocks_tbl(c
, d
, md
)) goto done
;
320 // Process the chain of extended header blocks
321 if(md
->first_ext_block
) {
322 cur_ext_header_blk
= md
->first_ext_block
;
324 i64 next_ext_header_blk
= 0;
326 if(cur_ext_header_blk
== 0) break;
327 if(md
->outf
->len
>= md
->fsize
) break;
329 if(!read_file_segment_from_extension_block(c
, d
, md
, cur_ext_header_blk
,
330 &next_ext_header_blk
))
334 cur_ext_header_blk
= next_ext_header_blk
;
338 if(md
->outf
->len
< md
->fsize
) {
339 on_adf_error(c
, d
, 26);
347 static void read_protection_flags(deark
*c
, lctx
*d
, struct member_data
*md
, i64 pos
)
351 n
= (UI
)de_getu32be(pos
);
352 de_dbg(c
, "protection flags: 0x%08x", n
);
353 if(md
->fi
&& !md
->is_dir
) {
354 // Some disks use the 0x2 bit to mean "non executable", but I don't think
355 // there's a good way to tell *which* disks.
356 if((n
& 0x0000ff0f)!=0) { // If these flags seem to be used...
358 md
->fi
->mode_flags
|= DE_MODEFLAG_NONEXE
;
361 md
->fi
->mode_flags
|= DE_MODEFLAG_EXE
;
367 static void do_file(deark
*c
, lctx
*d
, struct member_data
*md
)
374 int need_curpath_pop
= 0;
375 de_ucstring
*fullfn
= NULL
;
376 int saved_indent_level
;
378 de_dbg_indent_save(c
, &saved_indent_level
);
380 if(md
->sec_type
!=ADF_ST_FILE
) goto done
;
381 pos1
= md
->header_pos
;
382 de_dbg(c
, "file, header at blk#%"I64_FMT
" (%"I64_FMT
")", md
->header_blknum
, pos1
);
386 header_key
= de_getu32be_p(&pos
);
387 de_dbg(c
, "header_key: %u", (UI
)header_key
);
388 md
->tmpbpt
.high_seq
= de_getu32be_p(&pos
);
389 de_dbg(c
, "high_seq: %u", (UI
)md
->tmpbpt
.high_seq
);
390 pos
+= 4; // data_size - unused
391 md
->first_data_block
= de_getu32be_p(&pos
);
392 de_dbg(c
, "first data block: %"I64_FMT
, md
->first_data_block
);
394 if(header_key
!=md
->header_blknum
) {
395 de_err(c
, "Bad self-pointer (%"I64_FMT
") in block #%"I64_FMT
, header_key
,
400 blocks_tbl_pos
= md
->header_pos
+ 24;
401 md
->tmpbpt
.blocks_tbl_capacity
= (d
->bsize
/4) - 56;
402 md
->tmpbpt
.blocks_tbl
= de_mallocarray(c
, md
->tmpbpt
.blocks_tbl_capacity
,
403 sizeof(md
->tmpbpt
.blocks_tbl
[0]));
405 read_blocks_table(c
, d
, blocks_tbl_pos
, &md
->tmpbpt
);
407 read_protection_flags(c
, d
, md
, pos1
+d
->bsize
-192);
409 pos
= pos1
+d
->bsize
-188;
410 md
->fsize
= de_getu32be_p(&pos
);
411 de_dbg(c
, "file size: %"I64_FMT
, md
->fsize
);
413 pos
= pos1
+d
->bsize
-92;
414 read_ofs_timestamp(c
, pos
, &md
->mod_time
, "mod time");
416 pos
= pos1
+d
->bsize
-80;
417 fnlen
= (i64
)de_getbyte_p(&pos
);
418 if(fnlen
>30) fnlen
=30;
419 md
->fn
= ucstring_create(c
);
420 dbuf_read_to_ucstring(c
->infile
, pos
, fnlen
, md
->fn
, 0, DE_ENCODING_LATIN1
);
421 de_dbg(c
, "filename: \"%s\"", ucstring_getpsz_d(md
->fn
));
422 de_strarray_push(d
->curpath
, md
->fn
);
423 need_curpath_pop
= 1;
425 pos
= pos1
+d
->bsize
-12;
426 n
= de_getu32be_p(&pos
);
427 de_dbg(c
, "parent dir: %"I64_FMT
, n
);
428 md
->first_ext_block
= de_getu32be_p(&pos
);
429 de_dbg(c
, "first ext. block: %"I64_FMT
, md
->first_ext_block
);
431 if(md
->sec_type
!=ADF_ST_FILE
) {
432 de_dbg(c
, "[not a supported file type]");
436 md
->fi
->original_filename_flag
= 1;
437 fullfn
= ucstring_create(c
);
438 de_strarray_make_path(d
->curpath
, fullfn
, DE_MPFLAG_NOTRAILINGSLASH
);
439 de_finfo_set_name_from_ucstring(c
, md
->fi
, fullfn
, DE_SNFLAG_FULLPATH
);
441 if(md
->mod_time
.is_valid
) {
442 md
->fi
->timestamp
[DE_TIMESTAMPIDX_MODIFY
] = md
->mod_time
;
445 md
->outf
= dbuf_create_output_file(c
, NULL
, md
->fi
, 0x0);
448 read_file_using_blocks_table(c
, d
, md
);
451 read_file_ofs_style(c
, d
, md
);
455 if(need_curpath_pop
) {
456 de_strarray_pop(d
->curpath
);
458 ucstring_destroy(fullfn
);
459 de_dbg_indent_restore(c
, saved_indent_level
);
462 static int do_header_block(deark
*c
, lctx
*d
, i64 blknum
);
464 static void do_file_list(deark
*c
, lctx
*d
, i64 blknum
)
468 int saved_indent_level
;
470 de_dbg_indent_save(c
, &saved_indent_level
);
472 pos1
= blocknum_to_offset(d
, blknum
);
473 de_dbg(c
, "file list starting at blk#%"I64_FMT
, blknum
);
478 if(!do_header_block(c
, d
, blknum
)) goto done
;
479 next_in_chain
= de_getu32be(pos1
+d
->bsize
-16);
480 de_dbg(c
, "next: %"I64_FMT
, next_in_chain
);
481 blknum
= next_in_chain
;
482 pos1
= blocknum_to_offset(d
, blknum
);
486 de_dbg_indent_restore(c
, saved_indent_level
);
489 static void do_hashtable(deark
*c
, lctx
*d
, i64 pos1
, i64 ht_size_in_longs
)
493 int saved_indent_level
;
496 de_dbg_indent_save(c
, &saved_indent_level
);
498 de_dbg(c
, "hashtable at %"I64_FMT
, pos1
);
501 for(k
=0; k
<ht_size_in_longs
; k
++) {
503 n
= de_getu32be_p(&pos
);
504 if(n
>0 || c
->debug_level
>=2) {
505 de_dbg(c
, "ht[%u]: %u", (UI
)k
, (UI
)n
);
510 do_file_list(c
, d
, n
);
511 de_dbg_indent(c
, -1);
514 de_dbg(c
, "hash buckets in use: %d of %d", (int)used_count
, (int)ht_size_in_longs
);
516 de_dbg_indent_restore(c
, saved_indent_level
);
519 // ST_ROOT or ST_USERDIR
520 static void do_directory(deark
*c
, lctx
*d
, struct member_data
*md
)
523 i64 ht_size_in_longs
;
524 int saved_indent_level
;
525 int need_curpath_pop
= 0;
526 de_ucstring
*fullfn
= NULL
;
527 struct de_timestamp tmpts
;
529 de_dbg_indent_save(c
, &saved_indent_level
);
532 pos1
= md
->header_pos
;
533 de_dbg(c
, "directory header block: #%"I64_FMT
" (%"I64_FMT
")", md
->header_blknum
, pos1
);
535 if(md
->sec_type
!=ADF_ST_ROOT
&& md
->sec_type
!=ADF_ST_USERDIR
) {
539 if(md
->sec_type
==ADF_ST_ROOT
) {
540 ht_size_in_longs
= de_getu32be(pos1
+12);
541 de_dbg(c
, "hashtable size: %"I64_FMT
" longwords", ht_size_in_longs
);
542 if(ht_size_in_longs
>128) {
543 on_adf_error(c
, d
, 20);
548 ht_size_in_longs
= (d
->bsize
/4) - 56;
551 read_protection_flags(c
, d
, md
, pos1
+d
->bsize
-192);
553 read_ofs_timestamp(c
, pos1
+d
->bsize
-92, &md
->mod_time
, "dir mod time");
555 if(md
->sec_type
==ADF_ST_USERDIR
) {
558 fnlen
= (i64
)de_getbyte(pos1
+d
->bsize
-80);
559 if(fnlen
>30) fnlen
=30;
560 md
->fn
= ucstring_create(c
);
561 dbuf_read_to_ucstring(c
->infile
, pos1
+d
->bsize
-79, fnlen
, md
->fn
, 0, DE_ENCODING_LATIN1
);
562 de_dbg(c
, "dirname: \"%s\"", ucstring_getpsz_d(md
->fn
));
563 de_strarray_push(d
->curpath
, md
->fn
);
564 need_curpath_pop
= 1;
567 if(md
->sec_type
==ADF_ST_ROOT
) {
568 read_ofs_timestamp(c
, pos1
+d
->bsize
-40, &tmpts
, "disk mod time");
569 read_ofs_timestamp(c
, pos1
+d
->bsize
-28, &tmpts
, "filesystem create time");
573 md
->fi
->is_directory
= 1;
574 if(md
->sec_type
==ADF_ST_ROOT
) {
575 md
->fi
->is_root_dir
= 1;
577 if(md
->sec_type
==ADF_ST_USERDIR
) {
579 md
->fi
->original_filename_flag
= 1;
580 fullfn
= ucstring_create(c
);
581 de_strarray_make_path(d
->curpath
, fullfn
, DE_MPFLAG_NOTRAILINGSLASH
);
582 de_finfo_set_name_from_ucstring(c
, md
->fi
, fullfn
, DE_SNFLAG_FULLPATH
);
586 if(md
->mod_time
.is_valid
) {
587 md
->fi
->timestamp
[DE_TIMESTAMPIDX_MODIFY
] = md
->mod_time
;
590 md
->outf
= dbuf_create_output_file(c
, NULL
, md
->fi
, 0x0);
591 dbuf_close(md
->outf
);
594 // Now recurse into the files and subdirs in this directory.
595 do_hashtable(c
, d
, pos1
+24, ht_size_in_longs
);
598 if(need_curpath_pop
) {
599 de_strarray_pop(d
->curpath
);
601 ucstring_destroy(fullfn
);
602 de_dbg_indent_restore(c
, saved_indent_level
);
605 // For block type 2 (ST_HEADER).
606 // Returns 1 unless the block isn't a header block.
607 static int do_header_block(deark
*c
, lctx
*d
, i64 blknum
)
612 int saved_indent_level
;
613 struct member_data
*md
= NULL
;
615 de_dbg_indent_save(c
, &saved_indent_level
);
618 if(d
->nesting_level
>MAX_NESTING_LEVEL
) goto done
;
620 pos1
= blocknum_to_offset(d
, blknum
);
622 if(!claim_block(c
, d
, blknum
)) {
626 blocktype
= (int)de_geti32be(pos1
);
627 if(blocktype
==ADF_T_HEADER
) {
631 md
= de_malloc(c
, sizeof(struct member_data
));
632 md
->header_blknum
= blknum
;
633 md
->header_pos
= blocknum_to_offset(d
, md
->header_blknum
);
634 md
->fi
= de_finfo_create(c
);
636 de_dbg(c
, "header block: #%"I64_FMT
" (%"I64_FMT
")", blknum
, pos1
);
639 de_dbg(c
, "block type: %d", blocktype
);
640 if(blocktype
!=ADF_T_HEADER
) {
641 de_err(c
, "Expected header block #%"I64_FMT
" (at %"I64_FMT
") not found", blknum
, pos1
);
644 md
->sec_type
= (UI
)de_getu32be(pos1
+d
->bsize
-4);
645 de_dbg(c
, "block secondary type: %d", md
->sec_type
);
647 if(md
->sec_type
==ADF_ST_ROOT
|| md
->sec_type
==ADF_ST_USERDIR
) {
648 do_directory(c
, d
, md
);
650 else if(md
->sec_type
==ADF_ST_FILE
) {
654 de_warn(c
, "Unsupported file type: %d", md
->sec_type
);
661 dbuf_close(md
->outf
);
663 de_finfo_destroy(c
, md
->fi
);
664 ucstring_destroy(md
->fn
);
665 de_free(c
, md
->tmpbpt
.blocks_tbl
);
668 de_dbg_indent_restore(c
, saved_indent_level
);
673 // If true, sets d->root_block
674 static int test_root_block(deark
*c
, lctx
*d
, i64 blknum
)
678 pos
= blocknum_to_offset(d
, blknum
);
679 if(de_getu32be(pos
) != ADF_T_HEADER
) return 0;
680 if(de_getu32be(pos
+d
->bsize
-4) != ADF_ST_ROOT
) return 0;
681 d
->root_block
= blknum
;
685 // If found, sets d->root_block
686 static int find_root_block(deark
*c
, lctx
*d
, i64 root_block_reported
)
688 if(c
->infile
->len
>= 1802240) {
689 if(test_root_block(c
, d
, 880*2)) return 1;
692 if(test_root_block(c
, d
, 880)) return 1;
695 if(test_root_block(c
, d
, root_block_reported
)) return 1;
697 if((c
->infile
->len
>= (901120+d
->bsize
)) && (c
->infile
->len
< 1802240)) {
698 if(test_root_block(c
, d
, 880*2)) return 1;
704 static void de_run_amiga_adf(deark
*c
, de_module_params
*mparams
)
707 i64 root_block_reported
;
708 int saved_indent_level
;
709 de_ucstring
*flags_descr
;
711 de_dbg_indent_save(c
, &saved_indent_level
);
713 d
= de_malloc(c
, sizeof(lctx
));
716 de_dbg(c
, "header at %d", 0);
719 d
->bootblock_flags
= (de_getbyte(3) & 0x07);
720 flags_descr
= ucstring_create(c
);
722 if(d
->bootblock_flags
& 0x1) {
725 if(d
->bootblock_flags
& 0x2) {
728 if(d
->bootblock_flags
& 0x4) {
732 ucstring_append_flags_item(flags_descr
, d
->is_ffs
?"FFS":"OFS");
734 ucstring_append_flags_item(flags_descr
, "international mode");
737 ucstring_append_flags_item(flags_descr
, "dircache mode");
739 de_dbg(c
, "flags: 0x%02x (%s)", (UI
)d
->bootblock_flags
, ucstring_getpsz_d(flags_descr
));
740 ucstring_destroy(flags_descr
);
742 de_declare_fmtf(c
, "Amiga ADF, %s", d
->is_ffs
?"FFS":"OFS");
744 root_block_reported
= de_getu32be(8);
745 de_dbg(c
, "root block (reported): %"I64_FMT
, root_block_reported
);
747 d
->num_blocks
= de_pad_to_n(c
->infile
->len
, 512) / 512;
748 if(d
->num_blocks
> MAX_ADF_BLOCKS
) {
749 d
->num_blocks
= MAX_ADF_BLOCKS
;
751 de_dbg_indent(c
, -1);
753 if(d
->dirc_mode
|| d
->intnl_mode
) {
754 de_warn(c
, "This type of ADF file might not be supported correctly");
757 if(!find_root_block(c
, d
, root_block_reported
)) {
758 de_err(c
, "Root block not found");
762 d
->curpath
= de_strarray_create(c
, MAX_NESTING_LEVEL
+10);
764 if(!do_header_block(c
, d
, d
->root_block
)) goto done
;
768 de_free(c
, d
->block_used_flags
);
769 de_strarray_destroy(d
->curpath
);
772 de_dbg_indent_restore(c
, saved_indent_level
);
775 static int de_identify_amiga_adf(deark
*c
)
780 if(dbuf_memcmp(c
->infile
, 0, "DOS", 3)) return 0;
781 if(de_getbyte(3)>0x05) return 0;
782 has_ext
= de_input_file_has_ext(c
, "adf");
783 has_size
= (c
->infile
->len
==901120 || c
->infile
->len
==1802240);
784 if(has_ext
&& has_size
) return 100;
785 if(has_size
) return 90;
786 if(has_ext
) return 60;
790 void de_module_amiga_adf(deark
*c
, struct deark_module_info
*mi
)
792 mi
->id
= "amiga_adf";
793 mi
->desc
= "Amiga disk image";
794 mi
->run_fn
= de_run_amiga_adf
;
795 mi
->identify_fn
= de_identify_amiga_adf
;