1 /* vim:expandtab:ts=2 sw=2:
2 */
3 /* Grafx2 - The Ultimate 256-color bitmap paint program
4
5 Copyright owned by various GrafX2 authors, see COPYRIGHT.txt for details.
6
7 Grafx2 is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License
9 as published by the Free Software Foundation; version 2
10 of the License.
11
12 Grafx2 is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Grafx2; if not, see <http://www.gnu.org/licenses/>
19 */
20
21 ///@file motoformats.c
22 /// Formats for the MO/TO Thomson machines
23
24 #include <stdlib.h>
25 #include <string.h>
26 #if defined(_MSC_VER)
27 #define strdup _strdup
28 #endif
29 #include "struct.h"
30 #include "io.h"
31 #include "loadsave.h"
32 #include "loadsavefuncs.h"
33 #include "fileformats.h"
34 #include "oldies.h"
35 #include "input.h"
36 #include "engine.h"
37 #include "screen.h"
38 #include "windows.h"
39 #include "help.h"
40 #include "gfx2mem.h"
41 #include "gfx2log.h"
42
43 extern char Program_version[]; // generated in pversion.c
44 extern const char SVN_revision[]; // generated in version.c
45
46 /////////////////////////////// Thomson Files ///////////////////////////////
47
48 /**
49 * Test for Thomson file
50 */
Test_MOTO(T_IO_Context * context,FILE * file)51 void Test_MOTO(T_IO_Context * context, FILE * file)
52 {
53 long file_size;
54
55 file_size = File_length_file(file);
56
57 File_error = 1;
58 if (file_size <= 10)
59 return;
60 switch (MOTO_Check_binary_file(file))
61 {
62 case 0: // Not Thomson binary format
63 switch (file_size)
64 {
65 // Files in RAW formats (from TGA2teo)
66 case 8004: // 2 colors palette
67 case 8008: // 4 colors palette
68 case 8032: // 16 colors palette
69 {
70 char * filename;
71 char * path;
72 char * ext;
73
74 // Check there are both FORME and COULEUR files
75 filename = strdup(context->File_name);
76 ext = strrchr(filename, '.');
77 if (ext == NULL || ext == filename)
78 {
79 free(filename);
80 return;
81 }
82 if ((ext[-1] | 32) == 'c')
83 ext[-1] = (ext[-1] & 32) | 'P';
84 else if ((ext[-1] | 32) == 'p')
85 ext[-1] = (ext[-1] & 32) | 'C';
86 else
87 {
88 free(filename);
89 return;
90 }
91 path = Filepath_append_to_dir(context->File_directory, filename);
92 if (File_exists(path))
93 File_error = 0;
94 free(path);
95 free(filename);
96 }
97 return;
98 default:
99 break;
100 }
101 break;
102 case 2: // MAP file (SAVEP/LOADP)
103 case 3: // TO autoloading picture
104 case 4: // MO autoloading picture
105 File_error = 0;
106 return;
107 }
108 }
109
110 /**
111 * Load a picture for Thomson TO8/TO8D/TO9/TO9+/MO6
112 *
113 * One of the supported format is the one produced by TGA2Teo :
114 * - Picture data is splitted into 2 files, one for each VRAM bank :
115 * - The first VRAM bank is called "forme" (shape).
116 * In 40col mode it stores pixels.
117 * - The second VRAM bank is called "couleur" (color).
118 * In 40col mode it store color indexes for foreground and background.
119 * - File extension is .BIN, character before extension is "P" for the first
120 * file, and "C" for the second.
121 * - The color palette is stored in both files after the data.
122 *
123 * The mode is detected thanks to the number of color in the palette :
124 * - 2 colors is 80col (640x200)
125 * - 4 colors is bitmap4 (320x200 4 colors)
126 * - 16 colors is either bitmap16 (160x200 16colors)
127 * or 40col (320x200 16 colors with 2 unique colors in each 8x1 pixels
128 * block).
129 *
130 * As it is not possible to disriminate bitmap16 and 40col, opening the "P"
131 * file sets bitmap16, opening the "C" file sets 40col.
132 *
133 * This function also supports .MAP files (with optional TO-SNAP extension)
134 * and our own "autoloading" BIN files.
135 * See http://pulkomandy.tk/projects/GrafX2/wiki/Develop/FileFormats/MOTO for
136 * a detailled description.
137 */
Load_MOTO(T_IO_Context * context)138 void Load_MOTO(T_IO_Context * context)
139 {
140 // FORME / COULEUR
141 FILE * file;
142 byte * vram_forme = NULL;
143 byte * vram_couleur = NULL;
144 long file_size;
145 int file_type;
146 int bx, x, y, i;
147 byte bpp = 4;
148 byte code;
149 word length, address;
150 int transpose = 1; // transpose the upper bits of the color plane bytes
151 // FFFFBBBB becomes bfFFFBBB (for TO7 compatibility)
152 enum MOTO_Graphic_Mode mode = MOTO_MODE_40col;
153 enum PIXEL_RATIO ratio = PIXEL_SIMPLE;
154 int width = 320, height = 200, columns = 40;
155
156 File_error = 1;
157 file = Open_file_read(context);
158 if (file == NULL)
159 return;
160 file_size = File_length_file(file);
161 // Load default palette
162 if (Config.Clear_palette)
163 memset(context->Palette,0,sizeof(T_Palette));
164 MOTO_set_TO7_palette(context->Palette);
165
166 file_type = MOTO_Check_binary_file(file);
167 if (fseek(file, 0, SEEK_SET) < 0)
168 {
169 fclose(file);
170 return;
171 }
172
173 if (file_type == 2) // MAP file
174 {
175 // http://collection.thomson.free.fr/code/articles/prehisto_bulletin/page.php?XI=0&XJ=13
176 byte map_mode, col_count, line_count;
177 byte * vram_current;
178 int end_marks;
179
180 if (!(Read_byte(file,&code) && Read_word_be(file,&length) && Read_word_be(file,&address)))
181 {
182 fclose(file);
183 return;
184 }
185 if (length < 5 || !(Read_byte(file,&map_mode) && Read_byte(file,&col_count) && Read_byte(file,&line_count)))
186 {
187 fclose(file);
188 return;
189 }
190 length -= 3;
191 columns = col_count + 1;
192 height = 8 * (line_count + 1);
193 switch(map_mode)
194 {
195 default:
196 case 0: // bitmap4 or 40col
197 width = 8 * columns;
198 mode = MOTO_MODE_40col; // default to 40col
199 bpp = 4;
200 break;
201 case 0x40: // bitmap16
202 columns >>= 1;
203 width = 4 * columns;
204 mode = MOTO_MODE_bm16;
205 bpp = 4;
206 ratio = PIXEL_WIDE;
207 break;
208 case 0x80: // 80col
209 columns >>= 1;
210 width = 16 * columns;
211 mode = MOTO_MODE_80col;
212 bpp = 1;
213 ratio = PIXEL_TALL;
214 break;
215 }
216 GFX2_Log(GFX2_DEBUG, "Map mode &H%02X row=%u line=%u (%dx%d) %d\n", map_mode, col_count, line_count, width, height, columns * height);
217 vram_forme = GFX2_malloc(columns * height);
218 vram_couleur = GFX2_malloc(columns * height);
219 // Check extension (TO-SNAP / PPM / ???)
220 if (length > 36)
221 {
222 long pos_backup;
223 word data;
224
225 pos_backup = ftell(file);
226 fseek(file, length-2, SEEK_CUR); // go to last word of chunk
227 Read_word_be(file, &data);
228 GFX2_Log(GFX2_DEBUG, "%04X\n", data);
229 switch (data)
230 {
231 case 0xA55A: // TO-SNAP
232 fseek(file, -40, SEEK_CUR); // go to begin of extension
233 Read_word_be(file, &data); // SCRMOD. 0=>40col, 1=>bm4, $40=>bm16, $80=>80col
234 GFX2_Log(GFX2_DEBUG, "SCRMOD=&H%04X ", data);
235 Read_word_be(file, &data); // Border color
236 GFX2_Log(GFX2_DEBUG, "BORDER=%u ", data);
237 Read_word_be(file, &data); // Mode BASIC (CONSOLE,,,,X) 0=40col, 1=80col, 2=bm4, 3=bm16, etc.
238 GFX2_Log(GFX2_DEBUG, "CONSOLE,,,,%u\n", data);
239 if(data == 2)
240 {
241 mode = MOTO_MODE_bm4;
242 bpp = 2;
243 }
244 for (i = 0; i < 16; i++)
245 {
246 Read_word_be(file, &data); // Palette entry
247 if (data & 0x8000) data = ~data;
248 MOTO_gamma_correct_MOTO_to_RGB(&context->Palette[i], data);
249 }
250 snprintf(context->Comment, sizeof(context->Comment), "TO-SNAP .MAP file");
251 break;
252 case 0x484C: // 'HL' PPM
253 fseek(file, -36, SEEK_CUR); // go to begin of extension
254 for (i = 0; i < 16; i++)
255 {
256 Read_word_be(file, &data); // Palette entry
257 if (data & 0x8000) data = ~data;
258 MOTO_gamma_correct_MOTO_to_RGB(&context->Palette[i], data);
259 }
260 Read_word_be(file, &data); // Mode BASIC (CONSOLE,,,,X) 0=40col, 1=80col, 2=bm4, 3=bm16, etc.
261 GFX2_Log(GFX2_DEBUG, "CONSOLE,,,,%u\n", data);
262 if(data == 2)
263 {
264 mode = MOTO_MODE_bm4;
265 bpp = 2;
266 }
267 snprintf(context->Comment, sizeof(context->Comment), "PPM .MAP file");
268 break;
269 default:
270 snprintf(context->Comment, sizeof(context->Comment), "standard .MAP file");
271 }
272 fseek(file, pos_backup, SEEK_SET); // RESET Position
273 }
274 i = 0;
275 vram_current = vram_forme;
276 end_marks = 0;
277 while (length > 1)
278 {
279 byte byte1, byte2;
280 Read_byte(file,&byte1);
281 Read_byte(file,&byte2);
282 length-=2;
283 if(byte1 == 0)
284 {
285 if (byte2 == 0)
286 {
287 // end of vram stream
288 GFX2_Log(GFX2_DEBUG, "0000 i=%d length=%ld\n", i, length);
289 if (end_marks == 1)
290 break;
291 i = 0;
292 vram_current = vram_couleur;
293 end_marks++;
294 }
295 else while(byte2-- > 0 && length > 0) // copy
296 {
297 Read_byte(file,vram_current + i);
298 length--;
299 i += columns; // to the next line
300 if (i >= columns * height)
301 {
302 if (mode == MOTO_MODE_bm4 || mode == MOTO_MODE_40col)
303 i -= (columns * height - 1); // to the 1st line of the next column
304 else
305 {
306 i -= columns * height; // back to the 1st line of the current column
307 if (vram_current == vram_forme) // other VRAM
308 vram_current = vram_couleur;
309 else
310 {
311 vram_current = vram_forme;
312 i++; // next column
313 }
314 }
315 }
316 }
317 }
318 else while(byte1-- > 0) // run length
319 {
320 vram_current[i] = byte2;
321 i += columns; // to the next line
322 if (i >= columns * height)
323 {
324 if (mode == MOTO_MODE_bm4 || mode == MOTO_MODE_40col)
325 i -= (columns * height - 1); // to the 1st line of the next column
326 else
327 {
328 i -= columns * height; // back to the 1st line of the current column
329 if (vram_current == vram_forme) // other VRAM
330 vram_current = vram_couleur;
331 else
332 {
333 vram_current = vram_forme;
334 i++; // next column
335 }
336 }
337 }
338 }
339 }
340 fclose(file);
341 }
342 else if(file_type == 3 || file_type == 4)
343 {
344 if (file_type == 4) // MO file
345 {
346 transpose = 0;
347 MOTO_set_MO5_palette(context->Palette);
348 }
349
350 do
351 {
352 if (!(Read_byte(file,&code) && Read_word_be(file,&length) && Read_word_be(file,&address)))
353 {
354 if (vram_forme)
355 break;
356 fclose(file);
357 return;
358 }
359 // MO5/MO6 VRAM address is &H0000
360 // TO7/TO8/TO9 VRAM addres is &H4000
361 if (length >= 8000 && length <= 8192 && (address == 0x4000 || address == 0))
362 {
363 if (vram_forme == NULL)
364 {
365 vram_forme = calloc(8192, 1);
366 Read_bytes(file, vram_forme, length);
367 length = 0;
368 }
369 else if (vram_couleur == NULL)
370 {
371 vram_couleur = calloc(8192, 1);
372 Read_bytes(file, vram_couleur, length);
373 if (length >= 8032)
374 {
375 for (x = 0; x < 16; x++)
376 {
377 // 1 byte Blue (4 lower bits)
378 // 1 byte Green (4 upper bits) / Red (4 lower bits)
379 MOTO_gamma_correct_MOTO_to_RGB(&context->Palette[x],
380 vram_couleur[8000+x*2]<<8 | vram_couleur[8000+x*2+1]);
381 }
382 if (length >= 8064)
383 {
384 memcpy(context->Comment, vram_couleur + 8032, 32);
385 if (vram_couleur[8063] >= '0' && vram_couleur[8063] <= '3')
386 mode = vram_couleur[8063] - '0';
387 }
388 context->Comment[COMMENT_SIZE] = '\0';
389 }
390 length = 0;
391 }
392 }
393 if (length > 0)
394 fseek(file, length, SEEK_CUR);
395 } while(code == 0);
396 fclose(file);
397 switch (mode)
398 {
399 case MOTO_MODE_40col: // default
400 break;
401 case MOTO_MODE_bm4:
402 bpp = 2;
403 break;
404 case MOTO_MODE_80col:
405 bpp = 1;
406 width = 640;
407 ratio = PIXEL_TALL;
408 break;
409 case MOTO_MODE_bm16:
410 width = 160;
411 ratio = PIXEL_WIDE;
412 break;
413 }
414 }
415 else
416 {
417 char * filename;
418 char * path;
419 char * ext;
420 int n_colors;
421
422 vram_forme = GFX2_malloc(file_size);
423 if (vram_forme == NULL)
424 {
425 fclose(file);
426 return;
427 }
428 if (!Read_bytes(file, vram_forme, file_size))
429 {
430 free(vram_forme);
431 fclose(file);
432 return;
433 }
434 n_colors = (file_size - 8000) / 2;
435 switch(n_colors)
436 {
437 case 16:
438 bpp = 4;
439 // 16 colors : either 40col or bm16 mode !
440 // select later
441 break;
442 case 4:
443 bpp = 2;
444 mode = MOTO_MODE_bm4;
445 break;
446 default:
447 bpp = 1;
448 mode = MOTO_MODE_80col;
449 width = 640;
450 ratio = PIXEL_TALL;
451 }
452 filename = strdup(context->File_name);
453 ext = strrchr(filename, '.');
454 if (ext == NULL || ext == filename)
455 {
456 free(vram_forme);
457 free(filename);
458 return;
459 }
460 if ((ext[-1] | 32) == 'c')
461 {
462 vram_couleur = vram_forme;
463 vram_forme = NULL;
464 ext[-1] = (ext[-1] & 32) | 'P';
465 }
466 else if ((ext[-1] | 32) == 'p')
467 {
468 ext[-1] = (ext[-1] & 32) | 'C';
469 if (n_colors == 16)
470 {
471 mode = MOTO_MODE_bm16;
472 width = 160;
473 ratio = PIXEL_WIDE;
474 }
475 }
476 else
477 {
478 free(vram_forme);
479 free(filename);
480 return;
481 }
482 path = Filepath_append_to_dir(context->File_directory, filename);
483 file = fopen(path, "rb");
484 if (file == NULL)
485 GFX2_Log(GFX2_ERROR, "Failed to open %s\n", path);
486 free(path);
487 free(filename);
488 if (vram_forme == NULL)
489 {
490 vram_forme = GFX2_malloc(file_size);
491 if (vram_forme == NULL)
492 {
493 free(vram_couleur);
494 fclose(file);
495 return;
496 }
497 Read_bytes(file,vram_forme,file_size);
498 }
499 else
500 {
501 vram_couleur = GFX2_malloc(file_size);
502 if (vram_couleur == NULL)
503 {
504 free(vram_forme);
505 fclose(file);
506 return;
507 }
508 Read_bytes(file,vram_couleur,file_size);
509 }
510 fclose(file);
511 GFX2_Log(GFX2_DEBUG, "MO/TO: %s,%s file_size=%ld n_colors=%d\n", context->File_name, filename, file_size, n_colors);
512 for (x = 0; x < n_colors; x++)
513 {
514 // 1 byte Blue (4 lower bits)
515 // 1 byte Green (4 upper bits) / Red (4 lower bits)
516 MOTO_gamma_correct_MOTO_to_RGB(&context->Palette[x],
517 vram_couleur[8000+x*2]<<8 | vram_couleur[8000+x*2+1]);
518 }
519 }
520 Pre_load(context, width, height, file_size, FORMAT_MOTO, ratio, bpp);
521 if (mode == MOTO_MODE_40col)
522 Set_image_mode(context, IMAGE_MODE_THOMSON);
523 File_error = 0;
524 i = 0;
525 for (y = 0; y < height; y++)
526 {
527 for (bx = 0; bx < columns; bx++)
528 {
529 byte couleur_forme;
530 byte couleur_fond;
531 byte forme, couleurs;
532
533 forme = vram_forme[i];
534 if (vram_couleur)
535 couleurs = vram_couleur[i];
536 else
537 couleurs = (mode == MOTO_MODE_40col) ? 0x01 : 0x00;
538 i++;
539 switch(mode)
540 {
541 case MOTO_MODE_bm4:
542 for (x = bx*8; x < bx*8+8; x++)
543 {
544 Set_pixel(context, x, y, ((forme & 0x80) >> 6) | ((couleurs & 0x80) >> 7));
545 forme <<= 1;
546 couleurs <<= 1;
547 }
548 #if 0 // the following would be for the alternate bm4 mode
549 for (x = bx*8; x < bx*8+4; x++)
550 {
551 Set_pixel(context, x, y, couleurs >> 6);
552 couleurs <<= 2;
553 }
554 for (x = bx*8 + 4; x < bx*8+8; x++)
555 {
556 Set_pixel(context, x, y, forme >> 6);
557 forme <<= 2;
558 }
559 #endif
560 break;
561 case MOTO_MODE_bm16:
562 Set_pixel(context, bx*4, y, forme >> 4);
563 Set_pixel(context, bx*4+1, y, forme & 0x0F);
564 Set_pixel(context, bx*4+2, y, couleurs >> 4);
565 Set_pixel(context, bx*4+3, y, couleurs & 0x0F);
566 break;
567 case MOTO_MODE_80col:
568 for (x = bx*16; x < bx*16+8; x++)
569 {
570 Set_pixel(context, x, y, (forme & 0x80) >> 7);
571 Set_pixel(context, x+8, y, (couleurs & 0x80) >> 7);
572 forme <<= 1;
573 couleurs <<= 1;
574 }
575 break;
576 case MOTO_MODE_40col:
577 default:
578 if (transpose)
579 {
580 // the color plane byte is bfFFFBBB (for TO7 compatibility)
581 // with the upper bits of both foreground (forme) and
582 // background (fond) inverted.
583 couleur_forme = ((couleurs & 0x78) >> 3) ^ 0x08;
584 couleur_fond = ((couleurs & 7) | ((couleurs & 0x80) >> 4)) ^ 0x08;
585 }
586 else
587 {
588 // MO5 : the color plane byte is FFFFBBBB
589 couleur_forme = couleurs >> 4;
590 couleur_fond = couleurs & 0x0F;
591 }
592 for (x = bx*8; x < bx*8+8; x++)
593 {
594 Set_pixel(context, x, y, (forme & 0x80)?couleur_forme:couleur_fond);
595 forme <<= 1;
596 }
597 }
598 }
599 }
600 free(vram_forme);
601 free(vram_couleur);
602 }
603
604 /**
605 * Pack a stream of byte in the format used by Thomson MO/TO MAP files.
606 *
607 * - 00 cc xx yy .. : encodes a "copy run" (cc = bytes to copy)
608 * - cc xx : encodes a "repeat run" (cc > 0 : count)
609 */
610 //#define MOTO_MAP_NOPACKING
MOTO_MAP_pack(byte * packed,const byte * unpacked,unsigned int unpacked_len)611 unsigned int MOTO_MAP_pack(byte * packed, const byte * unpacked, unsigned int unpacked_len)
612 {
613 unsigned int src;
614 unsigned int dst = 0;
615 unsigned int count;
616 #ifndef MOTO_MAP_NOPACKING
617 unsigned int repeat;
618 unsigned int i;
619 word * counts;
620 #endif
621
622 GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack(%p, %p, %u)\n", packed, unpacked, unpacked_len);
623 if (unpacked_len == 0)
624 return 0;
625 if (unpacked_len == 1)
626 {
627 packed[0] = 1;
628 packed[1] = unpacked[0];
629 return 2;
630 }
631 #ifdef MOTO_MAP_NOPACKING
632 // compression disabled
633 src = 0;
634 while ((unpacked_len - src) > 255)
635 {
636 packed[dst++] = 0;
637 packed[dst++] = 255;
638 memcpy(packed+dst, unpacked+src, 255);
639 dst += 255;
640 src += 255;
641 }
642 count = unpacked_len - src;
643 packed[dst++] = 0;
644 packed[dst++] = count;
645 memcpy(packed+dst, unpacked+src, count);
646 dst += count;
647 src += count;
648 return dst;
649 #else
650 counts = GFX2_malloc(sizeof(word) * (unpacked_len + 1));
651 i = 0;
652 repeat = (unpacked[0] == unpacked[1]);
653 count = 2;
654 src = 2;
655 // 1st step : count lenght of the Copy runs and Repeat runs
656 while (src < unpacked_len)
657 {
658 if (repeat)
659 {
660 if (unpacked[src-1] == unpacked[src])
661 count++;
662 else
663 {
664 // flush the repeat run
665 counts[i++] = count | 0x8000; // 0x8000 is the marker for repeat runs
666 count = 1;
667 repeat = 0;
668 }
669 }
670 else
671 {
672 if (unpacked[src-1] != unpacked[src])
673 count++;
674 else if (count == 1)
675 {
676 count++;
677 repeat = 1;
678 }
679 else
680 {
681 // flush the copy run
682 counts[i++] = (count-1) | (count == 2 ? 0x8000 : 0); // mark copy run of 1 as repeat of 1
683 count = 2;
684 repeat = 1;
685 }
686 }
687 src++;
688 }
689 // flush the last run
690 counts[i++] = ((repeat || count == 1) ? 0x8000 : 0) | count;
691 counts[i++] = 0; // end marker
692 // check consistency of counts
693 count = 0;
694 for (i = 0; counts[i] != 0; i++)
695 count += (counts[i] & ~0x8000);
696 if (count != unpacked_len)
697 GFX2_Log(GFX2_ERROR, "*** encoding error in MOTO_MAP_pack() *** count=%u unpacked_len=%u\n",
698 count, unpacked_len);
699 // output optimized packed stream
700 // repeat run are encoded cc xx
701 // copy run are encoded 00 cc xx xx xx xx
702 i = 0;
703 src = 0;
704 while (counts[i] != 0)
705 {
706 while (counts[i] & 0x8000) // repeat run
707 {
708 count = counts[i] & ~0x8000;
709 GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack() %4u %4u repeat %u times %02x\n", src, i, count, unpacked[src]);
710 while(count > 255)
711 {
712 packed[dst++] = 255;
713 packed[dst++] = unpacked[src];
714 count -= 255;
715 src += 255;
716 }
717 packed[dst++] = count;
718 packed[dst++] = unpacked[src];
719 src += count;
720 i++;
721 }
722 while (counts[i] != 0 && !(counts[i] & 0x8000)) // copy run
723 {
724 // calculate the "savings" of repeat runs between 2 copy run
725 int savings = 0;
726 unsigned int j;
727 GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack() %4u %4u copy %u bytes\n", src, i, counts[i]);
728 for (j = i + 1; counts[j] & 0x8000; j++) // check repeat runs until the next copy run
729 {
730 count = counts[j] & ~0x8000;
731 if (savings < 0 && (savings + (int)count - 2) > 0)
732 break;
733 savings += count - 2; // a repeat run outputs 2 bytes for count bytes of input
734 }
735 count = counts[i];
736 GFX2_Log(GFX2_DEBUG, " savings=%d i=%u j=%u (counts[j]=0x%04x)\n", savings, i, j, counts[j]);
737 if (savings < 2 && (j > i + 1))
738 {
739 unsigned int k;
740 if (counts[j] == 0) // go to the end of stream
741 {
742 for (k = i + 1; k < j; k++)
743 count += (counts[k] & ~0x8000);
744 GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack() src=%u extend copy from %u to %u\n", src, counts[i], count);
745 i = j - 1;
746 }
747 else
748 {
749 for (k = i + 1; k < j; k++)
750 count += (counts[k] & ~0x8000);
751 if (!(counts[j] & 0x8000))
752 { // merge with the next copy run (and the repeat runs between)
753 GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack() src=%u merge savings=%d\n", src, savings);
754 i = j;
755 counts[i] += count;
756 continue;
757 }
758 else
759 { // merge with the next few repeat runs
760 GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack() src=%u extends savings=%d\n", src, savings);
761 i = j - 1;
762 }
763 }
764 }
765 while (count > 255)
766 {
767 packed[dst++] = 0;
768 packed[dst++] = 255;
769 memcpy(packed+dst, unpacked+src, 255);
770 dst += 255;
771 src += 255;
772 count -= 255;
773 }
774 packed[dst++] = 0;
775 packed[dst++] = count;
776 memcpy(packed+dst, unpacked+src, count);
777 dst += count;
778 src += count;
779 i++;
780 }
781 }
782 free(counts);
783 return dst;
784 #endif
785 }
786
787
788 /**
789 * GUI window to choose Thomson MO/TO saving parameters
790 *
791 * @param[out] machine target machine
792 * @param[out] format file format (0 = BIN, 1 = MAP)
793 * @param[in,out] mode video mode @ref MOTO_Graphic_Mode
794 */
Save_MOTO_window(enum MOTO_Machine_Type * machine,int * format,enum MOTO_Graphic_Mode * mode)795 static int Save_MOTO_window(enum MOTO_Machine_Type * machine, int * format, enum MOTO_Graphic_Mode * mode)
796 {
797 int button;
798 T_Dropdown_button * machine_dd;
799 T_Dropdown_button * format_dd;
800 T_Dropdown_button * mode_dd;
801 static const char * mode_list[] = { "40col", "80col", "bm4", "bm16" };
802 char text_info[24];
803
804 Open_window(200, 125, "Thomson MO/TO Saving");
805 Window_set_normal_button(110,100,80,15,"Save",1,1,KEY_RETURN); // 1
806 Window_set_normal_button(10,100,80,15,"Cancel",1,1,KEY_ESCAPE); // 2
807
808 Print_in_window(13,18,"Target Machine:",MC_Dark,MC_Light);
809 machine_dd = Window_set_dropdown_button(10,28,110,15,100,
810 (*mode == MOTO_MODE_40col) ? "TO7/TO7-70" : "TO9/TO8/TO9+",
811 1, 0, 1, LEFT_SIDE,0); // 3
812 if (*mode == MOTO_MODE_40col)
813 Window_dropdown_add_item(machine_dd, MACHINE_TO7, "TO7/TO7-70");
814 Window_dropdown_add_item(machine_dd, MACHINE_TO8, "TO9/TO8/TO9+");
815 if (*mode == MOTO_MODE_40col)
816 Window_dropdown_add_item(machine_dd, MACHINE_MO5, "MO5");
817 Window_dropdown_add_item(machine_dd, MACHINE_MO6, "MO6");
818
819 Print_in_window(13,46,"Format:",MC_Dark,MC_Light);
820 format_dd = Window_set_dropdown_button(10,56,110,15,92,"BIN",1, 0, 1, LEFT_SIDE,0); // 4
821 Window_dropdown_add_item(format_dd, 0, "BIN");
822 Window_dropdown_add_item(format_dd, 1, "MAP/TO-SNAP");
823
824 Print_in_window(136,46,"Mode:",MC_Dark,MC_Light);
825 mode_dd = Window_set_dropdown_button(136,56,54,15,44,mode_list[*mode],1, 0, 1, LEFT_SIDE,0); // 5
826 if (*mode == MOTO_MODE_40col)
827 Window_dropdown_add_item(mode_dd, *mode, mode_list[*mode]);
828 if (*mode == MOTO_MODE_80col)
829 Window_dropdown_add_item(mode_dd, *mode, mode_list[*mode]);
830 if (*mode == MOTO_MODE_40col)
831 Window_dropdown_add_item(mode_dd, MOTO_MODE_bm4, mode_list[MOTO_MODE_bm4]);
832 if (*mode == MOTO_MODE_bm16)
833 Window_dropdown_add_item(mode_dd, *mode, mode_list[*mode]);
834
835 Update_window_area(0,0,Window_width,Window_height);
836 Display_cursor();
837 do
838 {
839 button = Window_clicked_button();
840 if (Is_shortcut(Key, 0x100+BUTTON_HELP))
841 {
842 Key = 0;
843 Window_help(BUTTON_SAVE, "THOMSON MO/TO FORMAT");
844 }
845 else switch (button)
846 {
847 case 3:
848 *machine = (enum MOTO_Machine_Type)Window_attribute2;
849 break;
850 case 4:
851 *format = Window_attribute2;
852 break;
853 case 5:
854 *mode = (enum MOTO_Graphic_Mode)Window_attribute2;
855 break;
856 }
857 Hide_cursor();
858 //"ABCDEFGHIJKLMNOPQRSTUVW"
859 memset(text_info, ' ', 23);
860 text_info[23] = '\0';
861 if (*machine == MACHINE_TO7 || *machine == MACHINE_TO770 || *machine == MACHINE_MO5)
862 {
863 if (*mode != MOTO_MODE_40col)
864 snprintf(text_info, sizeof(text_info), "%s only supports 40col",
865 (*machine == MACHINE_MO5) ? "MO5" : "TO7");
866 else if (*format == 1)
867 strncpy(text_info, "No TO-SNAP extension. ", sizeof(text_info));
868 else
869 strncpy(text_info, "No palette to save. ", sizeof(text_info));
870 }
871 Print_in_window(9, 80, text_info, MC_Dark, MC_Light);
872 Display_cursor();
873 } while(button!=1 && button!=2);
874
875 Close_window();
876 Display_cursor();
877 return button==1;
878 }
879
880 /**
881 * Save a picture in MAP or BIN Thomson MO/TO file format.
882 *
883 * File format details :
884 * http://pulkomandy.tk/projects/GrafX2/wiki/Develop/FileFormats/MOTO
885 */
Save_MOTO(T_IO_Context * context)886 void Save_MOTO(T_IO_Context * context)
887 {
888 int transpose = 1; // transpose upper bits in "couleur" vram
889 enum MOTO_Machine_Type target_machine = MACHINE_TO7;
890 int format = 0; // 0 = BIN, 1 = MAP
891 enum MOTO_Graphic_Mode mode;
892 FILE * file = NULL;
893 byte * vram_forme;
894 byte * vram_couleur;
895 int i, x, y, bx;
896 word reg_prc = 0xE7C3; // PRC : TO7/8/9 0xE7C3 ; MO5/MO6 0xA7C0
897 byte prc_value = 0x65;// Value to write to PRC to select VRAM bank
898 // MO5 : 0x51
899 word vram_address = 0x4000; // 4000 on TO7/TO8/TO9, 0000 on MO5/MO6
900
901 File_error = 1;
902
903 /**
904 * In the future we could support other resolution for .MAP
905 * format.
906 * And even in .BIN format, we could store less lines. */
907 if (context->Height != 200)
908 {
909 Warning_message("must be 640x200, 320x200 or 160x200");
910 return;
911 }
912
913 switch (context->Width)
914 {
915 case 160:
916 mode = MOTO_MODE_bm16;
917 target_machine = MACHINE_TO8;
918 break;
919 case 640:
920 mode = MOTO_MODE_80col;
921 target_machine = MACHINE_TO8;
922 break;
923 case 320:
924 mode = MOTO_MODE_40col; // or bm4
925 break;
926 default:
927 Warning_message("must be 640x200, 320x200 or 160x200");
928 return;
929 }
930
931 if (!Save_MOTO_window(&target_machine, &format, &mode))
932 return;
933
934 if (target_machine == MACHINE_MO5 || target_machine == MACHINE_MO6)
935 {
936 reg_prc = 0xA7C0; // PRC : MO5/MO6 0xA7C0
937 prc_value = 0x51;
938 vram_address = 0;
939 transpose = 0;
940 }
941
942 vram_forme = GFX2_malloc(8192);
943 vram_couleur = GFX2_malloc(8192);
944 switch (mode)
945 {
946 case MOTO_MODE_40col:
947 {
948 /**
949 * The 40col encoding algorithm is optimized for further vertical
950 * RLE packing. The "attibute" byte is kept as constant as possible
951 * between adjacent blocks.
952 */
953 unsigned color_freq[16];
954 unsigned max_freq = 0;
955 byte previous_fond = 0, previous_forme = 0;
956 byte most_used_color = 0;
957
958 // search for most used color to prefer it as background color
959 for (i = 0; i < 16; i++)
960 color_freq[i] = 0;
961 for (y = 0; y < context->Height; y++)
962 {
963 for (x = 0; x < context->Width; x++)
964 {
965 byte col = Get_pixel(context, x, y);
966 if (col > 15)
967 {
968 Warning_with_format("color %u > 15 at pixel (%d,%d)", col, x, y);
969 goto error;
970 }
971 color_freq[col]++;
972 }
973 }
974 for (i = 0; i < 16; i++)
975 {
976 if (color_freq[i] > max_freq)
977 {
978 max_freq = color_freq[i];
979 most_used_color = (byte)i; // most used color
980 }
981 }
982 previous_fond = most_used_color;
983 max_freq = 0;
984 for (i = 0; i < 16; i++)
985 {
986 if (i != most_used_color && color_freq[i] > max_freq)
987 {
988 max_freq = color_freq[i];
989 previous_forme = (byte)i; // second most used color
990 }
991 }
992 GFX2_Log(GFX2_DEBUG, "Save_MOTO() most used color index %u, 2nd %u\n", previous_fond, previous_forme);
993
994 if (target_machine == MACHINE_MO5)
995 {
996 /**
997 * For MO5 we use a different 40col algorithm
998 * to make sure the last pixel of a GPL and the first the next
999 * are both FORME or both FOND, else we get an ugly glitch on the
1000 * EFGJ033 Gate Array MO5!
1001 */
1002 byte forme_byte = 0;
1003 byte couleur_byte = 0x10;
1004 GFX2_Log(GFX2_DEBUG, "Save_MOTO() 40col using MO5 algo\n");
1005 for (y = 0; y < context->Height; y++)
1006 {
1007 for (bx = 0; bx < 40; bx++)
1008 {
1009 byte fond = 0xff, forme = 0xff;
1010 forme_byte &= 1; // Last bit of the previous FORME byte
1011 x = bx*8;
1012 if (forme_byte)
1013 forme = Get_pixel(context, x, y);
1014 else
1015 fond = Get_pixel(context, x, y);
1016 while (++x < bx * 8 + 8)
1017 {
1018 byte col = Get_pixel(context, x, y);
1019 forme_byte <<= 1;
1020 if (col == forme)
1021 forme_byte |= 1;
1022 else if (col != fond)
1023 {
1024 if (forme == 0xff)
1025 {
1026 forme_byte |= 1;
1027 forme = col;
1028 }
1029 else if (fond == 0xff)
1030 fond = col;
1031 else
1032 {
1033 Warning_with_format("Constraint error at pixel (%d,%d)", x, y);
1034 goto error;
1035 }
1036 }
1037 }
1038 if (forme != 0xff)
1039 couleur_byte = (forme << 4) | (couleur_byte & 0x0f);
1040 if (fond != 0xff)
1041 couleur_byte = (couleur_byte & 0xf0) | fond;
1042 vram_forme[bx+y*40] = forme_byte;
1043 vram_couleur[bx+y*40] = couleur_byte;
1044 }
1045 }
1046 }
1047 else
1048 {
1049 GFX2_Log(GFX2_DEBUG, "Save_MOTO() 40col using optimized algo\n");
1050 // encoding of each 8x1 block
1051 for (bx = 0; bx < 40; bx++)
1052 {
1053 for (y = 0; y < context->Height; y++)
1054 {
1055 byte forme_byte = 1;
1056 byte col;
1057 byte c1, c1_count = 1;
1058 byte c2 = 0xff, c2_count = 0;
1059 byte fond, forme;
1060 x = bx * 8;
1061 c1 = Get_pixel(context, x, y);
1062 while (++x < bx * 8 + 8)
1063 {
1064 forme_byte <<= 1;
1065 col = Get_pixel(context, x, y);
1066 if (col > 15)
1067 {
1068 Warning_with_format("color %d > 15 at pixel (%d,%d)", col, x, y);
1069 goto error;
1070 }
1071 if (col == c1)
1072 {
1073 forme_byte |= 1;
1074 c1_count++;
1075 }
1076 else
1077 {
1078 c2_count++;
1079 if (c2 == 0xff)
1080 c2 = col;
1081 else if (col != c2)
1082 {
1083 Warning_with_format("constraint error at pixel (%d,%d)", x, y);
1084 goto error;
1085 }
1086 }
1087 }
1088 if (c2 == 0xff)
1089 {
1090 // Only one color in the 8x1 block
1091 if (c1 == previous_fond)
1092 c2 = previous_forme;
1093 else
1094 c2 = previous_fond;
1095 }
1096 // select background color (fond)
1097 // and foreground color (forme)
1098 if (c1 == previous_fond)
1099 {
1100 fond = c1;
1101 forme = c2;
1102 forme_byte = ~forme_byte;
1103 }
1104 else if (c2 == previous_fond)
1105 {
1106 fond = c2;
1107 forme = c1;
1108 }
1109 else if (c1 == most_used_color)
1110 {
1111 fond = c1;
1112 forme = c2;
1113 forme_byte = ~forme_byte;
1114 }
1115 else if (c2 == most_used_color)
1116 {
1117 fond = c2;
1118 forme = c1;
1119 }
1120 else if (c1_count >= c2_count)
1121 {
1122 fond = c1;
1123 forme = c2;
1124 forme_byte = ~forme_byte;
1125 }
1126 else
1127 {
1128 fond = c2;
1129 forme = c1;
1130 }
1131 // write to VRAM
1132 vram_forme[bx+y*40] = forme_byte;
1133 // transpose for TO7 compatibility
1134 if (transpose)
1135 vram_couleur[bx+y*40] = ((fond & 7) | ((fond & 8) << 4) | (forme << 3)) ^ 0xC0;
1136 else
1137 vram_couleur[bx+y*40] = fond | (forme << 4);
1138 previous_fond = fond;
1139 previous_forme = forme;
1140 }
1141 if (transpose)
1142 {
1143 previous_fond = (vram_couleur[bx] & 7) | (~vram_couleur[bx] & 0x80) >> 4;
1144 previous_forme = ((vram_couleur[bx] & 0x78) >> 3) ^ 8;
1145 }
1146 else
1147 {
1148 previous_fond = vram_couleur[bx] & 15;
1149 previous_forme = vram_couleur[bx] >> 4;
1150 }
1151 }
1152 }
1153 }
1154 break;
1155 case MOTO_MODE_80col:
1156 for (bx = 0; bx < context->Width / 16; bx++)
1157 {
1158 for (y = 0; y < context->Height; y++)
1159 {
1160 byte val = 0;
1161 for (x = bx * 16; x < bx*16 + 8; x++)
1162 val = (val << 1) | Get_pixel(context, x, y);
1163 vram_forme[y*(context->Width/16)+bx] = val;
1164 for (; x < bx*16 + 16; x++)
1165 val = (val << 1) | Get_pixel(context, x, y);
1166 vram_couleur[y*(context->Width/16)+bx] = val;
1167 }
1168 }
1169 break;
1170 case MOTO_MODE_bm4:
1171 for (y = 0; y < context->Height; y++)
1172 {
1173 for (bx = 0; bx < context->Width / 8; bx++)
1174 {
1175 byte val1 = 0, val2 = 0, pixel;
1176 for (x = bx * 8; x < bx*8 + 8; x++)
1177 {
1178 pixel = Get_pixel(context, x, y);
1179 if (pixel > 3)
1180 {
1181 Warning_with_format("color %d > 3 at pixel (%d,%d)", pixel, x, y);
1182 goto error;
1183 }
1184 val1 = (val1 << 1) | (pixel >> 1);
1185 val2 = (val2 << 1) | (pixel & 1);
1186 }
1187 vram_forme[y*(context->Width/8)+bx] = val1;
1188 vram_couleur[y*(context->Width/8)+bx] = val2;
1189 }
1190 }
1191 break;
1192 case MOTO_MODE_bm16:
1193 for (bx = 0; bx < context->Width / 4; bx++)
1194 {
1195 for (y = 0; y < context->Height; y++)
1196 {
1197 vram_forme[y*(context->Width/4)+bx] = (Get_pixel(context, bx*4, y) << 4) | Get_pixel(context, bx*4+1, y);
1198 vram_couleur[y*(context->Width/4)+bx] = (Get_pixel(context, bx*4+2, y) << 4) | Get_pixel(context, bx*4+3, y);
1199 }
1200 }
1201 break;
1202 }
1203 // palette
1204 for (i = 0; i < 16; i++)
1205 {
1206 word to8color = MOTO_gamma_correct_RGB_to_MOTO(context->Palette + i);
1207 vram_forme[8000+i*2] = to8color >> 8;
1208 vram_forme[8000+i*2+1] = to8color & 0xFF;
1209 }
1210
1211 file = Open_file_write(context);
1212 if (file == NULL)
1213 goto error;
1214
1215 if (format == 0) // BIN
1216 {
1217 word chunk_length;
1218
1219 if (target_machine == MACHINE_TO7 || target_machine == MACHINE_TO770 || target_machine == MACHINE_MO5)
1220 chunk_length = 8000; // Do not save palette
1221 else
1222 {
1223 chunk_length = 8000 + 32 + 32; // data + palette + comment
1224 // Commentaire
1225 if (context->Comment[0] != '\0')
1226 strncpy((char *)vram_forme + 8032, context->Comment, 32);
1227 else
1228 snprintf((char *)vram_forme + 8032, 32, "GrafX2 %s.%s", Program_version, SVN_revision);
1229 // also saves the video mode
1230 vram_forme[8063] = '0' + mode;
1231 memcpy(vram_couleur + 8000, vram_forme + 8000, 64);
1232 }
1233
1234 // Format BIN
1235 // TO8/TO9 : set LGAMOD 0xE7DC 40col=0 bm4=0x21 80col=0x2a bm16=0x7b
1236 if (!DECB_BIN_Add_Chunk(file, 1, reg_prc, &prc_value))
1237 goto error;
1238 if (!DECB_BIN_Add_Chunk(file, chunk_length, vram_address, vram_forme))
1239 goto error;
1240 prc_value &= 0xFE; // select color data
1241 if (!DECB_BIN_Add_Chunk(file, 1, reg_prc, &prc_value))
1242 goto error;
1243 if (!DECB_BIN_Add_Chunk(file, chunk_length, vram_address, vram_couleur))
1244 goto error;
1245 if (!DECB_BIN_Add_End(file, 0x0000))
1246 goto error;
1247 }
1248 else
1249 {
1250 // format MAP with TO-SNAP extensions
1251 byte * unpacked_data;
1252 byte * packed_data;
1253
1254 unpacked_data = GFX2_malloc(16*1024);
1255 packed_data = GFX2_malloc(16*1024);
1256 if (packed_data == NULL || unpacked_data == NULL)
1257 {
1258 GFX2_Log(GFX2_ERROR, "Failed to allocate 2x16kB of memory\n");
1259 free(packed_data);
1260 free(unpacked_data);
1261 goto error;
1262 }
1263 switch (mode)
1264 {
1265 case MOTO_MODE_40col:
1266 case MOTO_MODE_bm4:
1267 packed_data[0] = 0; // mode
1268 packed_data[1] = (context->Width / 8) - 1;
1269 break;
1270 case MOTO_MODE_80col:
1271 packed_data[0] = 0x80; // mode
1272 packed_data[1] = (context->Width / 8) - 1;
1273 break;
1274 case MOTO_MODE_bm16:
1275 packed_data[0] = 0x40; // mode
1276 packed_data[1] = (context->Width / 2) - 1;
1277 break;
1278 }
1279 packed_data[2] = (context->Height / 8) - 1;
1280 // 1st step : put data to pack in a linear buffer
1281 // 2nd step : pack data
1282 i = 0;
1283 switch (mode)
1284 {
1285 case MOTO_MODE_40col:
1286 case MOTO_MODE_bm4:
1287 for (bx = 0; bx <= packed_data[1]; bx++)
1288 {
1289 for (y = 0; y < context->Height; y++)
1290 {
1291 unpacked_data[i] = vram_forme[bx + y*(packed_data[1]+1)];
1292 unpacked_data[i+8192] = vram_couleur[bx + y*(packed_data[1]+1)];
1293 i++;
1294 }
1295 }
1296 i = 3;
1297 i += MOTO_MAP_pack(packed_data+3, unpacked_data, context->Height * (packed_data[1]+1));
1298 packed_data[i++] = 0; // ending of VRAM forme packing
1299 packed_data[i++] = 0;
1300 i += MOTO_MAP_pack(packed_data+i, unpacked_data + 8192, context->Height * (packed_data[1]+1));
1301 packed_data[i++] = 0; // ending of VRAM couleur packing
1302 packed_data[i++] = 0;
1303 break;
1304 case MOTO_MODE_80col:
1305 case MOTO_MODE_bm16:
1306 for (bx = 0; bx < (packed_data[1] + 1) / 2; bx++)
1307 {
1308 for (y = 0; y < context->Height; y++)
1309 unpacked_data[i++] = vram_forme[bx + y*(packed_data[1]+1)/2];
1310 for (y = 0; y < context->Height; y++)
1311 unpacked_data[i++] = vram_couleur[bx + y*(packed_data[1]+1)/2];
1312 }
1313 i = 3;
1314 i += MOTO_MAP_pack(packed_data+3, unpacked_data, context->Height * (packed_data[1]+1));
1315 packed_data[i++] = 0; // ending of VRAM forme packing
1316 packed_data[i++] = 0;
1317 packed_data[i++] = 0; // ending of VRAM couleur packing
1318 packed_data[i++] = 0;
1319 break;
1320 }
1321 if (i&1) // align
1322 packed_data[i++] = 0;
1323 if (target_machine != MACHINE_TO7 && target_machine != MACHINE_TO770 && target_machine != MACHINE_MO5)
1324 {
1325 // add TO-SNAP extension
1326 // see http://collection.thomson.free.fr/code/articles/prehisto_bulletin/page.php?XI=0&XJ=13
1327 // bytes 0-1 : Hardware video mode (value of SCRMOD 0x605F)
1328 packed_data[i++] = 0;
1329 switch (mode)
1330 {
1331 case MOTO_MODE_40col:
1332 packed_data[i++] = 0;
1333 break;
1334 case MOTO_MODE_bm4:
1335 packed_data[i++] = 0x01;
1336 break;
1337 case MOTO_MODE_80col:
1338 packed_data[i++] = 0x80;
1339 break;
1340 case MOTO_MODE_bm16:
1341 packed_data[i++] = 0x40;
1342 break;
1343 }
1344 // bytes 2-3 : Border color
1345 packed_data[i++] = 0;
1346 packed_data[i++] = 0;
1347 // bytes 4-5 : BASIC video mode (CONSOLE,,,,X)
1348 packed_data[i++] = 0;
1349 switch (mode)
1350 {
1351 case MOTO_MODE_40col:
1352 packed_data[i++] = 0;
1353 break;
1354 case MOTO_MODE_bm4:
1355 packed_data[i++] = 2;
1356 break;
1357 case MOTO_MODE_80col:
1358 packed_data[i++] = 1;
1359 break;
1360 case MOTO_MODE_bm16:
1361 packed_data[i++] = 3;
1362 break;
1363 }
1364 // bytes 6-37 : BGR palette
1365 for (x = 0; x < 16; x++)
1366 {
1367 word bgr = MOTO_gamma_correct_RGB_to_MOTO(context->Palette + x);
1368 packed_data[i++] = bgr >> 8;
1369 packed_data[i++] = bgr & 0xff;
1370 }
1371 // bytes 38-39 : TO-SNAP signature
1372 packed_data[i++] = 0xA5;
1373 packed_data[i++] = 0x5A;
1374 }
1375
1376 free(unpacked_data);
1377
1378 if (!DECB_BIN_Add_Chunk(file, i, 0, packed_data) ||
1379 !DECB_BIN_Add_End(file, 0x0000))
1380 {
1381 free(packed_data);
1382 goto error;
1383 }
1384 free(packed_data);
1385 }
1386 fclose(file);
1387 File_error = 0;
1388 return;
1389
1390 error:
1391 free(vram_forme);
1392 free(vram_couleur);
1393 if (file)
1394 fclose(file);
1395 File_error = 1;
1396 }
1397