1 // This file is part of Deark.
2 // Copyright (C) 2016 Jason Summers
3 // See the file COPYING for terms of use.
7 #include <deark-config.h>
8 #include <deark-private.h>
9 DE_DECLARE_MODULE(de_module_bsave
);
11 #define BSAVE_HDRSIZE 7
13 typedef struct localctx_struct
{
14 i64 base_addr
, offset_from_base
, data_size
;
18 u8 pcpaint_border_col
;
21 int has_dimension_fields
;
27 // Return a width that might be overridden by user request.
28 static i64
get_width(deark
*c
, lctx
*d
, i64 default_width
)
31 s
= de_get_ext_option(c
, "bsave:width");
32 if(s
) return de_atoi64(s
);
36 static i64
get_height(deark
*c
, lctx
*d
, i64 default_height
)
39 s
= de_get_ext_option(c
, "bsave:height");
40 if(s
) return de_atoi64(s
);
41 return default_height
;
44 // 16-color 160x100 (maybe up to 160x102) mode.
45 // This is really a text mode, and can be processed by do_char() as well.
46 static int do_cga16(deark
*c
, lctx
*d
)
48 de_bitmap
*img
= NULL
;
50 i64 max_possible_height
;
53 u8 charcode
, colorcode
;
58 de_declare_fmt(c
, "BSAVE-PC 16-color CGA pseudo-graphics");
60 w
= get_width(c
, d
, 160);
61 h
= get_height(c
, d
, 100);
63 // Every pair of bytes codes for two pixels; i.e. one byte per pixel.
65 max_possible_height
= (d
->data_size
+src_rowspan
-1)/src_rowspan
;
66 if(h
> max_possible_height
)
67 h
= max_possible_height
;
70 de_err(c
, "Not enough data for this format");
74 img
= de_bitmap_create(c
, w
, h
, 3);
78 charcode
= de_getbyte(BSAVE_HDRSIZE
+ j
*src_rowspan
+ i
);
79 colorcode
= de_getbyte(BSAVE_HDRSIZE
+ j
*src_rowspan
+ i
+1);
81 if(charwarning
==0 && charcode
!=0xdd && charcode
!=0xde) {
82 // TODO: We could also handle space characters and full-block characters,
83 // at least. But maybe not worth the trouble.
84 de_warn(c
, "Unexpected code found (0x%02x). Format may not be correct.", (int)charcode
);
89 color0
= colorcode
>>4;
90 color1
= colorcode
&0x0f;
93 color1
= colorcode
>>4;
94 color0
= colorcode
&0x0f;
97 de_bitmap_setpixel_rgb(img
, i
+0, j
, de_palette_pc16(color0
));
98 de_bitmap_setpixel_rgb(img
, i
+1, j
, de_palette_pc16(color1
));
106 de_bitmap_write_to_file(img
, NULL
, 0);
108 de_bitmap_destroy(img
);
112 // 4-color interlaced or non-interlaced
113 // "wh4": http://cd.textfiles.com/bthevhell/200/111/ - *.pic
114 static int do_4color(deark
*c
, lctx
*d
)
116 // TODO: This may not be the right palette.
117 static const u32 default_palette
[4] = { 0x000000, 0x55ffff, 0xff55ff, 0xffffff };
124 de_bitmap
*img
= NULL
;
126 if(d
->has_dimension_fields
) {
128 de_declare_fmt(c
, "BSAVE-PC 4-color, interlaced, 11-byte header");
130 de_declare_fmt(c
, "BSAVE-PC 4-color, noninterlaced, 11-byte header");
134 de_declare_fmt(c
, "BSAVE-PC 4-color, interlaced");
136 de_declare_fmt(c
, "BSAVE-PC 4-color, noninterlaced");
141 if(d
->has_dimension_fields
) {
142 // 11-byte header that includes width & height
143 w
= (de_getu16le(pos
) + 1)/2; // width = number of bits??
144 h
= de_getu16le(pos
+2);
148 w
= get_width(c
, d
, 320);
149 h
= get_height(c
, d
, 200); // TODO: Calculate this?
153 if(d
->has_pcpaint_sig
) {
155 palette
[i
] = de_palette_pcpaint_cga4(d
->pcpaint_pal_num
, (int)i
);
157 palette
[0] = de_palette_pc16(d
->pcpaint_border_col
);
161 palette
[i
] = default_palette
[i
];
165 src_rowspan
= (w
+3)/4;
166 img
= de_bitmap_create(c
, w
, h
, 3);
171 // Image is interlaced. Even-numbered scanlines are stored first.
172 palent
= (int)de_get_bits_symbol(c
->infile
, 2,
173 pos
+ (j
%2)*8192 + (j
/2)*src_rowspan
, i
);
176 palent
= (int)de_get_bits_symbol(c
->infile
, 2,
177 pos
+ j
*src_rowspan
, i
);
179 de_bitmap_setpixel_rgb(img
, i
, j
, palette
[palent
]);
183 de_bitmap_write_to_file(img
, NULL
, 0);
184 de_bitmap_destroy(img
);
188 // 2-color interlaced or non-interlaced
189 // "cga2": http://cd.textfiles.com/bthevhell/100/21/
190 // "wh2": http://cd.textfiles.com/bthevhell/200/112/
191 static int do_2color(deark
*c
, lctx
*d
)
197 de_bitmap
*img
= NULL
;
201 if(d
->has_dimension_fields
) {
203 de_declare_fmt(c
, "BSAVE-PC 2-color, interlaced, 11-byte header");
205 de_declare_fmt(c
, "BSAVE-PC 2-color, noninterlaced, 11-byte header");
209 de_declare_fmt(c
, "BSAVE-PC 2-color, interlaced");
211 de_declare_fmt(c
, "BSAVE-PC 2-color, noninterlaced");
214 if(d
->has_dimension_fields
) {
215 // 11-byte header that includes width & height
216 w
= de_getu16le(pos
);
217 h
= de_getu16le(pos
+2);
221 w
= get_width(c
, d
, 640);
222 h
= get_height(c
, d
, 200); // TODO: calculate this?
225 de_dbg_dimensions(c
, w
, h
);
226 src_rowspan
= (w
+7)/8;
228 img
= de_bitmap_create(c
, w
, h
, 1);
232 de_convert_row_bilevel(c
->infile
, pos
+ (j
%2)*8192 + (j
/2)*src_rowspan
,
236 de_convert_row_bilevel(c
->infile
, pos
+ j
*src_rowspan
, img
, j
, 0);
240 de_bitmap_write_to_file(img
, NULL
, 0);
241 de_bitmap_destroy(img
);
246 // http://cd.textfiles.com/advheaven2/PUZZLES/DRCODE12/
247 static int do_256color(deark
*c
, lctx
*d
)
253 de_bitmap
*img
= NULL
;
255 de_declare_fmt(c
, "BSAVE-PC 256-color");
257 w
= get_width(c
, d
, 320);
258 h
= get_height(c
, d
, 200);
260 img
= de_bitmap_create(c
, w
, h
, 3);
264 palent
= de_getbyte(BSAVE_HDRSIZE
+ j
*w
+ i
);
266 clr
= d
->pal
[(int)palent
];
269 clr
= de_palette_vga256(palent
);
271 de_bitmap_setpixel_rgb(img
, i
, j
, clr
);
275 de_bitmap_write_to_file(img
, NULL
, 0);
276 de_bitmap_destroy(img
);
280 // 11-byte header that includes width & height, 16 color, inter-row interlaced
281 // http://cd.textfiles.com/advheaven2/SOLITAIR/SP107/
282 static int do_wh16(deark
*c
, lctx
*d
)
285 de_bitmap
*img
= NULL
;
293 de_declare_fmt(c
, "BSAVE-PC 16-color, interlaced, 11-byte header");
296 w
= de_getu16le(pos
);
297 h
= de_getu16le(pos
+2);
300 de_dbg_dimensions(c
, w
, h
);
301 img
= de_bitmap_create(c
, w
, h
, 3);
303 src_rowspan1
= (w
+7)/8;
304 src_rowspan
= src_rowspan1
*4;
308 b0
= de_get_bits_symbol(c
->infile
, 1, pos
+ j
*src_rowspan
+ src_rowspan1
*0, i
);
309 b1
= de_get_bits_symbol(c
->infile
, 1, pos
+ j
*src_rowspan
+ src_rowspan1
*1, i
);
310 b2
= de_get_bits_symbol(c
->infile
, 1, pos
+ j
*src_rowspan
+ src_rowspan1
*2, i
);
311 b3
= de_get_bits_symbol(c
->infile
, 1, pos
+ j
*src_rowspan
+ src_rowspan1
*3, i
);
312 palent
= b0
| (b1
<<1) | (b2
<<2) | (b3
<<3);
313 de_bitmap_setpixel_rgb(img
, i
, j
, de_palette_pc16(palent
));
317 de_bitmap_write_to_file(img
, NULL
, 0);
319 de_bitmap_destroy(img
);
323 // Used at http://cd.textfiles.com/bthevhell/300/265/
324 // A strange 2-bits/2-pixel color format.
325 static int do_b265(deark
*c
, lctx
*d
)
327 static const u32 palette1
[4] = { 0xffffff, 0x55ffff, 0x000000, 0xffffff };
328 static const u32 palette2
[4] = { 0xffffff, 0x000000, 0x000000, 0x000000 };
332 i64 bits_per_scanline
;
333 de_bitmap
*img
= NULL
;
337 de_declare_fmt(c
, "BSAVE-PC special");
341 h
= d
->data_size
* 4 / fakewidth
;
342 bits_per_scanline
= w
;
344 img
= de_bitmap_create(c
, w
, h
, 3);
347 for(i
=0; i
<fakewidth
; i
++) {
348 palent
= (int)de_get_bits_symbol(c
->infile
, 2,
349 BSAVE_HDRSIZE
+ (j
/8)*bits_per_scanline
+ (i
/4)*8 + j
%8, i
%4);
350 de_bitmap_setpixel_rgb(img
, 2*i
, j
, palette1
[palent
]);
351 de_bitmap_setpixel_rgb(img
, 2*i
+1, j
, palette2
[palent
]);
355 de_bitmap_write_to_file(img
, NULL
, 0);
359 de_bitmap_destroy(img
);
363 static void do_char_1screen(deark
*c
, lctx
*d
, struct de_char_screen
*screen
, i64 pgnum
,
364 i64 pg_offset_in_data
, i64 width
, i64 height
)
371 struct de_encconv_state es
;
373 screen
->width
= width
;
374 screen
->height
= height
;
375 screen
->cell_rows
= de_mallocarray(c
, height
, sizeof(struct de_char_cell
*));
376 de_encconv_init(&es
, DE_ENCODING_CP437_G
);
378 for(j
=0; j
<height
; j
++) {
379 screen
->cell_rows
[j
] = de_mallocarray(c
, width
, sizeof(struct de_char_cell
));
381 for(i
=0; i
<width
; i
++) {
382 // 96 padding bytes per page?
383 offset
= BSAVE_HDRSIZE
+ pg_offset_in_data
+ j
*(width
*2) + i
*2;
385 b0
= de_getbyte(offset
);
386 b1
= de_getbyte(offset
+1);
389 //"The attribute byte stores the foreground color in the low nibble and the background color and blink attribute in the high nibble."
390 //TODO: "blink" attribute?
392 bgcol
= (b1
& 0xf0) >> 4;
394 screen
->cell_rows
[j
][i
].fgcol
= (u32
)fgcol
;
395 screen
->cell_rows
[j
][i
].bgcol
= (u32
)bgcol
;
396 screen
->cell_rows
[j
][i
].codepoint
= (i32
)ch
;
397 screen
->cell_rows
[j
][i
].codepoint_unicode
= de_char_to_unicode_ex((i32
)ch
, &es
);
402 static int do_char(deark
*c
, lctx
*d
)
404 struct de_char_context
*charctx
= NULL
;
410 i64 height_for_this_page
;
412 i64 bytes_for_this_page
;
413 i64 pg_offset_in_data
;
415 de_declare_fmt(c
, "BSAVE-PC character graphics");
417 width
= get_width(c
, d
, 80);
418 height
= get_height(c
, d
, 25);
420 // If there are multiple pages, the usually have some unused space between
421 // them. Try to guess how much.
423 if(width
*height
*2 <= 2048) {
424 bytes_per_page
= 2048; // E.g. 40x25(*2) = 2000
426 else if(width
*height
*2 <= 4096) {
427 bytes_per_page
= 4096; // E.g. 80x25(*2) = 4000
429 else if(width
*height
*2 <= 8192) {
430 bytes_per_page
= 8192; // Just guessing. Maybe 80x50.
433 bytes_per_page
= 16384; // E.g. 80x100 (160x100) pseudo-graphics mode.
436 numpages
= (d
->data_size
+ (bytes_per_page
-1))/bytes_per_page
;
440 de_dbg(c
, "pages: %d", (int)numpages
);
442 charctx
= de_malloc(c
, sizeof(struct de_char_context
));
443 charctx
->nscreens
= numpages
;
444 charctx
->screens
= de_mallocarray(c
, numpages
, sizeof(struct de_char_screen
*));
446 for(k
=0; k
<16; k
++) {
447 charctx
->pal
[k
] = de_palette_pc16((int)k
);
450 for(pgnum
=0; pgnum
<numpages
; pgnum
++) {
451 charctx
->screens
[pgnum
] = de_malloc(c
, sizeof(struct de_char_screen
));
453 pg_offset_in_data
= bytes_per_page
*pgnum
;
454 bytes_for_this_page
= d
->data_size
- pg_offset_in_data
;
455 if(bytes_for_this_page
<2) break;
457 // Reduce the height if there's not enough data for it.
458 height_for_this_page
= (bytes_for_this_page
+(width
*2-1)) / (width
*2);
459 if(height_for_this_page
>height
) {
460 height_for_this_page
= height
;
463 do_char_1screen(c
, d
, charctx
->screens
[pgnum
], pgnum
, pg_offset_in_data
, width
, height_for_this_page
);
466 de_char_output_to_file(c
, charctx
);
471 de_free_charctx(c
, charctx
);
475 static int do_read_palette_file(deark
*c
, lctx
*d
, const char *palfn
)
482 de_dbg(c
, "reading palette file %s", palfn
);
484 f
= dbuf_open_input_file(c
, palfn
);
486 de_err(c
, "Cannot read palette file %s", palfn
);
490 for(i
=0; i
<256; i
++) {
491 dbuf_read(f
, buf
, BSAVE_HDRSIZE
+ 3*i
, 3);
492 d
->pal
[i
] = DE_MAKE_RGB(de_scale_63_to_255(buf
[0]),
493 de_scale_63_to_255(buf
[1]),
494 de_scale_63_to_255(buf
[2]));
504 typedef int (*decoder_fn_type
)(deark
*c
, lctx
*d
);
506 static void de_run_bsave(deark
*c
, de_module_params
*mparams
)
508 const char *bsavefmt
;
512 decoder_fn_type decoder_fn
= NULL
;
514 d
= de_malloc(c
, sizeof(lctx
));
516 d
->base_addr
= de_getu16le(1);
517 d
->offset_from_base
= de_getu16le(3);
518 d
->data_size
= de_getu16le(5);
520 de_dbg(c
, "base_addr: 0x%04x", (int)d
->base_addr
);
521 de_dbg(c
, "offset_from_base: 0x%04x", (int)d
->offset_from_base
);
522 de_dbg(c
, "data_size: 0x%04x (%d)", (int)d
->data_size
, (int)d
->data_size
);
524 bsavefmt
= de_get_ext_option(c
, "bsave:fmt");
529 de_read(sig
,8007,14);
530 if(!de_memcmp(sig
, "PCPaint V1.", 11)) {
531 d
->has_pcpaint_sig
= 1;
532 d
->pcpaint_pal_num
= sig
[12];
533 d
->pcpaint_border_col
= sig
[13];
536 if(!de_strcmp(bsavefmt
,"cga2")) {
538 d
->has_dimension_fields
= 0;
539 decoder_fn
= do_2color
;
541 else if(!de_strcmp(bsavefmt
,"cga4")) {
543 decoder_fn
= do_4color
;
545 else if(!de_strcmp(bsavefmt
,"cga16")) {
546 decoder_fn
= do_cga16
;
548 else if(!de_strcmp(bsavefmt
,"mcga")) {
549 decoder_fn
= do_256color
;
551 else if(!de_strcmp(bsavefmt
,"char")) {
552 decoder_fn
= do_char
;
554 else if(!de_strcmp(bsavefmt
,"b265")) {
555 decoder_fn
= do_b265
;
557 else if(!de_strcmp(bsavefmt
,"wh2")) {
558 d
->has_dimension_fields
= 1;
559 decoder_fn
= do_2color
;
561 else if(!de_strcmp(bsavefmt
,"wh4")) {
562 d
->has_dimension_fields
= 1;
563 decoder_fn
= do_4color
;
565 else if(!de_strcmp(bsavefmt
,"wh16")) {
566 decoder_fn
= do_wh16
;
568 else if(!de_strcmp(bsavefmt
,"4col")) {
569 decoder_fn
= do_4color
;
571 else if(!de_strcmp(bsavefmt
,"2col")) {
572 d
->has_dimension_fields
= 0;
573 decoder_fn
= do_2color
;
575 else if(!de_strcmp(bsavefmt
,"auto")) {
576 // TODO: Better autodetection. This barely does anything.
577 if(d
->base_addr
==0xb800 && (d
->data_size
==16384 || d
->data_size
==16383)) {
579 decoder_fn
= do_4color
;
581 else if(d
->base_addr
==0xa000 && d
->data_size
==64000) {
582 decoder_fn
= do_256color
;
587 de_err(c
, "Unidentified BSAVE format, try \"-opt bsave:fmt=...\". "
588 "Use \"-m bsave -h\" for a list.");
592 if(!de_strcmp(bsavefmt
,"auto")) {
593 de_warn(c
, "BSAVE formats can't be reliably identified. You may need to "
594 "use \"-opt bsave:fmt=...\". Use \"-m bsave -h\" for a list.");
597 s
= de_get_ext_option(c
, "palfile");
598 if(!s
) s
= de_get_ext_option(c
, "file2");
600 if(!do_read_palette_file(c
, d
, s
)) goto done
;
603 (void)decoder_fn(c
, d
);
609 static int de_identify_bsave(deark
*c
)
611 // Note - Make sure XZ has higher confidence.
612 // Note - Make sure BLD has higher confidence.
613 if(de_getbyte(0)==0xfd) return 10;
617 static void de_help_bsave(deark
*c
)
619 de_msg(c
, "-opt bsave:fmt=...");
620 de_msg(c
, " char : Character graphics");
621 de_msg(c
, " cga2 : 2-color, 640x200");
622 de_msg(c
, " cga4 : 4-color, 320x200");
623 de_msg(c
, " cga16 : 16-color, 160x100 pseudographics");
624 de_msg(c
, " mcga : 256-color, 320x200");
625 de_msg(c
, " wh2 : 2-color, 11-byte header");
626 de_msg(c
, " wh4 : 4-color, 11-byte header");
627 de_msg(c
, " wh16 : 16-color, 11-byte header, inter-row interlaced");
628 de_msg(c
, " b256 : Special");
629 de_msg(c
, " 2col : 2-color noninterlaced");
630 de_msg(c
, " 4col : 4-color noninterlaced");
633 void de_module_bsave(deark
*c
, struct deark_module_info
*mi
)
636 mi
->desc
= "BSAVE/BLOAD image";
637 mi
->run_fn
= de_run_bsave
;
638 mi
->identify_fn
= de_identify_bsave
;
639 mi
->help_fn
= de_help_bsave
;