New "videomaster" module
[deark.git] / modules / bsave.c
blob1c380edeca2687d9574c29ca54162621e714e2fd
1 // This file is part of Deark.
2 // Copyright (C) 2016 Jason Summers
3 // See the file COPYING for terms of use.
5 // BSAVE image format
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;
16 int has_pcpaint_sig;
17 u8 pcpaint_pal_num;
18 u8 pcpaint_border_col;
20 int interlaced;
21 int has_dimension_fields;
23 int pal_valid;
24 u32 pal[256];
25 } lctx;
27 // Return a width that might be overridden by user request.
28 static i64 get_width(deark *c, lctx *d, i64 default_width)
30 const char *s;
31 s = de_get_ext_option(c, "bsave:width");
32 if(s) return de_atoi64(s);
33 return default_width;
36 static i64 get_height(deark *c, lctx *d, i64 default_height)
38 const char *s;
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;
49 i64 w, h;
50 i64 max_possible_height;
51 i64 i, j;
52 int retval = 0;
53 u8 charcode, colorcode;
54 i64 src_rowspan;
55 u8 color0, color1;
56 int charwarning = 0;
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.
64 src_rowspan = w;
65 max_possible_height = (d->data_size+src_rowspan-1)/src_rowspan;
66 if(h > max_possible_height)
67 h = max_possible_height;
69 if(h < 1) {
70 de_err(c, "Not enough data for this format");
71 goto done;
74 img = de_bitmap_create(c, w, h, 3);
76 for(j=0; j<h; j++) {
77 for(i=0; i<w; i+=2) {
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);
85 charwarning=1;
88 if(charcode==0xde) {
89 color0 = colorcode>>4;
90 color1 = colorcode&0x0f;
92 else {
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));
102 retval = 1;
104 done:
105 if(retval) {
106 de_bitmap_write_to_file(img, NULL, 0);
108 de_bitmap_destroy(img);
109 return retval;
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 };
118 u32 palette[4];
119 int palent;
120 i64 w, h;
121 i64 i,j;
122 i64 pos;
123 i64 src_rowspan;
124 de_bitmap *img = NULL;
126 if(d->has_dimension_fields) {
127 if(d->interlaced)
128 de_declare_fmt(c, "BSAVE-PC 4-color, interlaced, 11-byte header");
129 else
130 de_declare_fmt(c, "BSAVE-PC 4-color, noninterlaced, 11-byte header");
132 else {
133 if(d->interlaced)
134 de_declare_fmt(c, "BSAVE-PC 4-color, interlaced");
135 else
136 de_declare_fmt(c, "BSAVE-PC 4-color, noninterlaced");
139 pos = BSAVE_HDRSIZE;
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);
145 pos+=4;
147 else {
148 w = get_width(c, d, 320);
149 h = get_height(c, d, 200); // TODO: Calculate this?
152 // Set the palette
153 if(d->has_pcpaint_sig) {
154 for(i=0; i<4; i++) {
155 palette[i] = de_palette_pcpaint_cga4(d->pcpaint_pal_num, (int)i);
157 palette[0] = de_palette_pc16(d->pcpaint_border_col);
159 else {
160 for(i=0; i<4; i++) {
161 palette[i] = default_palette[i];
165 src_rowspan = (w+3)/4;
166 img = de_bitmap_create(c, w, h, 3);
168 for(j=0;j<h;j++) {
169 for(i=0;i<w;i++) {
170 if(d->interlaced) {
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);
175 else {
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);
185 return 1;
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)
193 i64 w, h;
194 i64 j;
195 i64 src_rowspan;
196 i64 pos;
197 de_bitmap *img = NULL;
199 pos = BSAVE_HDRSIZE;
201 if(d->has_dimension_fields) {
202 if(d->interlaced)
203 de_declare_fmt(c, "BSAVE-PC 2-color, interlaced, 11-byte header");
204 else
205 de_declare_fmt(c, "BSAVE-PC 2-color, noninterlaced, 11-byte header");
207 else {
208 if(d->interlaced)
209 de_declare_fmt(c, "BSAVE-PC 2-color, interlaced");
210 else
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);
218 pos+=4;
220 else {
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);
230 for(j=0; j<h; j++) {
231 if(d->interlaced) {
232 de_convert_row_bilevel(c->infile, pos + (j%2)*8192 + (j/2)*src_rowspan,
233 img, j, 0);
235 else {
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);
242 return 1;
245 // 256-color
246 // http://cd.textfiles.com/advheaven2/PUZZLES/DRCODE12/
247 static int do_256color(deark *c, lctx *d)
249 i64 w, h;
250 i64 i, j;
251 u8 palent;
252 u32 clr;
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);
262 for(j=0; j<h; j++) {
263 for(i=0; i<w; i++) {
264 palent = de_getbyte(BSAVE_HDRSIZE + j*w + i);
265 if(d->pal_valid) {
266 clr = d->pal[(int)palent];
268 else {
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);
277 return 1;
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)
284 i64 i, j;
285 de_bitmap *img = NULL;
286 i64 w, h;
287 i64 src_rowspan1;
288 i64 src_rowspan;
289 i64 pos;
290 u8 palent;
291 u8 b0, b1, b2, b3;
293 de_declare_fmt(c, "BSAVE-PC 16-color, interlaced, 11-byte header");
295 pos = BSAVE_HDRSIZE;
296 w = de_getu16le(pos);
297 h = de_getu16le(pos+2);
298 pos+=4;
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;
306 for(j=0; j<h; j++) {
307 for(i=0; i<w; i++) {
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);
320 return 1;
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 };
329 int palent;
330 i64 w, h;
331 i64 i,j;
332 i64 bits_per_scanline;
333 de_bitmap *img = NULL;
334 i64 fakewidth;
335 int retval = 0;
337 de_declare_fmt(c, "BSAVE-PC special");
339 w = 320;
340 fakewidth = w/2;
341 h = d->data_size * 4 / fakewidth;
342 bits_per_scanline = w;
344 img = de_bitmap_create(c, w, h, 3);
346 for(j=0; j<h; j++) {
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);
357 retval = 1;
359 de_bitmap_destroy(img);
360 return retval;
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)
366 i64 i, j;
367 unsigned int ch;
368 u8 fgcol, bgcol;
369 i64 offset;
370 u8 b0, b1;
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);
388 ch = b0;
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?
391 fgcol = (b1 & 0x0f);
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;
405 int retval = 0;
406 i64 k;
407 i64 numpages;
408 i64 pgnum;
409 i64 width, height;
410 i64 height_for_this_page;
411 i64 bytes_per_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.
432 else {
433 bytes_per_page = 16384; // E.g. 80x100 (160x100) pseudo-graphics mode.
436 numpages = (d->data_size + (bytes_per_page-1))/bytes_per_page;
437 if(numpages<1) {
438 goto done;
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);
468 retval = 1;
470 done:
471 de_free_charctx(c, charctx);
472 return retval;
475 static int do_read_palette_file(deark *c, lctx *d, const char *palfn)
477 dbuf *f = NULL;
478 int retval = 0;
479 i64 i;
480 u8 buf[3];
482 de_dbg(c, "reading palette file %s", palfn);
484 f = dbuf_open_input_file(c, palfn);
485 if(!f) {
486 de_err(c, "Cannot read palette file %s", palfn);
487 goto done;
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]));
496 d->pal_valid = 1;
498 retval = 1;
499 done:
500 dbuf_close(f);
501 return retval;
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;
509 const char *s;
510 lctx *d;
511 u8 sig[14];
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");
525 if(!bsavefmt) {
526 bsavefmt="auto";
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")) {
537 d->interlaced = 1;
538 d->has_dimension_fields = 0;
539 decoder_fn = do_2color;
541 else if(!de_strcmp(bsavefmt,"cga4")) {
542 d->interlaced = 1;
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)) {
578 d->interlaced = 1;
579 decoder_fn = do_4color;
581 else if(d->base_addr==0xa000 && d->data_size==64000) {
582 decoder_fn = do_256color;
586 if(!decoder_fn) {
587 de_err(c, "Unidentified BSAVE format, try \"-opt bsave:fmt=...\". "
588 "Use \"-m bsave -h\" for a list.");
589 goto done;
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");
599 if(s) {
600 if(!do_read_palette_file(c, d, s)) goto done;
603 (void)decoder_fn(c, d);
605 done:
606 de_free(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;
614 return 0;
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)
635 mi->id = "bsave";
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;