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