1 // This file is part of Deark.
2 // Copyright (C) 2020 Jason Summers
3 // See the file COPYING for terms of use.
5 // Amiga DMS (Disk Masher System) disk image
7 // The DMS module was developed with the help of information from xDMS -
8 // public domain(-ish) software by Andre Rodrigues de la Rocha.
11 // DMS does a thing I call "persistent decompression state".
12 // Roughly speaking, the state of some decompressors is reset only after
13 // processing a track for which the low bit of the track_flags field is 0.
14 // Otherwise it persists, and will most likely be used with a future track.
15 // I don't understand the fine details of how this works, especially when it
16 // comes to "extra" tracks that are not part of the main track sequence.
17 // xDMS behaves as if the state from an extra track never persists into a
18 // "real" track, or vice versa, even if its flag says it does. I have a file
19 // that seems to confirm that that is the case.
20 // But that still leaves a lot of open questions. (What's the precise
21 // definition of an extra track? What if there are multiple compressed extra
22 // tracks? Or an extra track in the middle of the real tracks? To what extent
23 // can multiple compression methods be used in the same file, and how do they
26 #include <deark-private.h>
27 #include <deark-fmtutil.h>
29 DE_DECLARE_MODULE(de_module_amiga_dms
);
31 // Used as both the maximum number of physical tracks in the file, and (one more
32 // than) the highest logical track number allowed for a "real" track.
33 #define DMS_MAX_TRACKS 200
35 #define DMS_FILE_HDR_LEN 56
36 #define DMS_TRACK_HDR_LEN 20
38 #define DMSCMPR_NONE 0
40 #define DMSCMPR_QUICK 2
41 #define DMSCMPR_MEDIUM 3
42 #define DMSCMPR_DEEP 4
43 #define DMSCMPR_HEAVY1 5
44 #define DMSCMPR_HEAVY2 6
46 struct dms_track_info
{
47 i64 track_num
; // The reported (logical) track number
56 u32 crc_cmprdata_reported
;
57 u32 crc_header_reported
;
62 struct dms_tracks_by_file_order_entry
{
68 struct dms_tracks_by_track_num_entry
{
73 struct dms_std_cmpr_state
;
74 struct dmsheavy_cmpr_state
;
76 struct std_saved_state_wrapper
{
77 struct dms_std_cmpr_state
*saved_state
;
83 i64 first_track
, last_track
;
84 i64 num_tracks_in_file
;
86 // Entries in use: 0 <= n < .num_tracks_in_file
87 struct dms_tracks_by_file_order_entry tracks_by_file_order
[DMS_MAX_TRACKS
];
89 // Entries potentially in use: .first_track <= n <= .last_track
90 struct dms_tracks_by_track_num_entry tracks_by_track_num
[DMS_MAX_TRACKS
];
92 struct std_saved_state_wrapper saved_medium_state
;
93 struct std_saved_state_wrapper saved_deep_cmpr_state
;
94 struct dmsheavy_cmpr_state
*saved_heavy_state
;
97 // For some decompressors, the portion of the decompression context that can
98 // persist between tracks
99 struct dms_std_cmpr_state
{
101 const char *cmpr_meth_name
;
103 struct de_dfilter_ctx
*dfctx_codec1
;
104 struct de_dfilter_ctx
*dfctx_rle
;
105 i64 intermediate_nbytes
;
108 struct de_dfilter_out_params dcmpro1
;
109 struct de_dfilter_results dres1
;
112 static const char *dms_get_cmprtype_name(UI n
)
114 const char *name
= NULL
;
116 case DMSCMPR_NONE
: name
="uncompressed"; break;
117 case DMSCMPR_RLE
: name
="simple (RLE)"; break;
118 case DMSCMPR_QUICK
: name
="quick"; break;
119 case DMSCMPR_MEDIUM
: name
="medium (RLE + LZ77)"; break;
120 case DMSCMPR_DEEP
: name
="deep (RLE + LZ77+dynamic_huffman)"; break;
121 case DMSCMPR_HEAVY1
: name
="heavy1 (optional RLE + LZ77-4K+Huffman)"; break;
122 case DMSCMPR_HEAVY2
: name
="heavy2 (optional RLE + LZ77-8K+Huffman)"; break;
124 return name
?name
:"?";
127 static void read_unix_timestamp(deark
*c
, i64 pos
, struct de_timestamp
*ts
, const char *name
)
130 char timestamp_buf
[64];
132 t
= de_geti32be(pos
);
133 de_unix_time_to_timestamp(t
, ts
, 0x1);
134 de_timestamp_to_string(ts
, timestamp_buf
, sizeof(timestamp_buf
), 0);
135 de_dbg(c
, "%s: %"I64_FMT
" (%s)", name
, t
, timestamp_buf
);
138 static void destroy_std_cmpr_state(deark
*c
, struct dms_std_cmpr_state
*cst
)
141 if(cst
->dfctx_codec1
) {
142 de_dfilter_destroy(cst
->dfctx_codec1
);
145 de_dfilter_destroy(cst
->dfctx_rle
);
147 dbuf_close(cst
->outf_codec1
);
151 static void my_std1_write_cb(dbuf
*f
, void *userdata
,
152 const u8
*buf
, i64 size
)
154 struct dms_std_cmpr_state
*u
= (struct dms_std_cmpr_state
*)userdata
;
157 de_internal_err_fatal(f
->c
, "%s", u
->cmpr_meth_name
);
159 de_dfilter_addbuf(u
->dfctx_rle
, buf
, size
);
160 u
->intermediate_nbytes
+= size
;
163 /////// Heavy (LZH) compression ///////
165 // Note: A lot of this is very similar to the code in fmtutil-lzh.c.
166 // The main problem with using standard LZH code for DMS is that some of the
167 // decompression state persists from one track to the next. But not all of it
168 // -- you can't just concatenate the compressed data together before
171 struct lzh_tree_wrapper
{
172 struct fmtutil_huffman_decoder
*ht
;
175 // The portion of the Heavy decompression context that can persist between tracks.
176 struct dmsheavy_cmpr_state
{
178 UI heavy_prev_offset
;
179 struct de_lz77buffer
*ringbuf
;
181 struct lzh_tree_wrapper literals_tree
;
182 struct lzh_tree_wrapper offsets_tree
;
185 // The portion of the Heavy decompression context that does *not* persist between tracks.
188 struct de_dfilter_in_params
*dcmpri
;
189 struct de_dfilter_out_params
*dcmpro
;
190 struct de_dfilter_results
*dres
;
196 // bitrd.eof_flag: Always set if err_flag is set.
197 struct de_bitreader bitrd
;
202 struct dmslzh_params
{
203 UI cmpr_type
; // 5=heavy1, 6=heavy2
205 struct dmsheavy_cmpr_state
*heavy_state
;
208 static void lzh_set_eof_flag(struct lzh_ctx
*cctx
)
210 cctx
->bitrd
.eof_flag
= 1;
213 static void lzh_set_err_flag(struct lzh_ctx
*cctx
)
215 lzh_set_eof_flag(cctx
);
219 static int lzh_have_enough_output(struct lzh_ctx
*cctx
)
221 if(cctx
->dcmpro
->len_known
) {
222 if(cctx
->nbytes_written
>= cctx
->dcmpro
->expected_len
) {
229 static void lha5like_lz77buf_writebytecb(struct de_lz77buffer
*rb
, u8 n
)
231 struct lzh_ctx
*cctx
= (struct lzh_ctx
*)rb
->userdata
;
233 if(lzh_have_enough_output(cctx
)) {
236 dbuf_writebyte(cctx
->dcmpro
->f
, n
);
237 cctx
->nbytes_written
++;
240 static UI
read_next_code_using_tree(struct lzh_ctx
*cctx
, struct lzh_tree_wrapper
*tree
)
242 fmtutil_huffman_valtype val
= 0;
245 if(!tree
->ht
) goto done
;
247 ret
= fmtutil_huffman_read_next_value(tree
->ht
->bk
, &cctx
->bitrd
, &val
, NULL
);
248 if(cctx
->bitrd
.eof_flag
) {
249 de_dfilter_set_errorf(cctx
->c
, cctx
->dres
, cctx
->modname
,
250 "Unexpected end of compressed data");
251 lzh_set_err_flag(cctx
);
256 de_dfilter_set_errorf(cctx
->c
, cctx
->dres
, cctx
->modname
,
257 "Huffman decoding error");
258 lzh_set_err_flag(cctx
);
267 static int dmsheavy_read_tree(struct lzh_ctx
*cctx
, struct lzh_tree_wrapper
*htw
,
268 UI ncodes_nbits
, UI symlen_nbits
)
275 if(htw
->ht
) goto done
;
277 ncodes
= (UI
)de_bitreader_getbits(&cctx
->bitrd
, ncodes_nbits
);
278 de_dbg2(c
, "num codes: %u", ncodes
);
280 htw
->ht
= fmtutil_huffman_create_decoder(c
, (i64
)ncodes
, (i64
)ncodes
);
285 null_val
= (UI
)de_bitreader_getbits(&cctx
->bitrd
, ncodes_nbits
);
286 fmtutil_huffman_add_code(c
, htw
->ht
->bk
, 0, 0, (fmtutil_huffman_valtype
)null_val
);
287 de_dbg3(c
, "val0: %u", null_val
);
293 while(curr_idx
< ncodes
) {
296 symlen
= (UI
)de_bitreader_getbits(&cctx
->bitrd
, symlen_nbits
);
297 de_dbg3(c
, "len[%u] = %u", curr_idx
, symlen
);
298 fmtutil_huffman_record_a_code_length(c
, htw
->ht
->builder
, (fmtutil_huffman_valtype
)curr_idx
, symlen
);
301 if(cctx
->bitrd
.eof_flag
) goto done
;
303 if(!fmtutil_huffman_make_canonical_code(c
, htw
->ht
->bk
, htw
->ht
->builder
, 0)) goto done
;
308 lzh_set_err_flag(cctx
);
313 static void dmsheavy_discard_tree(deark
*c
, struct lzh_tree_wrapper
*htw
)
316 fmtutil_huffman_destroy_decoder(c
, htw
->ht
);
321 static void decompress_dms_heavy(struct lzh_ctx
*cctx
, struct dmslzh_params
*lzhp
,
322 struct dmsheavy_cmpr_state
*hvst
)
327 int saved_indent_level
;
330 de_dbg_indent_save(c
, &saved_indent_level
);
332 if(lzhp
->cmpr_type
!= hvst
->cmpr_type
) {
333 de_dfilter_set_errorf(c
, cctx
->dres
, cctx
->modname
,
334 "Mixing Heavy compression types is not supported");
338 if(lzhp
->cmpr_type
==DMSCMPR_HEAVY1
) {
340 cctx
->heavy_np
= 14; // for heavy1
344 cctx
->heavy_np
= 15; // for heavy2
348 hvst
->ringbuf
= de_lz77buffer_create(cctx
->c
, rb_size
);
351 hvst
->ringbuf
->userdata
= (void*)cctx
;
352 hvst
->ringbuf
->writebyte_cb
= lha5like_lz77buf_writebytecb
;
354 if(!cctx
->dcmpro
->len_known
) {
355 // I think we (may) have to know the output length, because zero-length Huffman
356 // codes are(?) possible, and unlike lh5 we aren't told how many codes there are.
357 de_dfilter_set_errorf(cctx
->c
, cctx
->dres
, cctx
->modname
, "Internal error");
361 if(lzhp
->dms_track_flags
& 0x02) {
362 dmsheavy_discard_tree(c
, &hvst
->literals_tree
);
363 dmsheavy_discard_tree(c
, &hvst
->offsets_tree
);
364 hvst
->trees_exist
= 0;
367 if(!hvst
->trees_exist
) {
368 hvst
->trees_exist
= 1;
369 de_dbg2(c
, "c tree");
371 ret
= dmsheavy_read_tree(cctx
, &hvst
->literals_tree
, 9, 5);
372 de_dbg_indent(c
, -1);
375 de_dbg2(c
, "p tree");
377 ret
= dmsheavy_read_tree(cctx
, &hvst
->offsets_tree
, 5, 4);
378 de_dbg_indent(c
, -1);
382 de_bitreader_describe_curpos(&cctx
->bitrd
, pos_descr
, sizeof(pos_descr
));
383 de_dbg2(c
, "cmpr data codes at %s", pos_descr
);
388 if(cctx
->bitrd
.eof_flag
) goto done
;
389 if(lzh_have_enough_output(cctx
)) goto done
;
391 code
= read_next_code_using_tree(cctx
, &hvst
->literals_tree
);
392 if(cctx
->bitrd
.eof_flag
) goto done
;
393 if(c
->debug_level
>=3) {
394 de_dbg3(c
, "code: %u (opos=%"I64_FMT
")", code
, cctx
->dcmpro
->f
->len
);
397 if(code
< 256) { // literal
398 de_lz77buffer_add_literal_byte(hvst
->ringbuf
, (u8
)code
);
400 else { // repeat previous bytes
406 ocode1
= read_next_code_using_tree(cctx
, &hvst
->offsets_tree
);
407 if(cctx
->bitrd
.eof_flag
) goto done
;
409 if(ocode1
== cctx
->heavy_np
-1) {
410 offset
= hvst
->heavy_prev_offset
;
419 ocode2
= (UI
)de_bitreader_getbits(&cctx
->bitrd
, ocode1
-1);
420 if(cctx
->bitrd
.eof_flag
) goto done
;
421 offset
= ocode2
| (1U<<(ocode1
-1));
423 hvst
->heavy_prev_offset
= offset
;
426 de_lz77buffer_copy_from_hist(hvst
->ringbuf
,
427 (UI
)(hvst
->ringbuf
->curpos
-offset
-1), length
);
432 de_dbg_indent_restore(c
, saved_indent_level
);
435 static void destroy_heavy_state(deark
*c
, struct dmsheavy_cmpr_state
*hvst
)
438 dmsheavy_discard_tree(c
, &hvst
->literals_tree
);
439 dmsheavy_discard_tree(c
, &hvst
->offsets_tree
);
440 de_lz77buffer_destroy(c
, hvst
->ringbuf
);
443 static void dmslzh_codectype1(deark
*c
, struct de_dfilter_in_params
*dcmpri
,
444 struct de_dfilter_out_params
*dcmpro
, struct de_dfilter_results
*dres
,
445 void *codec_private_params
)
447 struct dmslzh_params
*lzhp
= (struct dmslzh_params
*)codec_private_params
;
448 struct lzh_ctx
*cctx
= NULL
;
449 struct dmsheavy_cmpr_state
*hvst
= NULL
;
451 cctx
= de_malloc(c
, sizeof(struct lzh_ctx
));
452 cctx
->modname
= "undmslzh";
454 cctx
->dcmpri
= dcmpri
;
455 cctx
->dcmpro
= dcmpro
;
457 cctx
->bitrd
.f
= dcmpri
->f
;
458 cctx
->bitrd
.curpos
= dcmpri
->pos
;
459 cctx
->bitrd
.endpos
= dcmpri
->pos
+ dcmpri
->len
;
461 if(lzhp
->heavy_state
) {
462 // If a previous decompression state exists, use it.
463 hvst
= lzhp
->heavy_state
;
464 lzhp
->heavy_state
= NULL
;
467 hvst
= de_malloc(c
, sizeof(struct dmsheavy_cmpr_state
));
468 hvst
->cmpr_type
= lzhp
->cmpr_type
;
471 decompress_dms_heavy(cctx
, lzhp
, hvst
);
473 hvst
->ringbuf
->userdata
= NULL
;
474 hvst
->ringbuf
->writebyte_cb
= NULL
;
475 lzhp
->heavy_state
= hvst
;
479 // A default error message
480 de_dfilter_set_errorf(c
, dres
, cctx
->modname
, "LZH decoding error");
484 de_bitreader_skip_to_byte_boundary(&cctx
->bitrd
);
485 cctx
->dres
->bytes_consumed
= cctx
->bitrd
.curpos
- cctx
->dcmpri
->pos
;
486 if(cctx
->dres
->bytes_consumed
<0) {
487 cctx
->dres
->bytes_consumed
= 0;
489 cctx
->dres
->bytes_consumed_valid
= 1;
492 if(hvst
) destroy_heavy_state(c
, hvst
);
496 /////// RLE compression ///////
500 // ---------------------------------------------------------
501 // 0x90 0x00 emit 0x90
502 // 0x90 0x01..0xfe n3 emit n2 copies of n3
503 // 0x90 0xff n3 n4 n5 emit (n4#n5) copies of n3
507 DMSRLE_STATE_NEUTRAL
= 0,
510 DMSRLE_STATE_90_FF_N3
,
511 DMSRLE_STATE_90_FF_N3_N4
515 enum dmsrle_state state
;
519 static void dmsrle_codec_addbuf(struct de_dfilter_ctx
*dfctx
,
520 const u8
*buf
, i64 buf_len
)
523 struct dmsrle_ctx
*rctx
= (struct dmsrle_ctx
*)dfctx
->codec_private
;
527 for(i
=0; i
<buf_len
; i
++) {
533 switch(rctx
->state
) {
534 case DMSRLE_STATE_NEUTRAL
:
536 rctx
->state
= DMSRLE_STATE_90
;
539 dbuf_writebyte(dfctx
->dcmpro
->f
, n
);
542 case DMSRLE_STATE_90
:
544 dbuf_writebyte(dfctx
->dcmpro
->f
, 0x90);
545 rctx
->state
= DMSRLE_STATE_NEUTRAL
;
549 rctx
->state
= DMSRLE_STATE_90_N2
;
552 case DMSRLE_STATE_90_N2
:
555 rctx
->state
= DMSRLE_STATE_90_FF_N3
;
558 count
= (i64
)rctx
->n2
;
559 dbuf_write_run(dfctx
->dcmpro
->f
, n
, count
);
560 rctx
->state
= DMSRLE_STATE_NEUTRAL
;
563 case DMSRLE_STATE_90_FF_N3
:
565 rctx
->state
= DMSRLE_STATE_90_FF_N3_N4
;
567 case DMSRLE_STATE_90_FF_N3_N4
:
568 count
= (i64
)(((UI
)rctx
->n4
<< 8) | n
);
569 dbuf_write_run(dfctx
->dcmpro
->f
, rctx
->n3
, count
);
570 rctx
->state
= DMSRLE_STATE_NEUTRAL
;
578 static void dmsrle_codec_destroy(struct de_dfilter_ctx
*dfctx
)
580 struct dmsrle_ctx
*rctx
= (struct dmsrle_ctx
*)dfctx
->codec_private
;
583 de_free(dfctx
->c
, rctx
);
585 dfctx
->codec_private
= NULL
;
588 // codec_private_params: Unused, should be NULL.
589 static void dmsrle_codec(struct de_dfilter_ctx
*dfctx
, void *codec_private_params
)
591 struct dmsrle_ctx
*rctx
= NULL
;
593 rctx
= de_malloc(dfctx
->c
, sizeof(struct dmsrle_ctx
));
594 rctx
->state
= DMSRLE_STATE_NEUTRAL
;
595 dfctx
->codec_private
= (void*)rctx
;
596 dfctx
->codec_addbuf_fn
= dmsrle_codec_addbuf
;
597 dfctx
->codec_finish_fn
= NULL
;
598 dfctx
->codec_destroy_fn
= dmsrle_codec_destroy
;
601 ///////////////// Codec for the LZ77 part of "Medium" decompression //////////////
605 MEDLZST_WAITING_FOR_OCODE1
,
606 MEDLZST_WAITING_FOR_OCODE2
609 struct medium_state_machine
{
610 enum medlzst_enum state
;
611 // TODO: We don't really need this much state.
625 struct de_dfilter_out_params
*dcmpro
;
626 struct de_dfilter_results
*dres
;
629 struct de_lz77buffer
*ringbuf
;
630 struct de_bitbuf_lowlevel bbll
;
631 struct medium_state_machine mdst
;
634 static int medium_have_enough_output(struct medium_ctx
*mctx
)
636 if(mctx
->dcmpro
->len_known
) {
637 if(mctx
->nbytes_written
>= mctx
->dcmpro
->expected_len
) {
644 static void mediumlz77_codec_addbuf(struct de_dfilter_ctx
*dfctx
,
645 const u8
*buf
, i64 buf_len
)
647 struct medium_ctx
*mctx
= (struct medium_ctx
*)dfctx
->codec_private
;
648 struct medium_state_machine
*mdst
= &mctx
->mdst
;
654 if(dfctx
->finished_flag
|| mctx
->errflag
) {
659 if(medium_have_enough_output(mctx
)) {
660 dfctx
->finished_flag
= 1;
664 // Top off the bitbuf, if possible. 32 is an arbitrary number that's
665 // large enough for this format.
666 while(bufpos
<buf_len
&& mctx
->bbll
.nbits_in_bitbuf
<32) {
667 de_bitbuf_lowlevel_add_byte(&mctx
->bbll
, buf
[bufpos
++]);
670 if(mdst
->state
==MEDLZST_WAITING_FOR_OCODE1
) goto read_ocode1
;
671 if(mdst
->state
==MEDLZST_WAITING_FOR_OCODE2
) goto read_ocode2
;
673 // Make sure we have enough source data to read the flag bit,
674 // plus either the literal byte or the first_code.
675 if(mctx
->bbll
.nbits_in_bitbuf
< 9) goto done
;
677 n
= (UI
)de_bitbuf_lowlevel_get_bits(&mctx
->bbll
, 1);
679 if(n
) { // literal byte
682 b
= (u8
)de_bitbuf_lowlevel_get_bits(&mctx
->bbll
, 8);
683 de_lz77buffer_add_literal_byte(mctx
->ringbuf
, (u8
)b
);
687 // TODO: This seems overly complicated. Is there a simpler way to
690 mdst
->first_code
= (UI
)de_bitbuf_lowlevel_get_bits(&mctx
->bbll
, 8);
691 fmtutil_get_lzhuf_d_code_and_len(mdst
->first_code
, &mdst
->d_code1
, &mdst
->d_len1
);
694 mdst
->ocode1_nbits
= mdst
->d_len1
;
695 if(mctx
->bbll
.nbits_in_bitbuf
< mdst
->ocode1_nbits
) {
696 mdst
->state
= MEDLZST_WAITING_FOR_OCODE1
;
699 mdst
->ocode1
= (UI
)de_bitbuf_lowlevel_get_bits(&mctx
->bbll
, mdst
->ocode1_nbits
);
701 mdst
->tmp_code
= ((mdst
->first_code
<< mdst
->ocode1_nbits
) | mdst
->ocode1
) & 0xff;
702 fmtutil_get_lzhuf_d_code_and_len(mdst
->tmp_code
, &mdst
->d_code2
, &mdst
->d_len2
);
705 mdst
->ocode2_nbits
= mdst
->d_len2
;
706 if(mctx
->bbll
.nbits_in_bitbuf
< mdst
->ocode2_nbits
) {
707 mdst
->state
= MEDLZST_WAITING_FOR_OCODE2
;
710 mdst
->ocode2
= (UI
)de_bitbuf_lowlevel_get_bits(&mctx
->bbll
, mdst
->ocode2_nbits
);
712 offset_rel
= (mdst
->d_code2
<< 8) | (((mdst
->tmp_code
<< mdst
->ocode2_nbits
) | mdst
->ocode2
) & 0xff);
713 length
= mdst
->d_code1
+ 3;
714 de_lz77buffer_copy_from_hist(mctx
->ringbuf
, mctx
->ringbuf
->curpos
- 1 - offset_rel
, length
);
715 mdst
->state
= MEDLZST_NEUTRAL
;
719 if(!dfctx
->finished_flag
&& !mctx
->errflag
) {
721 // It shouldn't be possible to get here. If we do, it means some input
722 // bytes will be lost.
723 de_dfilter_set_generic_error(dfctx
->c
, mctx
->dres
, mctx
->modname
);
729 static void medium_lz77buf_writebytecb(struct de_lz77buffer
*rb
, u8 n
)
731 struct medium_ctx
*mctx
= (struct medium_ctx
*)rb
->userdata
;
733 if(medium_have_enough_output(mctx
)) {
736 dbuf_writebyte(mctx
->dcmpro
->f
, n
);
737 mctx
->nbytes_written
++;
740 static void mediumlz77_codec_command(struct de_dfilter_ctx
*dfctx
, int cmd
, UI flags
)
742 struct medium_ctx
*mctx
= (struct medium_ctx
*)dfctx
->codec_private
;
744 if(cmd
==DE_DFILTER_COMMAND_FINISH_BLOCK
) {
745 de_bitbuf_lowlevel_empty(&mctx
->bbll
);
746 mctx
->mdst
.state
= MEDLZST_NEUTRAL
;
747 de_lz77buffer_set_curpos(mctx
->ringbuf
, mctx
->ringbuf
->curpos
+ 66);
749 else if(cmd
==DE_DFILTER_COMMAND_RESET_COUNTERS
) {
750 mctx
->nbytes_written
= 0;
752 dfctx
->finished_flag
= 0;
756 static void mediumlz77_codec_destroy(struct de_dfilter_ctx
*dfctx
)
758 struct medium_ctx
*mctx
= (struct medium_ctx
*)dfctx
->codec_private
;
761 de_lz77buffer_destroy(dfctx
->c
, mctx
->ringbuf
);
762 de_free(dfctx
->c
, mctx
);
764 dfctx
->codec_private
= NULL
;
767 // codec_private_params: unused
768 static void dmsmediumlz77_codec(struct de_dfilter_ctx
*dfctx
, void *codec_private_params
)
770 struct medium_ctx
*mctx
= NULL
;
772 mctx
= de_malloc(dfctx
->c
, sizeof(struct medium_ctx
));
774 mctx
->modname
= "dmsmedium_lz77";
775 mctx
->dcmpro
= dfctx
->dcmpro
;
776 mctx
->dres
= dfctx
->dres
;
778 dfctx
->codec_private
= (void*)mctx
;
779 dfctx
->codec_addbuf_fn
= mediumlz77_codec_addbuf
;
780 dfctx
->codec_command_fn
= mediumlz77_codec_command
;
781 dfctx
->codec_destroy_fn
= mediumlz77_codec_destroy
;
783 mctx
->ringbuf
= de_lz77buffer_create(dfctx
->c
, 16*1024);
784 // The set_curpos isn't needed, but could help with debugging. It makes our
785 // internal state the same as most other DMS software.
786 de_lz77buffer_set_curpos(mctx
->ringbuf
, 0x3fbe);
788 mctx
->ringbuf
->userdata
= (void*)mctx
;
789 mctx
->ringbuf
->writebyte_cb
= medium_lz77buf_writebytecb
;
792 ///////////////// "Medium" and "Deep" decompression //////////////
794 // Note: The Medium and Deep decompressors have a different design than Heavy.
795 // Both the codecs are "pushable", which is a good thing for DMS's segmented
797 // It may look more complicated, but ultimately it's cleaner and less hacky.
799 static void do_decompress_track_medium_or_deep(deark
*c
, struct dmsctx
*d
, struct dms_track_info
*tri
,
800 struct de_dfilter_in_params
*dcmpri
, struct de_dfilter_out_params
*dcmpro
,
801 struct de_dfilter_results
*dres
)
803 struct dms_std_cmpr_state
*cst
= NULL
;
804 struct std_saved_state_wrapper
*ssw
;
806 if(tri
->cmpr_type
==DMSCMPR_DEEP
) {
807 ssw
= &d
->saved_deep_cmpr_state
;
810 ssw
= &d
->saved_medium_state
;
813 if(ssw
->saved_state
) {
815 // Reclaim saved decompression state, if any
816 cst
= ssw
->saved_state
;
817 ssw
->saved_state
= NULL
;
819 cst
->dcmpro1
.len_known
= 1;
820 cst
->dcmpro1
.expected_len
= tri
->intermediate_len
;
822 de_dfilter_command(cst
->dfctx_codec1
, DE_DFILTER_COMMAND_RESET_COUNTERS
, 0);
825 destroy_std_cmpr_state(c
, ssw
->saved_state
);
826 ssw
->saved_state
= NULL
;
831 cst
= de_malloc(c
, sizeof(struct dms_std_cmpr_state
));
832 cst
->cmpr_meth
= tri
->cmpr_type
;
833 if(cst
->cmpr_meth
==DMSCMPR_DEEP
) {
834 cst
->cmpr_meth_name
= "deepcmpr";
837 cst
->cmpr_meth_name
= "mediumcmpr";
840 cst
->outf_codec1
= dbuf_create_custom_dbuf(c
, 0, 0);
841 cst
->outf_codec1
->userdata_for_customwrite
= (void*)cst
;
842 cst
->outf_codec1
->customwrite_fn
= my_std1_write_cb
;
844 cst
->dcmpro1
.f
= cst
->outf_codec1
;
845 cst
->dcmpro1
.len_known
= 1;
846 cst
->dcmpro1
.expected_len
= tri
->intermediate_len
;
849 // Reset the "length" of this virtual dbuf.
850 dbuf_truncate(cst
->outf_codec1
, 0);
853 de_dfilter_destroy(cst
->dfctx_rle
);
854 cst
->dfctx_rle
= NULL
;
856 cst
->dfctx_rle
= de_dfilter_create(c
, dmsrle_codec
, NULL
, dcmpro
, dres
);
858 if(!cst
->dfctx_codec1
) {
859 if(cst
->cmpr_meth
==DMSCMPR_DEEP
) {
860 struct de_lh1_params lh1p
;
862 de_zeromem(&lh1p
, sizeof(struct de_lh1_params
));
863 lh1p
.is_dms_deep
= 1;
864 cst
->dfctx_codec1
= de_dfilter_create(c
, dfilter_lh1_codec
,
865 (void*)&lh1p
, &cst
->dcmpro1
, &cst
->dres1
);
868 cst
->dfctx_codec1
= de_dfilter_create(c
, dmsmediumlz77_codec
,
869 NULL
, &cst
->dcmpro1
, &cst
->dres1
);
873 cst
->dfctx_codec1
->input_file_offset
= dcmpri
->pos
;
874 cst
->intermediate_nbytes
= 0;
876 de_dfilter_addslice(cst
->dfctx_codec1
, dcmpri
->f
, dcmpri
->pos
, dcmpri
->len
);
877 de_dfilter_command(cst
->dfctx_codec1
, DE_DFILTER_COMMAND_FINISH_BLOCK
, 0);
878 de_dfilter_finish(cst
->dfctx_rle
);
880 // If cst->dfctx_lzah->dres has the error message we need, copy it to the
882 de_dfilter_transfer_error(c
, cst
->dfctx_codec1
->dres
, dres
);
885 // Note that this saved state may be deleted soon, in dms_decompress_track().
886 ssw
->saved_state
= cst
;
889 destroy_std_cmpr_state(c
, cst
);
893 ///////////////////////////////////
895 static void do_decompress_heavy_lzh_rle(deark
*c
, struct dmsctx
*d
, struct dms_track_info
*tri
,
896 struct de_dfilter_in_params
*dcmpri
, struct de_dfilter_out_params
*dcmpro
,
897 struct de_dfilter_results
*dres
, struct dmslzh_params
*lzhparams
)
899 struct de_dcmpr_two_layer_params tlp
;
901 de_zeromem(&tlp
, sizeof(struct de_dcmpr_two_layer_params
));
902 tlp
.codec1_type1
= dmslzh_codectype1
;
903 tlp
.codec1_private_params
= (void*)lzhparams
;
904 tlp
.codec2
= dmsrle_codec
;
908 tlp
.intermed_expected_len
= tri
->intermediate_len
;
909 tlp
.intermed_len_known
= 1;
910 de_dfilter_decompress_two_layer(c
, &tlp
);
913 static void do_decompress_heavy(deark
*c
, struct dmsctx
*d
, struct dms_track_info
*tri
,
914 struct de_dfilter_in_params
*dcmpri
, struct de_dfilter_out_params
*dcmpro
,
915 struct de_dfilter_results
*dres
)
917 struct dmslzh_params lzhparams
;
919 de_zeromem(&lzhparams
, sizeof(struct dmslzh_params
));
920 lzhparams
.cmpr_type
= tri
->cmpr_type
;
921 lzhparams
.dms_track_flags
= tri
->track_flags
;
923 lzhparams
.heavy_state
= d
->saved_heavy_state
;
924 d
->saved_heavy_state
= NULL
;
927 if(tri
->track_flags
& 0x04) {
928 do_decompress_heavy_lzh_rle(c
, d
, tri
, dcmpri
, dcmpro
, dres
, &lzhparams
);
932 dmslzh_codectype1(c
, dcmpri
, dcmpro
, dres
, (void*)&lzhparams
);
936 d
->saved_heavy_state
= lzhparams
.heavy_state
;
939 destroy_heavy_state(c
, lzhparams
.heavy_state
);
943 static void destroy_saved_dcrmpr_state(deark
*c
, struct dmsctx
*d
)
945 if(d
->saved_medium_state
.saved_state
) {
946 destroy_std_cmpr_state(c
, d
->saved_medium_state
.saved_state
);
947 d
->saved_medium_state
.saved_state
= NULL
;
949 if(d
->saved_deep_cmpr_state
.saved_state
) {
950 destroy_std_cmpr_state(c
, d
->saved_deep_cmpr_state
.saved_state
);
951 d
->saved_deep_cmpr_state
.saved_state
= NULL
;
953 if(d
->saved_heavy_state
) {
954 destroy_heavy_state(c
, d
->saved_heavy_state
);
955 d
->saved_heavy_state
= NULL
;
959 static int dms_decompress_track(deark
*c
, struct dmsctx
*d
, struct dms_track_info
*tri
,
964 struct de_dfilter_in_params dcmpri
;
965 struct de_dfilter_out_params dcmpro
;
966 struct de_dfilter_results dres
;
968 if(outf
->len
!=0) goto done
;
970 if(tri
->dpos
+ tri
->cmpr_len
> c
->infile
->len
) {
971 de_err(c
, "Track goes beyond end of file");
975 de_dfilter_init_objects(c
, &dcmpri
, &dcmpro
, &dres
);
976 dcmpri
.f
= c
->infile
;
977 dcmpri
.pos
= tri
->dpos
;
978 dcmpri
.len
= tri
->cmpr_len
;
980 dcmpro
.len_known
= 1;
981 dcmpro
.expected_len
= tri
->uncmpr_len
;
985 if(tri
->cmpr_type
==DMSCMPR_NONE
) {
986 fmtutil_decompress_uncompressed(c
, &dcmpri
, &dcmpro
, &dres
, 0);
988 else if(tri
->cmpr_type
==DMSCMPR_RLE
) {
989 de_dfilter_decompress_oneshot(c
, dmsrle_codec
, NULL
,
990 &dcmpri
, &dcmpro
, &dres
);
992 else if(tri
->cmpr_type
==DMSCMPR_MEDIUM
) {
993 do_decompress_track_medium_or_deep(c
, d
, tri
, &dcmpri
, &dcmpro
, &dres
);
995 else if(tri
->cmpr_type
==DMSCMPR_DEEP
) {
996 do_decompress_track_medium_or_deep(c
, d
, tri
, &dcmpri
, &dcmpro
, &dres
);
998 else if(tri
->cmpr_type
==DMSCMPR_HEAVY1
|| tri
->cmpr_type
==DMSCMPR_HEAVY2
) {
999 do_decompress_heavy(c
, d
, tri
, &dcmpri
, &dcmpro
, &dres
);
1002 de_err(c
, "[%s] Unsupported compression method: %u (%s)",
1003 tri
->shortname
, tri
->cmpr_type
,
1004 dms_get_cmprtype_name(tri
->cmpr_type
));
1009 de_err(c
, "[%s] Decompression failed: %s", tri
->shortname
,
1010 de_dfilter_get_errmsg(c
, &dres
));
1014 unc_nbytes
= outf
->len
;
1016 dbuf_truncate(outf
, tri
->uncmpr_len
);
1018 if(unc_nbytes
< tri
->uncmpr_len
) {
1019 de_err(c
, "[%s] Expected %"I64_FMT
" decompressed bytes, got %"I64_FMT
,
1020 tri
->shortname
, tri
->uncmpr_len
, unc_nbytes
);
1023 if(unc_nbytes
> tri
->uncmpr_len
) {
1024 de_warn(c
, "[%s] Expected %"I64_FMT
" decompressed bytes, got %"I64_FMT
,
1025 tri
->shortname
, tri
->uncmpr_len
, unc_nbytes
);
1031 if(tri
->is_real
&& !(tri
->track_flags
& 0x1)) {
1032 destroy_saved_dcrmpr_state(c
, d
);
1037 static int dms_checksum_cbfn(struct de_bufferedreadctx
*brctx
, const u8
*buf
,
1040 u32
*cksum
= (u32
*)brctx
->userdata
;
1043 for(i
=0; i
<buf_len
; i
++) {
1044 *cksum
+= (u32
)buf
[i
];
1049 // outf is presumed to be membuf containing one track, and nothing else.
1050 static u32
dms_calc_checksum(deark
*c
, dbuf
*outf
)
1054 dbuf_buffered_read(outf
, 0, outf
->len
, dms_checksum_cbfn
, (void*)&cksum
);
1059 static void get_trackflags_descr(deark
*c
, de_ucstring
*s
, UI tflags1
, UI cmpr
)
1061 UI tflags
= tflags1
;
1063 if(cmpr
==5 || cmpr
==6) {
1065 ucstring_append_flags_item(s
, "w/RLE");
1069 ucstring_append_flags_item(s
, "track has Huffman tree defs");
1074 ucstring_append_flags_item(s
, "persist decompr. state");
1077 if(tflags
>0) ucstring_append_flags_itemf(s
, "0x%02x", tflags
);
1080 // Read track and decompress to outf (which caller supplies as an empty membuf).
1081 // track_idx: the index into d->tracks_by_file_order
1082 // Returns nonzero if successfully decompressed.
1083 static int dms_read_and_decompress_track(deark
*c
, struct dmsctx
*d
,
1084 i64 track_idx
, dbuf
*outf
)
1087 struct dms_track_info
*tri
= NULL
;
1088 de_ucstring
*descr
= NULL
;
1090 int saved_indent_level
;
1092 de_dbg_indent_save(c
, &saved_indent_level
);
1094 tri
= de_malloc(c
, sizeof(struct dms_track_info
));
1095 pos1
= d
->tracks_by_file_order
[track_idx
].file_pos
;
1096 tri
->track_num
= (i64
)d
->tracks_by_file_order
[track_idx
].track_num
;
1097 tri
->is_real
= d
->tracks_by_file_order
[track_idx
].is_real
;
1098 de_snprintf(tri
->shortname
, sizeof(tri
->shortname
), "%strack %d",
1099 (tri
->is_real
?"":"extra "), (int)tri
->track_num
);
1101 de_dbg(c
, "%s at %"I64_FMT
, tri
->shortname
, pos1
);
1102 de_dbg_indent(c
, 1);
1104 pos
+= 2; // signature, already checked
1105 pos
+= 2; // reported track number, already read
1106 pos
+= 2; // Unknown field
1107 tri
->cmpr_len
= de_getu16be_p(&pos
);
1108 de_dbg(c
, "cmpr len: %"I64_FMT
, tri
->cmpr_len
);
1109 tri
->intermediate_len
= de_getu16be_p(&pos
);
1110 de_dbg(c
, "intermediate len: %"I64_FMT
, tri
->intermediate_len
);
1111 tri
->uncmpr_len
= de_getu16be_p(&pos
);
1112 de_dbg(c
, "uncmpr len: %"I64_FMT
, tri
->uncmpr_len
);
1114 tri
->track_flags
= (UI
)de_getbyte_p(&pos
);
1115 tri
->cmpr_type
= (UI
)de_getbyte_p(&pos
);
1117 descr
= ucstring_create(c
);
1118 get_trackflags_descr(c
, descr
, tri
->track_flags
, tri
->cmpr_type
);
1119 de_dbg(c
, "track flags: 0x%02x (%s)", tri
->track_flags
, ucstring_getpsz_d(descr
));
1121 de_dbg(c
, "track cmpr type: %u (%s)", tri
->cmpr_type
, dms_get_cmprtype_name(tri
->cmpr_type
));
1122 tri
->cksum_reported
= (u32
)de_getu16be_p(&pos
);
1123 de_dbg(c
, "checksum (reported): 0x%04x", (UI
)tri
->cksum_reported
);
1124 tri
->crc_cmprdata_reported
= (u32
)de_getu16be_p(&pos
);
1125 de_dbg(c
, "crc of cmpr data (reported): 0x%04x", (UI
)tri
->crc_cmprdata_reported
);
1126 tri
->crc_header_reported
= (u32
)de_getu16be_p(&pos
);
1127 de_dbg(c
, "crc of header (reported): 0x%04x", (UI
)tri
->crc_header_reported
);
1129 tri
->dpos
= pos1
+ DMS_TRACK_HDR_LEN
;
1130 de_dbg(c
, "cmpr data at %"I64_FMT
, tri
->dpos
);
1131 de_dbg_indent(c
, 1);
1132 if(!dms_decompress_track(c
, d
, tri
, outf
)) goto done
;
1133 de_dbg_indent(c
, -1);
1135 tri
->cksum_calc
= dms_calc_checksum(c
, outf
);
1136 de_dbg(c
, "checksum (calculated): 0x%04x", (UI
)tri
->cksum_calc
);
1137 if(tri
->cksum_calc
!= tri
->cksum_reported
) {
1138 de_err(c
, "[%s] Checksum check failed", tri
->shortname
);
1144 ucstring_destroy(descr
);
1146 de_dbg_indent_restore(c
, saved_indent_level
);
1150 static void write_extra_track(deark
*c
, struct dmsctx
*d
, i64 track_idx
, dbuf
*trackbuf
)
1153 dbuf
*outf_extra
= NULL
;
1155 de_snprintf(ext
, sizeof(ext
), "extratrack%d.bin",
1156 (int)d
->tracks_by_file_order
[track_idx
].track_num
);
1157 outf_extra
= dbuf_create_output_file(c
, ext
, NULL
, DE_CREATEFLAG_IS_AUX
);
1158 dbuf_copy(trackbuf
, 0, trackbuf
->len
, outf_extra
);
1159 dbuf_close(outf_extra
);
1162 // Write out all the tracks, whether real or extra.
1163 static void do_dms_main(deark
*c
, struct dmsctx
*d
)
1166 int real_track_failure_flag
= 0;
1168 dbuf
*trackbuf
= NULL
;
1170 trackbuf
= dbuf_create_membuf(c
, 11264, 0);
1171 outf
= dbuf_create_output_file(c
, "adf", NULL
, 0);
1173 for(i
=0; i
<d
->num_tracks_in_file
; i
++) {
1176 if(real_track_failure_flag
&& d
->tracks_by_file_order
[i
].is_real
) {
1180 dbuf_truncate(trackbuf
, 0);
1182 ret_dcmpr
= dms_read_and_decompress_track(c
, d
, i
, trackbuf
);
1185 if(d
->tracks_by_file_order
[i
].is_real
) {
1186 real_track_failure_flag
= 1;
1191 if(d
->tracks_by_file_order
[i
].is_real
) {
1192 dbuf_copy(trackbuf
, 0, trackbuf
->len
, outf
);
1195 write_extra_track(c
, d
, i
, trackbuf
);
1200 dbuf_close(trackbuf
);
1203 static int do_dms_header(deark
*c
, struct dmsctx
*d
, i64 pos1
)
1207 struct de_timestamp cr_time
;
1210 de_dbg(c
, "header at %"I64_FMT
, pos1
);
1211 de_dbg_indent(c
, 1);
1213 // [0..3] = signature
1215 d
->info_bits
= (UI
)de_getu32be_p(&pos
); // [8..11] = info bits
1216 de_dbg(c
, "infobits: 0x%08x", d
->info_bits
);
1218 de_zeromem(&cr_time
, sizeof(struct de_timestamp
));
1219 read_unix_timestamp(c
, pos
, &cr_time
, "creation time");
1222 d
->first_track
= de_getu16be_p(&pos
); // [16..17] = firsttrack
1223 de_dbg(c
, "first track: %d", (int)d
->first_track
);
1224 if(d
->first_track
>= DMS_MAX_TRACKS
) goto done
;
1225 if(d
->first_track
!= 0) {
1226 de_info(c
, "Note: First track is #%d, not #0. This may be a partial disk image.",
1227 (int)d
->first_track
);
1230 d
->last_track
= de_getu16be_p(&pos
); // [18..19] = lasttrack
1231 de_dbg(c
, "last track: %u", (int)d
->last_track
);
1232 if(d
->last_track
< d
->first_track
) goto done
;
1233 if(d
->last_track
>= DMS_MAX_TRACKS
) goto done
;
1235 n
= de_getu32be_p(&pos
); // [20..23] = packed len
1236 de_dbg(c
, "compressed len: %"I64_FMT
, n
);
1238 n
= de_getu32be_p(&pos
); // [24..27] = unpacked len
1239 de_dbg(c
, "decompressed len: %"I64_FMT
, n
);
1241 // [46..47] = creating software version
1243 n
= de_getu16be_p(&pos
); // [50..51] = disk type
1244 de_dbg(c
, "disk type: %u", (UI
)n
);
1246 d
->cmpr_type
= (UI
)de_getu16be_p(&pos
); // [52..53] = compression mode
1247 de_dbg(c
, "compression type: %u (%s)", d
->cmpr_type
,
1248 dms_get_cmprtype_name(d
->cmpr_type
));
1250 n
= de_getu16be_p(&pos
); // [54..55] = crc
1251 de_dbg(c
, "crc (reported): 0x%04x", (UI
)n
);
1256 de_dbg_indent(c
, -1);
1260 static int dms_scan_file(deark
*c
, struct dmsctx
*d
, i64 pos1
)
1264 u32 next_real_tracknum_expected
;
1267 de_dbg(c
, "scanning file");
1268 de_dbg_indent(c
, 1);
1270 d
->num_tracks_in_file
= 0;
1273 i64 track_num_reported
;
1279 if(pos
+DMS_TRACK_HDR_LEN
> c
->infile
->len
) break;
1281 if(dbuf_memcmp(c
->infile
, pos
, "TR", 2)) {
1282 de_dbg(c
, "[track not found at %"I64_FMT
"; assuming disk image ends here]", pos
);
1285 if(d
->num_tracks_in_file
>= DMS_MAX_TRACKS
) {
1286 de_err(c
, "Too many tracks in file");
1290 track_num_reported
= de_getu16be(pos
+2);
1291 cmpr_len
= de_getu16be(pos
+6);
1292 uncmpr_len
= de_getu16be(pos
+10);
1293 track_flags
= de_getbyte(pos
+12);
1294 cmpr_type
= de_getbyte(pos
+13);
1296 de_dbg(c
, "track[%d] at %"I64_FMT
", #%d, len=%"I64_FMT
"/%"I64_FMT
", cmpr=%u, flags=0x%02x",
1297 (int)d
->num_tracks_in_file
, pos
, (int)track_num_reported
, cmpr_len
, uncmpr_len
,
1298 (UI
)cmpr_type
, (UI
)track_flags
);
1300 d
->tracks_by_file_order
[d
->num_tracks_in_file
].file_pos
= pos
;
1301 d
->tracks_by_file_order
[d
->num_tracks_in_file
].track_num
= (u32
)track_num_reported
;
1303 if(track_num_reported
>=d
->first_track
&& track_num_reported
<=d
->last_track
) {
1304 d
->tracks_by_track_num
[track_num_reported
].order_in_file
= (u32
)d
->num_tracks_in_file
;
1305 d
->tracks_by_track_num
[track_num_reported
].in_use
= 1;
1308 d
->num_tracks_in_file
++;
1309 pos
+= DMS_TRACK_HDR_LEN
+ cmpr_len
;
1312 // Make sure all expected tracks are present, and mark the "real" tracks in
1313 // tracks_by_file_order[].
1314 // One reason for doing it this way is that there may be two tracks numbered 0,
1315 // with the second one being the real one.
1316 for(i
=d
->first_track
; i
<=d
->last_track
; i
++) {
1317 if(!d
->tracks_by_track_num
[i
].in_use
) {
1318 // TODO: Maybe we should write a track of all zeroes instead (but how many zeroes?)
1319 de_err(c
, "Could not find track #%d", (int)i
);
1323 d
->tracks_by_file_order
[d
->tracks_by_track_num
[i
].order_in_file
].is_real
= 1;
1327 next_real_tracknum_expected
= (u32
)d
->first_track
;
1328 for(i
=0; i
<d
->num_tracks_in_file
; i
++) {
1329 if(d
->tracks_by_file_order
[i
].is_real
) {
1330 // I'm not going to bother supporting out-of-order tracks, at least until
1331 // I learn that such files exist.
1332 if(d
->tracks_by_file_order
[i
].track_num
!= next_real_tracknum_expected
) {
1333 de_err(c
, "Track numbers not in order. Not supported.");
1336 next_real_tracknum_expected
= d
->tracks_by_file_order
[i
].track_num
+ 1;
1342 de_dbg_indent(c
, -1);
1346 static void de_run_amiga_dms(deark
*c
, de_module_params
*mparams
)
1348 struct dmsctx
*d
= NULL
;
1350 d
= de_malloc(c
, sizeof(struct dmsctx
));
1351 if(!do_dms_header(c
, d
, 0)) goto done
;
1352 if(!dms_scan_file(c
, d
, DMS_FILE_HDR_LEN
)) goto done
;
1357 destroy_saved_dcrmpr_state(c
, d
);
1362 static int de_identify_amiga_dms(deark
*c
)
1366 if(dbuf_memcmp(c
->infile
, 0, "DMS!", 4)) return 0;
1367 dcmpr_size
= de_getu32be(24);
1368 if(dcmpr_size
==901120) return 100;
1372 void de_module_amiga_dms(deark
*c
, struct deark_module_info
*mi
)
1374 mi
->id
= "amiga_dms";
1375 mi
->desc
= "Amiga DMS disk image";
1376 mi
->run_fn
= de_run_amiga_dms
;
1377 mi
->identify_fn
= de_identify_amiga_dms
;