1 // This file is part of Deark.
2 // Copyright (C) 2023 Jason Summers
3 // See the file COPYING for terms of use.
5 // ColoRIX .SCI, .SCR, etc.
7 #include <deark-private.h>
8 #include <deark-fmtutil.h>
9 DE_DECLARE_MODULE(de_module_colorix
);
11 #define RIX_MIN_FILE_SIZE 36
12 #define RIX_UNC_SCR_FILE_SIZE 112016
13 #define RIX_MIN_OLD_SEGMENT_DLEN 100
16 #define RIXFMT_OLD_U 1
17 #define RIXFMT_OLD_C 2
25 u8 has_extension_block
;
28 i64 known_segment_size
;
35 RIX_RLESTATE_NEUTRAL
= 0,
36 RIX_RLESTATE_WAITING_FOR_REPEAT_COUNT
39 struct de_rixdecomp_params
{
40 i64 known_segment_size
; // Set either this or rowspan
45 struct rixdecomp_ctx
{
47 struct de_rixdecomp_params
*private_params
;
48 struct de_dfilter_in_params
*dcmpri
;
49 struct de_dfilter_out_params
*dcmpro
;
50 struct de_dfilter_results
*dres
;
56 i64 first_cmpr_segment_pos
;
57 struct fmtutil_huffman_decoder
*ht
;
58 struct de_bitreader bitrd
;
63 enum rle_state_type rle_state
;
65 u8 rle_byte_to_repeat
;
70 static void rixdecomp_interpret_nodetable_item(struct rixdecomp_ctx
*rhctx
,
71 i64 itempos
, u64 currcode
, UI currcode_nbits
)
75 if(rhctx
->errflag
) return;
76 if((itempos
< rhctx
->nodetable_dpos
) ||
77 (itempos
+2 > rhctx
->nodetable_dpos
+rhctx
->nodetable_dlen
))
82 if(currcode_nbits
>=FMTUTIL_HUFFMAN_MAX_CODE_LENGTH
) return;
84 dval
= (u16
)dbuf_getu16le(rhctx
->dcmpri
->f
, itempos
);
85 de_dbg2(rhctx
->c
, "item@%"I64_FMT
": 0x%04x", itempos
, (UI
)dval
);
87 if(dval
>=2 && dval
<0x1000 && (dval
%2==0)) { // a "pointer" item
88 // The very next item is the start of the "1" subtree.
89 rixdecomp_interpret_nodetable_item(rhctx
, itempos
+2, ((currcode
<<1) | 1), currcode_nbits
+1);
90 if(rhctx
->errflag
) goto done
;
92 // The pointer item tells how many bytes are between the pointer item
93 // and the start of the "0" subtree.
94 rixdecomp_interpret_nodetable_item(rhctx
, itempos
+2+(i64
)dval
, currcode
<<1, currcode_nbits
+1);
95 if(rhctx
->errflag
) goto done
;
97 else if(dval
>=0x1000 && dval
<=0x10ff) { // a leaf item
98 fmtutil_huffman_valtype adj_value
;
100 adj_value
= (fmtutil_huffman_valtype
)(dval
-0x1000);
101 if(rhctx
->c
->debug_level
>=3) {
102 de_dbg3(rhctx
->c
, "code: \"%s\" = %d",
103 de_print_base2_fixed(rhctx
->tmpbuf
, sizeof(rhctx
->tmpbuf
), currcode
, currcode_nbits
),
106 if(!fmtutil_huffman_add_code(rhctx
->c
, rhctx
->ht
->bk
, currcode
, currcode_nbits
, adj_value
)) {
119 // Read node table, construct Huffman codebook.
120 // Uses rhctx->nodetable_pos.
121 // Sets rhctx->first_cmpr_segment_pos.
122 static int rixdecomp_read_nodetable(deark
*c
, struct rixdecomp_ctx
*rhctx
)
126 i64 nodetable_dlen_raw
;
128 int saved_indent_level
;
130 de_dbg_indent_save(c
, &saved_indent_level
);
131 pos1
= rhctx
->nodetable_pos
;
133 de_dbg(c
, "huffman node table segment at %"I64_FMT
, pos1
);
135 nodetable_dlen_raw
= dbuf_getu16le_p(rhctx
->dcmpri
->f
, &pos
);
136 rhctx
->nodetable_dlen
= nodetable_dlen_raw
*2;
137 de_dbg(c
, "node table dlen: %"I64_FMT
, rhctx
->nodetable_dlen
);
138 rhctx
->nodetable_dpos
= pos
;
139 rhctx
->first_cmpr_segment_pos
= rhctx
->nodetable_dpos
+ rhctx
->nodetable_dlen
;
141 rhctx
->ht
= fmtutil_huffman_create_decoder(c
, 256, 256);
143 // We expect a maximum of 513: 256 leaf entries, + 255 pointer entries,
144 // + up to 2 extra zero-valued entries at the end.
145 if(nodetable_dlen_raw
< 1) {
146 de_dfilter_set_generic_error(c
, rhctx
->dres
, rhctx
->modname
);
150 de_dbg2(c
, "node table nodes at %"I64_FMT
, rhctx
->nodetable_dpos
);
152 rixdecomp_interpret_nodetable_item(rhctx
, rhctx
->nodetable_dpos
, 0, 0);
153 de_dbg_indent(c
, -1);
155 if(c
->debug_level
>=4) {
156 fmtutil_huffman_dump(c
, rhctx
->ht
);
161 de_dbg_indent_restore(c
, saved_indent_level
);
165 static void rixdecomp_process_rle_byte(deark
*c
, struct rixdecomp_ctx
*rhctx
, u8 n
)
171 if(rhctx
->nbytes_written
>= rhctx
->dcmpro
->expected_len
) {
175 switch(rhctx
->rle_state
) {
176 case RIX_RLESTATE_NEUTRAL
:
177 if(n
==0x00 || n
==0xff) {
178 rhctx
->rle_state
= RIX_RLESTATE_WAITING_FOR_REPEAT_COUNT
;
179 rhctx
->rle_byte_to_repeat
= n
;
187 case RIX_RLESTATE_WAITING_FOR_REPEAT_COUNT
:
189 val
= rhctx
->rle_byte_to_repeat
;
190 rhctx
->rle_state
= RIX_RLESTATE_NEUTRAL
;
194 for(k
= 0; k
<count
; k
++) {
195 if(rhctx
->use_xor_filter
) {
196 rhctx
->rle_curr_color
^= val
;
199 rhctx
->rle_curr_color
= val
;
201 dbuf_writebyte(rhctx
->dcmpro
->f
, rhctx
->rle_curr_color
);
203 rhctx
->nbytes_written
+= count
;
208 static void rixdecomp_process_codes_segment(deark
*c
, struct rixdecomp_ctx
*rhctx
, i64 dpos1
, i64 dlen
)
210 de_zeromem(&rhctx
->bitrd
, sizeof(struct de_bitreader
));
211 rhctx
->bitrd
.bbll
.is_lsb
= 0;
212 rhctx
->bitrd
.f
= rhctx
->dcmpri
->f
;
213 rhctx
->bitrd
.curpos
= dpos1
;
214 rhctx
->bitrd
.endpos
= dpos1
+ dlen
;
215 de_bitbuf_lowlevel_empty(&rhctx
->bitrd
.bbll
);
216 fmtutil_huffman_reset_cursor(rhctx
->ht
->cursor
);
218 rhctx
->rle_state
= RIX_RLESTATE_NEUTRAL
;
219 rhctx
->rle_curr_color
= 0x00;
223 fmtutil_huffman_valtype val
= 0;
225 ret
= fmtutil_huffman_read_next_value(rhctx
->ht
->bk
, &rhctx
->bitrd
, &val
, NULL
);
226 if(!ret
|| val
<0 || val
>255) {
227 // We don't always know exactly where the data stops, so don't report
232 rixdecomp_process_rle_byte(c
, rhctx
, (u8
)val
);
239 static void rixdecomp_codectype1(deark
*c
, struct de_dfilter_in_params
*dcmpri
,
240 struct de_dfilter_out_params
*dcmpro
, struct de_dfilter_results
*dres
,
241 void *codec_private_params
)
243 struct rixdecomp_ctx
*rhctx
= NULL
;
248 int saved_indent_level
;
250 de_dbg_indent_save(c
, &saved_indent_level
);
251 rhctx
= de_malloc(c
, sizeof(struct rixdecomp_ctx
));
253 rhctx
->private_params
= (struct de_rixdecomp_params
*)codec_private_params
;
254 rhctx
->modname
= "rixdecomp";
255 rhctx
->dcmpri
= dcmpri
;
256 rhctx
->dcmpro
= dcmpro
;
258 endpos
= dcmpri
->pos
+ rhctx
->dcmpri
->len
;
259 rhctx
->use_xor_filter
= (rhctx
->private_params
->imgtype
==0);
261 // Currently, we have some restrictions on the output dbuf.
262 if(dcmpro
->f
->btype
!=DBUF_TYPE_MEMBUF
|| dcmpro
->f
->len
!=0 ||
268 if(rhctx
->private_params
->rowspan
<1) goto done
;
270 rhctx
->nodetable_pos
= dcmpri
->pos
;
271 if(!rixdecomp_read_nodetable(c
, rhctx
)) goto done
;
274 pos
= rhctx
->first_cmpr_segment_pos
;
283 if(pos
+2 >= endpos
) break;
284 rhctx
->nbytes_written
= rhctx
->dcmpro
->f
->len
;
285 if(rhctx
->nbytes_written
>= rhctx
->dcmpro
->expected_len
) {
290 seg_dlen
= dbuf_getu16le_p(dcmpri
->f
, &pos
);
291 if(seg_dlen
==0) break;
294 de_dbg(c
, "compressed segment at %"I64_FMT
", dpos=%"I64_FMT
", dlen=%"I64_FMT
,
295 seg_pos
, seg_dpos
, seg_dlen
);
298 saved_len
= rhctx
->dcmpro
->f
->len
;
299 dbuf_enable_wbuffer(rhctx
->dcmpro
->f
);
300 rixdecomp_process_codes_segment(c
, rhctx
, seg_dpos
, seg_dlen
);
301 dbuf_disable_wbuffer(rhctx
->dcmpro
->f
);
303 seg_dcmpr_len
= rhctx
->dcmpro
->f
->len
- saved_len
;
304 de_dbg(c
, "decompressed size: %"I64_FMT
, seg_dcmpr_len
);
306 if(seg_dcmpr_len
< rhctx
->private_params
->rowspan
) {
307 de_dfilter_set_generic_error(c
, dres
, rhctx
->modname
);
311 de_dbg(c
, "number of rows: %"I64_FMT
, (i64
)(seg_dcmpr_len
/rhctx
->private_params
->rowspan
));
313 if(rhctx
->private_params
->known_segment_size
) {
314 rhctx
->nbytes_written
= (seg_count
+1) * rhctx
->private_params
->known_segment_size
;
315 dbuf_truncate(rhctx
->dcmpro
->f
, rhctx
->nbytes_written
);
317 else if(rhctx
->dcmpro
->f
->len
< rhctx
->dcmpro
->expected_len
) {
318 // For non-final segments, there is a potential problem.
319 // For a compressed segment, we know neither the (bit-exact) size of the
320 // compressed data, nor the size of the decompressed data. The padding
321 // bits in the final byte can be misinterpreted as compressed data, so
322 // we may have mistakenly decompressed them into garbage pixels that
323 // will mess up the rest of the image.
324 // It's possible that there is a formula that would tell us the size
325 // of the decompressed data. But I don't know what it is.
326 // Anyway, this is a quick and dirty attempt to work around the problem
327 // by detecting and deleting such garbage pixels. It's not foolproof, and
328 // better heuristics are possible.
329 num_extra_bytes
= rhctx
->dcmpro
->f
->len
% rhctx
->private_params
->rowspan
;
330 if(num_extra_bytes
>0) {
331 de_dbg(c
, "[ignoring %"I64_FMT
" bytes -- assuming garbage caused by padding bits]",
333 rhctx
->nbytes_written
= rhctx
->dcmpro
->f
->len
- num_extra_bytes
;
334 dbuf_truncate(rhctx
->dcmpro
->f
, rhctx
->nbytes_written
);
340 de_dbg_indent(c
, -1);
347 if(!ok
|| dres
->errcode
) {
348 de_dfilter_set_generic_error(c
, dres
, rhctx
->modname
);
351 fmtutil_huffman_destroy_decoder(c
, rhctx
->ht
);
354 de_dbg_indent_restore(c
, saved_indent_level
);
357 static int do_colorix_decompress(deark
*c
, struct colorix_ctx
*d
, i64 pos1
,
361 struct de_dfilter_in_params dcmpri
;
362 struct de_dfilter_out_params dcmpro
;
363 struct de_dfilter_results dres
;
364 struct de_rixdecomp_params params
;
366 de_zeromem(¶ms
, sizeof(struct de_rixdecomp_params
));
367 params
.known_segment_size
= d
->known_segment_size
;
368 params
.rowspan
= d
->rowspan
;
369 params
.imgtype
= d
->imgtype
;
371 de_dfilter_init_objects(c
, &dcmpri
, &dcmpro
, &dres
);
372 dcmpri
.f
= c
->infile
;
374 dcmpri
.len
= c
->infile
->len
- pos1
;
375 dcmpro
.f
= unc_pixels
;
376 dcmpro
.expected_len
= d
->unc_image_size
;
377 dcmpro
.len_known
= 1;
379 rixdecomp_codectype1(c
, &dcmpri
, &dcmpro
, &dres
, (void*)¶ms
);
380 dbuf_flush(dcmpro
.f
);
383 de_err(c
, "Decompression failed: %s", de_dfilter_get_errmsg(c
, &dres
));
392 static void do_colorix_image_RIX3(deark
*c
, struct colorix_ctx
*d
, i64 pos1
)
394 de_bitmap
*img
= NULL
;
395 dbuf
*unc_pixels
= 0;
396 dbuf
*final_pixels_dbuf
; // copy of pointer; do not free
397 i64 final_pixels_pos
;
398 int saved_indent_level
;
400 de_dbg_indent_save(c
, &saved_indent_level
);
401 de_dbg(c
, "image at %"I64_FMT
, pos1
);
403 if(!de_good_image_dimensions(c
, d
->width
, d
->height
)) goto done
;
406 d
->rowspan
= de_pad_to_n(d
->width
, 8) / 2;
409 d
->rowspan
= d
->width
;
411 if(d
->rowspan
<1) goto done
;
413 d
->unc_image_size
= d
->rowspan
* d
->height
;
414 img
= de_bitmap_create(c
, d
->width
, d
->height
, 3);
416 if(d
->is_compressed
) {
417 unc_pixels
= dbuf_create_membuf(c
, d
->unc_image_size
+256, 0);
418 if(!do_colorix_decompress(c
, d
, pos1
, unc_pixels
)) {
421 final_pixels_dbuf
= unc_pixels
;
422 final_pixels_pos
= 0;
425 final_pixels_dbuf
= c
->infile
;
426 final_pixels_pos
= pos1
;
430 de_convert_image_paletted_planar(final_pixels_dbuf
, final_pixels_pos
, 4,
431 d
->rowspan
, d
->rowspan
/4, d
->pal
, img
, 0x2);
434 de_convert_image_paletted(final_pixels_dbuf
, final_pixels_pos
, 8,
435 d
->rowspan
, d
->pal
, img
, 0);
438 de_bitmap_write_to_file(img
, NULL
, DE_CREATEFLAG_OPT_IMAGE
);
441 de_bitmap_destroy(img
);
442 dbuf_close(unc_pixels
);
443 de_dbg_indent_restore(c
, saved_indent_level
);
446 // Sets d->pal_nbytes
447 static void read_colorix_palette(deark
*c
, struct colorix_ctx
*d
, i64 pos1
)
449 if(d
->paltype
==0xab) {
450 de_read_simple_palette(c
, c
->infile
, pos1
, 16, 3, d
->pal
, 16, DE_RDPALTYPE_VGA18BIT
, 0);
453 else { // assume 0xaf
454 de_read_simple_palette(c
, c
->infile
, pos1
, 256, 3, d
->pal
, 256, DE_RDPALTYPE_VGA18BIT
, 0);
459 static void declare_colorix_fmt(deark
*c
, struct colorix_ctx
*d
)
461 de_declare_fmtf(c
, "ColoRIX - %s, %scompressed",
462 (d
->fmtver
==RIXFMT_RIX3
? "new" : "old"),
463 (d
->is_compressed
? "" : "un"));
466 static void do_colorix_RIX3(deark
*c
, struct colorix_ctx
*d
)
471 d
->width
= de_getu16le_p(&pos
);
472 d
->height
= de_getu16le_p(&pos
);
473 de_dbg_dimensions(c
, d
->width
, d
->height
);
475 d
->paltype
= de_getbyte_p(&pos
);
476 de_dbg(c
, "palette type: 0x%02x", (UI
)d
->paltype
);
477 if(d
->paltype
!=0xab && d
->paltype
!=0xaf) {
478 de_err(c
, "Unsupported palette type: 0x%02x", (UI
)d
->paltype
);
482 d
->stgtype
= de_getbyte_p(&pos
);
483 de_dbg(c
, "storage type: 0x%02x", (UI
)d
->stgtype
);
485 if(d
->stgtype
& 0x80) d
->is_compressed
= 1;
486 if(d
->stgtype
& 0x40) d
->has_extension_block
= 1;
487 if(d
->stgtype
& 0x20) d
->is_encrypted
= 1;
488 d
->imgtype
= d
->stgtype
& 0x0f; // I guess?
490 de_dbg(c
, "image type: %u", d
->imgtype
);
491 de_dbg_indent(c
, -1);
492 declare_colorix_fmt(c
, d
);
494 if(d
->is_encrypted
) {
495 de_err(c
, "Encrypted files not supported");
499 if(d
->has_extension_block
) {
502 e_len
= de_getu16le_p(&pos
);
503 de_dbg(c
, "extension block: dpos=%"I64_FMT
", dlen=%"I64_FMT
, pos
, e_len
);
504 // I've found no files with extension blocks, so no reason to parse them.
508 read_colorix_palette(c
, d
, pos
);
509 pos
+= d
->pal_nbytes
;
511 if(d
->imgtype
==0 || d
->imgtype
==4) {
515 de_err(c
, "Unsupported image type: 0x%02x", (UI
)d
->stgtype
);
519 do_colorix_image_RIX3(c
, d
, pos
);
524 static void acquire_palette_ega64idx(deark
*c
, struct colorix_ctx
*d
, i64 pos1
)
530 for(k
=0; k
<16; k
++) {
533 index
= (int)de_getbyte_p(&pos
);
535 d
->pal
[k
] = de_get_std_palette_entry(DE_PALID_EGA64
, 0, index
);
536 de_snprintf(tmps
, sizeof(tmps
), "%2d ", index
);
537 de_dbg_pal_entry2(c
, k
, d
->pal
[k
], tmps
, NULL
, NULL
);
541 static void do_colorix_old_SCR(deark
*c
, struct colorix_ctx
*d
)
543 de_bitmap
*img
= NULL
;
545 dbuf
*unc_pixels
= NULL
;
547 dbuf
*final_pixels_dbuf
; // copy of pointer; do not free
548 i64 final_pixels_pos
;
554 d
->unc_image_size
= (d
->width
*d
->height
)/2;
555 planespan
= d
->unc_image_size
/4;
556 d
->known_segment_size
= planespan
;
557 d
->rowspan
= d
->width
/8;
558 declare_colorix_fmt(c
, d
);
560 acquire_palette_ega64idx(c
, d
, 0);
562 if(d
->is_compressed
) {
563 unc_pixels
= dbuf_create_membuf(c
, d
->unc_image_size
+256, 0);
564 if(!do_colorix_decompress(c
, d
, 16, unc_pixels
)) goto done
;
565 final_pixels_dbuf
= unc_pixels
;
566 final_pixels_pos
= 0;
569 final_pixels_dbuf
= c
->infile
;
570 final_pixels_pos
= 16;
573 // TODO?: Improve de_convert_image_paletted_planar to support this plane order.
574 tmpbuf
= dbuf_create_membuf(c
, d
->unc_image_size
, 0);
575 dbuf_copy(final_pixels_dbuf
, final_pixels_pos
+planespan
*0, planespan
, tmpbuf
);
576 dbuf_copy(final_pixels_dbuf
, final_pixels_pos
+planespan
*2, planespan
, tmpbuf
);
577 dbuf_copy(final_pixels_dbuf
, final_pixels_pos
+planespan
*1, planespan
, tmpbuf
);
578 dbuf_copy(final_pixels_dbuf
, final_pixels_pos
+planespan
*3, planespan
, tmpbuf
);
580 img
= de_bitmap_create(c
, d
->width
, d
->height
, 3);
581 de_convert_image_paletted_planar(tmpbuf
, 0, 4,
582 d
->rowspan
, planespan
, d
->pal
, img
, 2);
584 fi
= de_finfo_create(c
);
585 fi
->density
.code
= DE_DENSITY_UNK_UNITS
;
586 fi
->density
.xdens
= 480.0;
587 fi
->density
.ydens
= (double)d
->height
;
588 de_bitmap_write_to_file_finfo(img
, fi
, DE_CREATEFLAG_OPT_IMAGE
);
591 de_bitmap_destroy(img
);
592 dbuf_close(unc_pixels
);
594 de_finfo_destroy(c
, fi
);
597 static int is_RIX3(dbuf
*f
)
599 if(!dbuf_memcmp(f
, 0, (const void*)"RIX3", 4)) {
605 // It's a pain to detect old compressed format, but I guess it's worth
607 static int looks_like_compressed_data(dbuf
*f
, i64 pos1
)
610 i64 first_image_seg_pos
;
613 i64 num_codebook_items
;
614 i64 num_items_to_check
;
616 // Validate the codebook size, and some of the items
617 num_codebook_items
= dbuf_getu16le_p(f
, &pos
);
618 if(num_codebook_items
<3 || num_codebook_items
>513) goto done
;
619 first_image_seg_pos
= pos
+ num_codebook_items
*2;
620 if(first_image_seg_pos
> f
->len
) goto done
;
622 num_items_to_check
= de_min_int(num_codebook_items
-2, 16);
623 for(i
=0; i
<num_items_to_check
; i
++) {
626 item
= (UI
)dbuf_getu16le_p(f
, &pos
);
628 if((item
&0x1)!=0) goto done
;
629 if(pos
+item
>= first_image_seg_pos
) goto done
;
631 else if(item
<=0x10ff) {
639 // Validate the image segment sizes
640 pos
= first_image_seg_pos
;
644 if(pos
+3 > f
->len
) goto done
;
645 seg_len
= dbuf_getu16le_p(f
, &pos
);
646 if(seg_len
<RIX_MIN_OLD_SEGMENT_DLEN
) goto done
;
659 // Returns RIXFMT_OLD_U, RIXFMT_OLD_C, or 0.
660 static int detect_old_fmt(dbuf
*f
, u8 strict
)
666 if(f
->len
<RIX_MIN_FILE_SIZE
) return 0;
669 dbuf_read(f
, buf
, 0, 16);
670 for(i
=0; i
<16; i
++) {
672 if((buf
[i
] & 0x80)!=0) {
677 if(f
->len
!=RIX_UNC_SCR_FILE_SIZE
) return 0;
681 if(buf
[i
]>0x3f) return 0;
683 if(!cmpr_flag
) return RIXFMT_OLD_U
;
684 if(!strict
) return RIXFMT_OLD_C
;
686 if(looks_like_compressed_data(f
, 16)) {
693 static void de_run_colorix(deark
*c
, de_module_params
*mparams
)
695 struct colorix_ctx
*d
= NULL
;
697 d
= de_malloc(c
, sizeof(struct colorix_ctx
));
699 if(is_RIX3(c
->infile
)) {
700 d
->fmtver
= RIXFMT_RIX3
;
703 d
->fmtver
= detect_old_fmt(c
->infile
, 0);
704 if(d
->fmtver
==RIXFMT_OLD_C
) {
705 d
->is_compressed
= 1;
709 if(d
->fmtver
==RIXFMT_RIX3
) {
710 do_colorix_RIX3(c
, d
);
712 else if(d
->fmtver
==RIXFMT_OLD_U
|| d
->fmtver
==RIXFMT_OLD_C
) {
713 do_colorix_old_SCR(c
, d
);
716 de_err(c
, "Unknown or unsupported RIX format");
724 static int de_identify_colorix(deark
*c
)
726 if(c
->infile
->len
< RIX_MIN_FILE_SIZE
) return 0;
728 if(is_RIX3(c
->infile
)) {
732 if(c
->detection_data
->best_confidence_so_far
>= 55) return 0;
734 if(de_input_file_has_ext(c
, "scr")) {
737 fmt
= detect_old_fmt(c
->infile
, 1);
738 if(fmt
==RIXFMT_OLD_U
) {
741 else if(fmt
==RIXFMT_OLD_C
) {
745 // TODO: There is supposedly also an old ".SCP" format, 640x480x16.
746 // But I can't find any samples.
751 void de_module_colorix(deark
*c
, struct deark_module_info
*mi
)
754 mi
->desc
= "ColoRIX";
755 mi
->run_fn
= de_run_colorix
;
756 mi
->identify_fn
= de_identify_colorix
;