This gives a much clearer overview of the licensing. It also shows there are some problems: - Some files are under GPLv3 only - Some files have no known license at all.
1397 lines
40 KiB
C
1397 lines
40 KiB
C
/* vim:expandtab:ts=2 sw=2:
|
|
*/
|
|
/* Grafx2 - The Ultimate 256-color bitmap paint program
|
|
|
|
Copyright owned by various GrafX2 authors, see COPYRIGHT.txt for details.
|
|
|
|
Grafx2 is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; version 2
|
|
of the License.
|
|
|
|
Grafx2 is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Grafx2; if not, see <http://www.gnu.org/licenses/>
|
|
*/
|
|
|
|
///@file motoformats.c
|
|
/// Formats for the MO/TO Thomson machines
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#if defined(_MSC_VER)
|
|
#define strdup _strdup
|
|
#endif
|
|
#include "struct.h"
|
|
#include "io.h"
|
|
#include "loadsave.h"
|
|
#include "loadsavefuncs.h"
|
|
#include "fileformats.h"
|
|
#include "oldies.h"
|
|
#include "input.h"
|
|
#include "engine.h"
|
|
#include "screen.h"
|
|
#include "windows.h"
|
|
#include "help.h"
|
|
#include "gfx2mem.h"
|
|
#include "gfx2log.h"
|
|
|
|
extern char Program_version[]; // generated in pversion.c
|
|
extern const char SVN_revision[]; // generated in version.c
|
|
|
|
/////////////////////////////// Thomson Files ///////////////////////////////
|
|
|
|
/**
|
|
* Test for Thomson file
|
|
*/
|
|
void Test_MOTO(T_IO_Context * context, FILE * file)
|
|
{
|
|
long file_size;
|
|
|
|
file_size = File_length_file(file);
|
|
|
|
File_error = 1;
|
|
if (file_size <= 10)
|
|
return;
|
|
switch (MOTO_Check_binary_file(file))
|
|
{
|
|
case 0: // Not Thomson binary format
|
|
switch (file_size)
|
|
{
|
|
// Files in RAW formats (from TGA2teo)
|
|
case 8004: // 2 colors palette
|
|
case 8008: // 4 colors palette
|
|
case 8032: // 16 colors palette
|
|
{
|
|
char * filename;
|
|
char * path;
|
|
char * ext;
|
|
|
|
// Check there are both FORME and COULEUR files
|
|
filename = strdup(context->File_name);
|
|
ext = strrchr(filename, '.');
|
|
if (ext == NULL || ext == filename)
|
|
{
|
|
free(filename);
|
|
return;
|
|
}
|
|
if ((ext[-1] | 32) == 'c')
|
|
ext[-1] = (ext[-1] & 32) | 'P';
|
|
else if ((ext[-1] | 32) == 'p')
|
|
ext[-1] = (ext[-1] & 32) | 'C';
|
|
else
|
|
{
|
|
free(filename);
|
|
return;
|
|
}
|
|
path = Filepath_append_to_dir(context->File_directory, filename);
|
|
if (File_exists(path))
|
|
File_error = 0;
|
|
free(path);
|
|
free(filename);
|
|
}
|
|
return;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case 2: // MAP file (SAVEP/LOADP)
|
|
case 3: // TO autoloading picture
|
|
case 4: // MO autoloading picture
|
|
File_error = 0;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load a picture for Thomson TO8/TO8D/TO9/TO9+/MO6
|
|
*
|
|
* One of the supported format is the one produced by TGA2Teo :
|
|
* - Picture data is splitted into 2 files, one for each VRAM bank :
|
|
* - The first VRAM bank is called "forme" (shape).
|
|
* In 40col mode it stores pixels.
|
|
* - The second VRAM bank is called "couleur" (color).
|
|
* In 40col mode it store color indexes for foreground and background.
|
|
* - File extension is .BIN, character before extension is "P" for the first
|
|
* file, and "C" for the second.
|
|
* - The color palette is stored in both files after the data.
|
|
*
|
|
* The mode is detected thanks to the number of color in the palette :
|
|
* - 2 colors is 80col (640x200)
|
|
* - 4 colors is bitmap4 (320x200 4 colors)
|
|
* - 16 colors is either bitmap16 (160x200 16colors)
|
|
* or 40col (320x200 16 colors with 2 unique colors in each 8x1 pixels
|
|
* block).
|
|
*
|
|
* As it is not possible to disriminate bitmap16 and 40col, opening the "P"
|
|
* file sets bitmap16, opening the "C" file sets 40col.
|
|
*
|
|
* This function also supports .MAP files (with optional TO-SNAP extension)
|
|
* and our own "autoloading" BIN files.
|
|
* See http://pulkomandy.tk/projects/GrafX2/wiki/Develop/FileFormats/MOTO for
|
|
* a detailled description.
|
|
*/
|
|
void Load_MOTO(T_IO_Context * context)
|
|
{
|
|
// FORME / COULEUR
|
|
FILE * file;
|
|
byte * vram_forme = NULL;
|
|
byte * vram_couleur = NULL;
|
|
long file_size;
|
|
int file_type;
|
|
int bx, x, y, i;
|
|
byte bpp = 4;
|
|
byte code;
|
|
word length, address;
|
|
int transpose = 1; // transpose the upper bits of the color plane bytes
|
|
// FFFFBBBB becomes bfFFFBBB (for TO7 compatibility)
|
|
enum MOTO_Graphic_Mode mode = MOTO_MODE_40col;
|
|
enum PIXEL_RATIO ratio = PIXEL_SIMPLE;
|
|
int width = 320, height = 200, columns = 40;
|
|
|
|
File_error = 1;
|
|
file = Open_file_read(context);
|
|
if (file == NULL)
|
|
return;
|
|
file_size = File_length_file(file);
|
|
// Load default palette
|
|
if (Config.Clear_palette)
|
|
memset(context->Palette,0,sizeof(T_Palette));
|
|
MOTO_set_TO7_palette(context->Palette);
|
|
|
|
file_type = MOTO_Check_binary_file(file);
|
|
if (fseek(file, 0, SEEK_SET) < 0)
|
|
{
|
|
fclose(file);
|
|
return;
|
|
}
|
|
|
|
if (file_type == 2) // MAP file
|
|
{
|
|
// http://collection.thomson.free.fr/code/articles/prehisto_bulletin/page.php?XI=0&XJ=13
|
|
byte map_mode, col_count, line_count;
|
|
byte * vram_current;
|
|
int end_marks;
|
|
|
|
if (!(Read_byte(file,&code) && Read_word_be(file,&length) && Read_word_be(file,&address)))
|
|
{
|
|
fclose(file);
|
|
return;
|
|
}
|
|
if (length < 5 || !(Read_byte(file,&map_mode) && Read_byte(file,&col_count) && Read_byte(file,&line_count)))
|
|
{
|
|
fclose(file);
|
|
return;
|
|
}
|
|
length -= 3;
|
|
columns = col_count + 1;
|
|
height = 8 * (line_count + 1);
|
|
switch(map_mode)
|
|
{
|
|
default:
|
|
case 0: // bitmap4 or 40col
|
|
width = 8 * columns;
|
|
mode = MOTO_MODE_40col; // default to 40col
|
|
bpp = 4;
|
|
break;
|
|
case 0x40: // bitmap16
|
|
columns >>= 1;
|
|
width = 4 * columns;
|
|
mode = MOTO_MODE_bm16;
|
|
bpp = 4;
|
|
ratio = PIXEL_WIDE;
|
|
break;
|
|
case 0x80: // 80col
|
|
columns >>= 1;
|
|
width = 16 * columns;
|
|
mode = MOTO_MODE_80col;
|
|
bpp = 1;
|
|
ratio = PIXEL_TALL;
|
|
break;
|
|
}
|
|
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);
|
|
vram_forme = GFX2_malloc(columns * height);
|
|
vram_couleur = GFX2_malloc(columns * height);
|
|
// Check extension (TO-SNAP / PPM / ???)
|
|
if (length > 36)
|
|
{
|
|
long pos_backup;
|
|
word data;
|
|
|
|
pos_backup = ftell(file);
|
|
fseek(file, length-2, SEEK_CUR); // go to last word of chunk
|
|
Read_word_be(file, &data);
|
|
GFX2_Log(GFX2_DEBUG, "%04X\n", data);
|
|
switch (data)
|
|
{
|
|
case 0xA55A: // TO-SNAP
|
|
fseek(file, -40, SEEK_CUR); // go to begin of extension
|
|
Read_word_be(file, &data); // SCRMOD. 0=>40col, 1=>bm4, $40=>bm16, $80=>80col
|
|
GFX2_Log(GFX2_DEBUG, "SCRMOD=&H%04X ", data);
|
|
Read_word_be(file, &data); // Border color
|
|
GFX2_Log(GFX2_DEBUG, "BORDER=%u ", data);
|
|
Read_word_be(file, &data); // Mode BASIC (CONSOLE,,,,X) 0=40col, 1=80col, 2=bm4, 3=bm16, etc.
|
|
GFX2_Log(GFX2_DEBUG, "CONSOLE,,,,%u\n", data);
|
|
if(data == 2)
|
|
{
|
|
mode = MOTO_MODE_bm4;
|
|
bpp = 2;
|
|
}
|
|
for (i = 0; i < 16; i++)
|
|
{
|
|
Read_word_be(file, &data); // Palette entry
|
|
if (data & 0x8000) data = ~data;
|
|
MOTO_gamma_correct_MOTO_to_RGB(&context->Palette[i], data);
|
|
}
|
|
snprintf(context->Comment, sizeof(context->Comment), "TO-SNAP .MAP file");
|
|
break;
|
|
case 0x484C: // 'HL' PPM
|
|
fseek(file, -36, SEEK_CUR); // go to begin of extension
|
|
for (i = 0; i < 16; i++)
|
|
{
|
|
Read_word_be(file, &data); // Palette entry
|
|
if (data & 0x8000) data = ~data;
|
|
MOTO_gamma_correct_MOTO_to_RGB(&context->Palette[i], data);
|
|
}
|
|
Read_word_be(file, &data); // Mode BASIC (CONSOLE,,,,X) 0=40col, 1=80col, 2=bm4, 3=bm16, etc.
|
|
GFX2_Log(GFX2_DEBUG, "CONSOLE,,,,%u\n", data);
|
|
if(data == 2)
|
|
{
|
|
mode = MOTO_MODE_bm4;
|
|
bpp = 2;
|
|
}
|
|
snprintf(context->Comment, sizeof(context->Comment), "PPM .MAP file");
|
|
break;
|
|
default:
|
|
snprintf(context->Comment, sizeof(context->Comment), "standard .MAP file");
|
|
}
|
|
fseek(file, pos_backup, SEEK_SET); // RESET Position
|
|
}
|
|
i = 0;
|
|
vram_current = vram_forme;
|
|
end_marks = 0;
|
|
while (length > 1)
|
|
{
|
|
byte byte1, byte2;
|
|
Read_byte(file,&byte1);
|
|
Read_byte(file,&byte2);
|
|
length-=2;
|
|
if(byte1 == 0)
|
|
{
|
|
if (byte2 == 0)
|
|
{
|
|
// end of vram stream
|
|
GFX2_Log(GFX2_DEBUG, "0000 i=%d length=%ld\n", i, length);
|
|
if (end_marks == 1)
|
|
break;
|
|
i = 0;
|
|
vram_current = vram_couleur;
|
|
end_marks++;
|
|
}
|
|
else while(byte2-- > 0 && length > 0) // copy
|
|
{
|
|
Read_byte(file,vram_current + i);
|
|
length--;
|
|
i += columns; // to the next line
|
|
if (i >= columns * height)
|
|
{
|
|
if (mode == MOTO_MODE_bm4 || mode == MOTO_MODE_40col)
|
|
i -= (columns * height - 1); // to the 1st line of the next column
|
|
else
|
|
{
|
|
i -= columns * height; // back to the 1st line of the current column
|
|
if (vram_current == vram_forme) // other VRAM
|
|
vram_current = vram_couleur;
|
|
else
|
|
{
|
|
vram_current = vram_forme;
|
|
i++; // next column
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else while(byte1-- > 0) // run length
|
|
{
|
|
vram_current[i] = byte2;
|
|
i += columns; // to the next line
|
|
if (i >= columns * height)
|
|
{
|
|
if (mode == MOTO_MODE_bm4 || mode == MOTO_MODE_40col)
|
|
i -= (columns * height - 1); // to the 1st line of the next column
|
|
else
|
|
{
|
|
i -= columns * height; // back to the 1st line of the current column
|
|
if (vram_current == vram_forme) // other VRAM
|
|
vram_current = vram_couleur;
|
|
else
|
|
{
|
|
vram_current = vram_forme;
|
|
i++; // next column
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
fclose(file);
|
|
}
|
|
else if(file_type == 3 || file_type == 4)
|
|
{
|
|
if (file_type == 4) // MO file
|
|
{
|
|
transpose = 0;
|
|
MOTO_set_MO5_palette(context->Palette);
|
|
}
|
|
|
|
do
|
|
{
|
|
if (!(Read_byte(file,&code) && Read_word_be(file,&length) && Read_word_be(file,&address)))
|
|
{
|
|
if (vram_forme)
|
|
break;
|
|
fclose(file);
|
|
return;
|
|
}
|
|
// MO5/MO6 VRAM address is &H0000
|
|
// TO7/TO8/TO9 VRAM addres is &H4000
|
|
if (length >= 8000 && length <= 8192 && (address == 0x4000 || address == 0))
|
|
{
|
|
if (vram_forme == NULL)
|
|
{
|
|
vram_forme = calloc(8192, 1);
|
|
Read_bytes(file, vram_forme, length);
|
|
length = 0;
|
|
}
|
|
else if (vram_couleur == NULL)
|
|
{
|
|
vram_couleur = calloc(8192, 1);
|
|
Read_bytes(file, vram_couleur, length);
|
|
if (length >= 8032)
|
|
{
|
|
for (x = 0; x < 16; x++)
|
|
{
|
|
// 1 byte Blue (4 lower bits)
|
|
// 1 byte Green (4 upper bits) / Red (4 lower bits)
|
|
MOTO_gamma_correct_MOTO_to_RGB(&context->Palette[x],
|
|
vram_couleur[8000+x*2]<<8 | vram_couleur[8000+x*2+1]);
|
|
}
|
|
if (length >= 8064)
|
|
{
|
|
memcpy(context->Comment, vram_couleur + 8032, 32);
|
|
if (vram_couleur[8063] >= '0' && vram_couleur[8063] <= '3')
|
|
mode = vram_couleur[8063] - '0';
|
|
}
|
|
context->Comment[COMMENT_SIZE] = '\0';
|
|
}
|
|
length = 0;
|
|
}
|
|
}
|
|
if (length > 0)
|
|
fseek(file, length, SEEK_CUR);
|
|
} while(code == 0);
|
|
fclose(file);
|
|
switch (mode)
|
|
{
|
|
case MOTO_MODE_40col: // default
|
|
break;
|
|
case MOTO_MODE_bm4:
|
|
bpp = 2;
|
|
break;
|
|
case MOTO_MODE_80col:
|
|
bpp = 1;
|
|
width = 640;
|
|
ratio = PIXEL_TALL;
|
|
break;
|
|
case MOTO_MODE_bm16:
|
|
width = 160;
|
|
ratio = PIXEL_WIDE;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
char * filename;
|
|
char * path;
|
|
char * ext;
|
|
int n_colors;
|
|
|
|
vram_forme = GFX2_malloc(file_size);
|
|
if (vram_forme == NULL)
|
|
{
|
|
fclose(file);
|
|
return;
|
|
}
|
|
if (!Read_bytes(file, vram_forme, file_size))
|
|
{
|
|
free(vram_forme);
|
|
fclose(file);
|
|
return;
|
|
}
|
|
n_colors = (file_size - 8000) / 2;
|
|
switch(n_colors)
|
|
{
|
|
case 16:
|
|
bpp = 4;
|
|
// 16 colors : either 40col or bm16 mode !
|
|
// select later
|
|
break;
|
|
case 4:
|
|
bpp = 2;
|
|
mode = MOTO_MODE_bm4;
|
|
break;
|
|
default:
|
|
bpp = 1;
|
|
mode = MOTO_MODE_80col;
|
|
width = 640;
|
|
ratio = PIXEL_TALL;
|
|
}
|
|
filename = strdup(context->File_name);
|
|
ext = strrchr(filename, '.');
|
|
if (ext == NULL || ext == filename)
|
|
{
|
|
free(vram_forme);
|
|
free(filename);
|
|
return;
|
|
}
|
|
if ((ext[-1] | 32) == 'c')
|
|
{
|
|
vram_couleur = vram_forme;
|
|
vram_forme = NULL;
|
|
ext[-1] = (ext[-1] & 32) | 'P';
|
|
}
|
|
else if ((ext[-1] | 32) == 'p')
|
|
{
|
|
ext[-1] = (ext[-1] & 32) | 'C';
|
|
if (n_colors == 16)
|
|
{
|
|
mode = MOTO_MODE_bm16;
|
|
width = 160;
|
|
ratio = PIXEL_WIDE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
free(vram_forme);
|
|
free(filename);
|
|
return;
|
|
}
|
|
path = Filepath_append_to_dir(context->File_directory, filename);
|
|
file = fopen(path, "rb");
|
|
if (file == NULL)
|
|
GFX2_Log(GFX2_ERROR, "Failed to open %s\n", path);
|
|
free(path);
|
|
free(filename);
|
|
if (vram_forme == NULL)
|
|
{
|
|
vram_forme = GFX2_malloc(file_size);
|
|
if (vram_forme == NULL)
|
|
{
|
|
free(vram_couleur);
|
|
fclose(file);
|
|
return;
|
|
}
|
|
Read_bytes(file,vram_forme,file_size);
|
|
}
|
|
else
|
|
{
|
|
vram_couleur = GFX2_malloc(file_size);
|
|
if (vram_couleur == NULL)
|
|
{
|
|
free(vram_forme);
|
|
fclose(file);
|
|
return;
|
|
}
|
|
Read_bytes(file,vram_couleur,file_size);
|
|
}
|
|
fclose(file);
|
|
GFX2_Log(GFX2_DEBUG, "MO/TO: %s,%s file_size=%ld n_colors=%d\n", context->File_name, filename, file_size, n_colors);
|
|
for (x = 0; x < n_colors; x++)
|
|
{
|
|
// 1 byte Blue (4 lower bits)
|
|
// 1 byte Green (4 upper bits) / Red (4 lower bits)
|
|
MOTO_gamma_correct_MOTO_to_RGB(&context->Palette[x],
|
|
vram_couleur[8000+x*2]<<8 | vram_couleur[8000+x*2+1]);
|
|
}
|
|
}
|
|
Pre_load(context, width, height, file_size, FORMAT_MOTO, ratio, bpp);
|
|
if (mode == MOTO_MODE_40col)
|
|
Set_image_mode(context, IMAGE_MODE_THOMSON);
|
|
File_error = 0;
|
|
i = 0;
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
for (bx = 0; bx < columns; bx++)
|
|
{
|
|
byte couleur_forme;
|
|
byte couleur_fond;
|
|
byte forme, couleurs;
|
|
|
|
forme = vram_forme[i];
|
|
if (vram_couleur)
|
|
couleurs = vram_couleur[i];
|
|
else
|
|
couleurs = (mode == MOTO_MODE_40col) ? 0x01 : 0x00;
|
|
i++;
|
|
switch(mode)
|
|
{
|
|
case MOTO_MODE_bm4:
|
|
for (x = bx*8; x < bx*8+8; x++)
|
|
{
|
|
Set_pixel(context, x, y, ((forme & 0x80) >> 6) | ((couleurs & 0x80) >> 7));
|
|
forme <<= 1;
|
|
couleurs <<= 1;
|
|
}
|
|
#if 0 // the following would be for the alternate bm4 mode
|
|
for (x = bx*8; x < bx*8+4; x++)
|
|
{
|
|
Set_pixel(context, x, y, couleurs >> 6);
|
|
couleurs <<= 2;
|
|
}
|
|
for (x = bx*8 + 4; x < bx*8+8; x++)
|
|
{
|
|
Set_pixel(context, x, y, forme >> 6);
|
|
forme <<= 2;
|
|
}
|
|
#endif
|
|
break;
|
|
case MOTO_MODE_bm16:
|
|
Set_pixel(context, bx*4, y, forme >> 4);
|
|
Set_pixel(context, bx*4+1, y, forme & 0x0F);
|
|
Set_pixel(context, bx*4+2, y, couleurs >> 4);
|
|
Set_pixel(context, bx*4+3, y, couleurs & 0x0F);
|
|
break;
|
|
case MOTO_MODE_80col:
|
|
for (x = bx*16; x < bx*16+8; x++)
|
|
{
|
|
Set_pixel(context, x, y, (forme & 0x80) >> 7);
|
|
Set_pixel(context, x+8, y, (couleurs & 0x80) >> 7);
|
|
forme <<= 1;
|
|
couleurs <<= 1;
|
|
}
|
|
break;
|
|
case MOTO_MODE_40col:
|
|
default:
|
|
if (transpose)
|
|
{
|
|
// the color plane byte is bfFFFBBB (for TO7 compatibility)
|
|
// with the upper bits of both foreground (forme) and
|
|
// background (fond) inverted.
|
|
couleur_forme = ((couleurs & 0x78) >> 3) ^ 0x08;
|
|
couleur_fond = ((couleurs & 7) | ((couleurs & 0x80) >> 4)) ^ 0x08;
|
|
}
|
|
else
|
|
{
|
|
// MO5 : the color plane byte is FFFFBBBB
|
|
couleur_forme = couleurs >> 4;
|
|
couleur_fond = couleurs & 0x0F;
|
|
}
|
|
for (x = bx*8; x < bx*8+8; x++)
|
|
{
|
|
Set_pixel(context, x, y, (forme & 0x80)?couleur_forme:couleur_fond);
|
|
forme <<= 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
free(vram_forme);
|
|
free(vram_couleur);
|
|
}
|
|
|
|
/**
|
|
* Pack a stream of byte in the format used by Thomson MO/TO MAP files.
|
|
*
|
|
* - 00 cc xx yy .. : encodes a "copy run" (cc = bytes to copy)
|
|
* - cc xx : encodes a "repeat run" (cc > 0 : count)
|
|
*/
|
|
//#define MOTO_MAP_NOPACKING
|
|
unsigned int MOTO_MAP_pack(byte * packed, const byte * unpacked, unsigned int unpacked_len)
|
|
{
|
|
unsigned int src;
|
|
unsigned int dst = 0;
|
|
unsigned int count;
|
|
#ifndef MOTO_MAP_NOPACKING
|
|
unsigned int repeat;
|
|
unsigned int i;
|
|
word * counts;
|
|
#endif
|
|
|
|
GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack(%p, %p, %u)\n", packed, unpacked, unpacked_len);
|
|
if (unpacked_len == 0)
|
|
return 0;
|
|
if (unpacked_len == 1)
|
|
{
|
|
packed[0] = 1;
|
|
packed[1] = unpacked[0];
|
|
return 2;
|
|
}
|
|
#ifdef MOTO_MAP_NOPACKING
|
|
// compression disabled
|
|
src = 0;
|
|
while ((unpacked_len - src) > 255)
|
|
{
|
|
packed[dst++] = 0;
|
|
packed[dst++] = 255;
|
|
memcpy(packed+dst, unpacked+src, 255);
|
|
dst += 255;
|
|
src += 255;
|
|
}
|
|
count = unpacked_len - src;
|
|
packed[dst++] = 0;
|
|
packed[dst++] = count;
|
|
memcpy(packed+dst, unpacked+src, count);
|
|
dst += count;
|
|
src += count;
|
|
return dst;
|
|
#else
|
|
counts = GFX2_malloc(sizeof(word) * (unpacked_len + 1));
|
|
i = 0;
|
|
repeat = (unpacked[0] == unpacked[1]);
|
|
count = 2;
|
|
src = 2;
|
|
// 1st step : count lenght of the Copy runs and Repeat runs
|
|
while (src < unpacked_len)
|
|
{
|
|
if (repeat)
|
|
{
|
|
if (unpacked[src-1] == unpacked[src])
|
|
count++;
|
|
else
|
|
{
|
|
// flush the repeat run
|
|
counts[i++] = count | 0x8000; // 0x8000 is the marker for repeat runs
|
|
count = 1;
|
|
repeat = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (unpacked[src-1] != unpacked[src])
|
|
count++;
|
|
else if (count == 1)
|
|
{
|
|
count++;
|
|
repeat = 1;
|
|
}
|
|
else
|
|
{
|
|
// flush the copy run
|
|
counts[i++] = (count-1) | (count == 2 ? 0x8000 : 0); // mark copy run of 1 as repeat of 1
|
|
count = 2;
|
|
repeat = 1;
|
|
}
|
|
}
|
|
src++;
|
|
}
|
|
// flush the last run
|
|
counts[i++] = ((repeat || count == 1) ? 0x8000 : 0) | count;
|
|
counts[i++] = 0; // end marker
|
|
// check consistency of counts
|
|
count = 0;
|
|
for (i = 0; counts[i] != 0; i++)
|
|
count += (counts[i] & ~0x8000);
|
|
if (count != unpacked_len)
|
|
GFX2_Log(GFX2_ERROR, "*** encoding error in MOTO_MAP_pack() *** count=%u unpacked_len=%u\n",
|
|
count, unpacked_len);
|
|
// output optimized packed stream
|
|
// repeat run are encoded cc xx
|
|
// copy run are encoded 00 cc xx xx xx xx
|
|
i = 0;
|
|
src = 0;
|
|
while (counts[i] != 0)
|
|
{
|
|
while (counts[i] & 0x8000) // repeat run
|
|
{
|
|
count = counts[i] & ~0x8000;
|
|
GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack() %4u %4u repeat %u times %02x\n", src, i, count, unpacked[src]);
|
|
while(count > 255)
|
|
{
|
|
packed[dst++] = 255;
|
|
packed[dst++] = unpacked[src];
|
|
count -= 255;
|
|
src += 255;
|
|
}
|
|
packed[dst++] = count;
|
|
packed[dst++] = unpacked[src];
|
|
src += count;
|
|
i++;
|
|
}
|
|
while (counts[i] != 0 && !(counts[i] & 0x8000)) // copy run
|
|
{
|
|
// calculate the "savings" of repeat runs between 2 copy run
|
|
int savings = 0;
|
|
unsigned int j;
|
|
GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack() %4u %4u copy %u bytes\n", src, i, counts[i]);
|
|
for (j = i + 1; counts[j] & 0x8000; j++) // check repeat runs until the next copy run
|
|
{
|
|
count = counts[j] & ~0x8000;
|
|
if (savings < 0 && (savings + (int)count - 2) > 0)
|
|
break;
|
|
savings += count - 2; // a repeat run outputs 2 bytes for count bytes of input
|
|
}
|
|
count = counts[i];
|
|
GFX2_Log(GFX2_DEBUG, " savings=%d i=%u j=%u (counts[j]=0x%04x)\n", savings, i, j, counts[j]);
|
|
if (savings < 2 && (j > i + 1))
|
|
{
|
|
unsigned int k;
|
|
if (counts[j] == 0) // go to the end of stream
|
|
{
|
|
for (k = i + 1; k < j; k++)
|
|
count += (counts[k] & ~0x8000);
|
|
GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack() src=%u extend copy from %u to %u\n", src, counts[i], count);
|
|
i = j - 1;
|
|
}
|
|
else
|
|
{
|
|
for (k = i + 1; k < j; k++)
|
|
count += (counts[k] & ~0x8000);
|
|
if (!(counts[j] & 0x8000))
|
|
{ // merge with the next copy run (and the repeat runs between)
|
|
GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack() src=%u merge savings=%d\n", src, savings);
|
|
i = j;
|
|
counts[i] += count;
|
|
continue;
|
|
}
|
|
else
|
|
{ // merge with the next few repeat runs
|
|
GFX2_Log(GFX2_DEBUG, "MOTO_MAP_pack() src=%u extends savings=%d\n", src, savings);
|
|
i = j - 1;
|
|
}
|
|
}
|
|
}
|
|
while (count > 255)
|
|
{
|
|
packed[dst++] = 0;
|
|
packed[dst++] = 255;
|
|
memcpy(packed+dst, unpacked+src, 255);
|
|
dst += 255;
|
|
src += 255;
|
|
count -= 255;
|
|
}
|
|
packed[dst++] = 0;
|
|
packed[dst++] = count;
|
|
memcpy(packed+dst, unpacked+src, count);
|
|
dst += count;
|
|
src += count;
|
|
i++;
|
|
}
|
|
}
|
|
free(counts);
|
|
return dst;
|
|
#endif
|
|
}
|
|
|
|
|
|
/**
|
|
* GUI window to choose Thomson MO/TO saving parameters
|
|
*
|
|
* @param[out] machine target machine
|
|
* @param[out] format file format (0 = BIN, 1 = MAP)
|
|
* @param[in,out] mode video mode @ref MOTO_Graphic_Mode
|
|
*/
|
|
static int Save_MOTO_window(enum MOTO_Machine_Type * machine, int * format, enum MOTO_Graphic_Mode * mode)
|
|
{
|
|
int button;
|
|
T_Dropdown_button * machine_dd;
|
|
T_Dropdown_button * format_dd;
|
|
T_Dropdown_button * mode_dd;
|
|
static const char * mode_list[] = { "40col", "80col", "bm4", "bm16" };
|
|
char text_info[24];
|
|
|
|
Open_window(200, 125, "Thomson MO/TO Saving");
|
|
Window_set_normal_button(110,100,80,15,"Save",1,1,KEY_RETURN); // 1
|
|
Window_set_normal_button(10,100,80,15,"Cancel",1,1,KEY_ESCAPE); // 2
|
|
|
|
Print_in_window(13,18,"Target Machine:",MC_Dark,MC_Light);
|
|
machine_dd = Window_set_dropdown_button(10,28,110,15,100,
|
|
(*mode == MOTO_MODE_40col) ? "TO7/TO7-70" : "TO9/TO8/TO9+",
|
|
1, 0, 1, LEFT_SIDE,0); // 3
|
|
if (*mode == MOTO_MODE_40col)
|
|
Window_dropdown_add_item(machine_dd, MACHINE_TO7, "TO7/TO7-70");
|
|
Window_dropdown_add_item(machine_dd, MACHINE_TO8, "TO9/TO8/TO9+");
|
|
if (*mode == MOTO_MODE_40col)
|
|
Window_dropdown_add_item(machine_dd, MACHINE_MO5, "MO5");
|
|
Window_dropdown_add_item(machine_dd, MACHINE_MO6, "MO6");
|
|
|
|
Print_in_window(13,46,"Format:",MC_Dark,MC_Light);
|
|
format_dd = Window_set_dropdown_button(10,56,110,15,92,"BIN",1, 0, 1, LEFT_SIDE,0); // 4
|
|
Window_dropdown_add_item(format_dd, 0, "BIN");
|
|
Window_dropdown_add_item(format_dd, 1, "MAP/TO-SNAP");
|
|
|
|
Print_in_window(136,46,"Mode:",MC_Dark,MC_Light);
|
|
mode_dd = Window_set_dropdown_button(136,56,54,15,44,mode_list[*mode],1, 0, 1, LEFT_SIDE,0); // 5
|
|
if (*mode == MOTO_MODE_40col)
|
|
Window_dropdown_add_item(mode_dd, *mode, mode_list[*mode]);
|
|
if (*mode == MOTO_MODE_80col)
|
|
Window_dropdown_add_item(mode_dd, *mode, mode_list[*mode]);
|
|
if (*mode == MOTO_MODE_40col)
|
|
Window_dropdown_add_item(mode_dd, MOTO_MODE_bm4, mode_list[MOTO_MODE_bm4]);
|
|
if (*mode == MOTO_MODE_bm16)
|
|
Window_dropdown_add_item(mode_dd, *mode, mode_list[*mode]);
|
|
|
|
Update_window_area(0,0,Window_width,Window_height);
|
|
Display_cursor();
|
|
do
|
|
{
|
|
button = Window_clicked_button();
|
|
if (Is_shortcut(Key, 0x100+BUTTON_HELP))
|
|
{
|
|
Key = 0;
|
|
Window_help(BUTTON_SAVE, "THOMSON MO/TO FORMAT");
|
|
}
|
|
else switch (button)
|
|
{
|
|
case 3:
|
|
*machine = (enum MOTO_Machine_Type)Window_attribute2;
|
|
break;
|
|
case 4:
|
|
*format = Window_attribute2;
|
|
break;
|
|
case 5:
|
|
*mode = (enum MOTO_Graphic_Mode)Window_attribute2;
|
|
break;
|
|
}
|
|
Hide_cursor();
|
|
//"ABCDEFGHIJKLMNOPQRSTUVW"
|
|
memset(text_info, ' ', 23);
|
|
text_info[23] = '\0';
|
|
if (*machine == MACHINE_TO7 || *machine == MACHINE_TO770 || *machine == MACHINE_MO5)
|
|
{
|
|
if (*mode != MOTO_MODE_40col)
|
|
snprintf(text_info, sizeof(text_info), "%s only supports 40col",
|
|
(*machine == MACHINE_MO5) ? "MO5" : "TO7");
|
|
else if (*format == 1)
|
|
strncpy(text_info, "No TO-SNAP extension. ", sizeof(text_info));
|
|
else
|
|
strncpy(text_info, "No palette to save. ", sizeof(text_info));
|
|
}
|
|
Print_in_window(9, 80, text_info, MC_Dark, MC_Light);
|
|
Display_cursor();
|
|
} while(button!=1 && button!=2);
|
|
|
|
Close_window();
|
|
Display_cursor();
|
|
return button==1;
|
|
}
|
|
|
|
/**
|
|
* Save a picture in MAP or BIN Thomson MO/TO file format.
|
|
*
|
|
* File format details :
|
|
* http://pulkomandy.tk/projects/GrafX2/wiki/Develop/FileFormats/MOTO
|
|
*/
|
|
void Save_MOTO(T_IO_Context * context)
|
|
{
|
|
int transpose = 1; // transpose upper bits in "couleur" vram
|
|
enum MOTO_Machine_Type target_machine = MACHINE_TO7;
|
|
int format = 0; // 0 = BIN, 1 = MAP
|
|
enum MOTO_Graphic_Mode mode;
|
|
FILE * file = NULL;
|
|
byte * vram_forme;
|
|
byte * vram_couleur;
|
|
int i, x, y, bx;
|
|
word reg_prc = 0xE7C3; // PRC : TO7/8/9 0xE7C3 ; MO5/MO6 0xA7C0
|
|
byte prc_value = 0x65;// Value to write to PRC to select VRAM bank
|
|
// MO5 : 0x51
|
|
word vram_address = 0x4000; // 4000 on TO7/TO8/TO9, 0000 on MO5/MO6
|
|
|
|
File_error = 1;
|
|
|
|
/**
|
|
* In the future we could support other resolution for .MAP
|
|
* format.
|
|
* And even in .BIN format, we could store less lines. */
|
|
if (context->Height != 200)
|
|
{
|
|
Warning_message("must be 640x200, 320x200 or 160x200");
|
|
return;
|
|
}
|
|
|
|
switch (context->Width)
|
|
{
|
|
case 160:
|
|
mode = MOTO_MODE_bm16;
|
|
target_machine = MACHINE_TO8;
|
|
break;
|
|
case 640:
|
|
mode = MOTO_MODE_80col;
|
|
target_machine = MACHINE_TO8;
|
|
break;
|
|
case 320:
|
|
mode = MOTO_MODE_40col; // or bm4
|
|
break;
|
|
default:
|
|
Warning_message("must be 640x200, 320x200 or 160x200");
|
|
return;
|
|
}
|
|
|
|
if (!Save_MOTO_window(&target_machine, &format, &mode))
|
|
return;
|
|
|
|
if (target_machine == MACHINE_MO5 || target_machine == MACHINE_MO6)
|
|
{
|
|
reg_prc = 0xA7C0; // PRC : MO5/MO6 0xA7C0
|
|
prc_value = 0x51;
|
|
vram_address = 0;
|
|
transpose = 0;
|
|
}
|
|
|
|
vram_forme = GFX2_malloc(8192);
|
|
vram_couleur = GFX2_malloc(8192);
|
|
switch (mode)
|
|
{
|
|
case MOTO_MODE_40col:
|
|
{
|
|
/**
|
|
* The 40col encoding algorithm is optimized for further vertical
|
|
* RLE packing. The "attibute" byte is kept as constant as possible
|
|
* between adjacent blocks.
|
|
*/
|
|
unsigned color_freq[16];
|
|
unsigned max_freq = 0;
|
|
byte previous_fond = 0, previous_forme = 0;
|
|
byte most_used_color = 0;
|
|
|
|
// search for most used color to prefer it as background color
|
|
for (i = 0; i < 16; i++)
|
|
color_freq[i] = 0;
|
|
for (y = 0; y < context->Height; y++)
|
|
{
|
|
for (x = 0; x < context->Width; x++)
|
|
{
|
|
byte col = Get_pixel(context, x, y);
|
|
if (col > 15)
|
|
{
|
|
Warning_with_format("color %u > 15 at pixel (%d,%d)", col, x, y);
|
|
goto error;
|
|
}
|
|
color_freq[col]++;
|
|
}
|
|
}
|
|
for (i = 0; i < 16; i++)
|
|
{
|
|
if (color_freq[i] > max_freq)
|
|
{
|
|
max_freq = color_freq[i];
|
|
most_used_color = (byte)i; // most used color
|
|
}
|
|
}
|
|
previous_fond = most_used_color;
|
|
max_freq = 0;
|
|
for (i = 0; i < 16; i++)
|
|
{
|
|
if (i != most_used_color && color_freq[i] > max_freq)
|
|
{
|
|
max_freq = color_freq[i];
|
|
previous_forme = (byte)i; // second most used color
|
|
}
|
|
}
|
|
GFX2_Log(GFX2_DEBUG, "Save_MOTO() most used color index %u, 2nd %u\n", previous_fond, previous_forme);
|
|
|
|
if (target_machine == MACHINE_MO5)
|
|
{
|
|
/**
|
|
* For MO5 we use a different 40col algorithm
|
|
* to make sure the last pixel of a GPL and the first the next
|
|
* are both FORME or both FOND, else we get an ugly glitch on the
|
|
* EFGJ033 Gate Array MO5!
|
|
*/
|
|
byte forme_byte = 0;
|
|
byte couleur_byte = 0x10;
|
|
GFX2_Log(GFX2_DEBUG, "Save_MOTO() 40col using MO5 algo\n");
|
|
for (y = 0; y < context->Height; y++)
|
|
{
|
|
for (bx = 0; bx < 40; bx++)
|
|
{
|
|
byte fond = 0xff, forme = 0xff;
|
|
forme_byte &= 1; // Last bit of the previous FORME byte
|
|
x = bx*8;
|
|
if (forme_byte)
|
|
forme = Get_pixel(context, x, y);
|
|
else
|
|
fond = Get_pixel(context, x, y);
|
|
while (++x < bx * 8 + 8)
|
|
{
|
|
byte col = Get_pixel(context, x, y);
|
|
forme_byte <<= 1;
|
|
if (col == forme)
|
|
forme_byte |= 1;
|
|
else if (col != fond)
|
|
{
|
|
if (forme == 0xff)
|
|
{
|
|
forme_byte |= 1;
|
|
forme = col;
|
|
}
|
|
else if (fond == 0xff)
|
|
fond = col;
|
|
else
|
|
{
|
|
Warning_with_format("Constraint error at pixel (%d,%d)", x, y);
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
if (forme != 0xff)
|
|
couleur_byte = (forme << 4) | (couleur_byte & 0x0f);
|
|
if (fond != 0xff)
|
|
couleur_byte = (couleur_byte & 0xf0) | fond;
|
|
vram_forme[bx+y*40] = forme_byte;
|
|
vram_couleur[bx+y*40] = couleur_byte;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GFX2_Log(GFX2_DEBUG, "Save_MOTO() 40col using optimized algo\n");
|
|
// encoding of each 8x1 block
|
|
for (bx = 0; bx < 40; bx++)
|
|
{
|
|
for (y = 0; y < context->Height; y++)
|
|
{
|
|
byte forme_byte = 1;
|
|
byte col;
|
|
byte c1, c1_count = 1;
|
|
byte c2 = 0xff, c2_count = 0;
|
|
byte fond, forme;
|
|
x = bx * 8;
|
|
c1 = Get_pixel(context, x, y);
|
|
while (++x < bx * 8 + 8)
|
|
{
|
|
forme_byte <<= 1;
|
|
col = Get_pixel(context, x, y);
|
|
if (col > 15)
|
|
{
|
|
Warning_with_format("color %d > 15 at pixel (%d,%d)", col, x, y);
|
|
goto error;
|
|
}
|
|
if (col == c1)
|
|
{
|
|
forme_byte |= 1;
|
|
c1_count++;
|
|
}
|
|
else
|
|
{
|
|
c2_count++;
|
|
if (c2 == 0xff)
|
|
c2 = col;
|
|
else if (col != c2)
|
|
{
|
|
Warning_with_format("constraint error at pixel (%d,%d)", x, y);
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
if (c2 == 0xff)
|
|
{
|
|
// Only one color in the 8x1 block
|
|
if (c1 == previous_fond)
|
|
c2 = previous_forme;
|
|
else
|
|
c2 = previous_fond;
|
|
}
|
|
// select background color (fond)
|
|
// and foreground color (forme)
|
|
if (c1 == previous_fond)
|
|
{
|
|
fond = c1;
|
|
forme = c2;
|
|
forme_byte = ~forme_byte;
|
|
}
|
|
else if (c2 == previous_fond)
|
|
{
|
|
fond = c2;
|
|
forme = c1;
|
|
}
|
|
else if (c1 == most_used_color)
|
|
{
|
|
fond = c1;
|
|
forme = c2;
|
|
forme_byte = ~forme_byte;
|
|
}
|
|
else if (c2 == most_used_color)
|
|
{
|
|
fond = c2;
|
|
forme = c1;
|
|
}
|
|
else if (c1_count >= c2_count)
|
|
{
|
|
fond = c1;
|
|
forme = c2;
|
|
forme_byte = ~forme_byte;
|
|
}
|
|
else
|
|
{
|
|
fond = c2;
|
|
forme = c1;
|
|
}
|
|
// write to VRAM
|
|
vram_forme[bx+y*40] = forme_byte;
|
|
// transpose for TO7 compatibility
|
|
if (transpose)
|
|
vram_couleur[bx+y*40] = ((fond & 7) | ((fond & 8) << 4) | (forme << 3)) ^ 0xC0;
|
|
else
|
|
vram_couleur[bx+y*40] = fond | (forme << 4);
|
|
previous_fond = fond;
|
|
previous_forme = forme;
|
|
}
|
|
if (transpose)
|
|
{
|
|
previous_fond = (vram_couleur[bx] & 7) | (~vram_couleur[bx] & 0x80) >> 4;
|
|
previous_forme = ((vram_couleur[bx] & 0x78) >> 3) ^ 8;
|
|
}
|
|
else
|
|
{
|
|
previous_fond = vram_couleur[bx] & 15;
|
|
previous_forme = vram_couleur[bx] >> 4;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case MOTO_MODE_80col:
|
|
for (bx = 0; bx < context->Width / 16; bx++)
|
|
{
|
|
for (y = 0; y < context->Height; y++)
|
|
{
|
|
byte val = 0;
|
|
for (x = bx * 16; x < bx*16 + 8; x++)
|
|
val = (val << 1) | Get_pixel(context, x, y);
|
|
vram_forme[y*(context->Width/16)+bx] = val;
|
|
for (; x < bx*16 + 16; x++)
|
|
val = (val << 1) | Get_pixel(context, x, y);
|
|
vram_couleur[y*(context->Width/16)+bx] = val;
|
|
}
|
|
}
|
|
break;
|
|
case MOTO_MODE_bm4:
|
|
for (y = 0; y < context->Height; y++)
|
|
{
|
|
for (bx = 0; bx < context->Width / 8; bx++)
|
|
{
|
|
byte val1 = 0, val2 = 0, pixel;
|
|
for (x = bx * 8; x < bx*8 + 8; x++)
|
|
{
|
|
pixel = Get_pixel(context, x, y);
|
|
if (pixel > 3)
|
|
{
|
|
Warning_with_format("color %d > 3 at pixel (%d,%d)", pixel, x, y);
|
|
goto error;
|
|
}
|
|
val1 = (val1 << 1) | (pixel >> 1);
|
|
val2 = (val2 << 1) | (pixel & 1);
|
|
}
|
|
vram_forme[y*(context->Width/8)+bx] = val1;
|
|
vram_couleur[y*(context->Width/8)+bx] = val2;
|
|
}
|
|
}
|
|
break;
|
|
case MOTO_MODE_bm16:
|
|
for (bx = 0; bx < context->Width / 4; bx++)
|
|
{
|
|
for (y = 0; y < context->Height; y++)
|
|
{
|
|
vram_forme[y*(context->Width/4)+bx] = (Get_pixel(context, bx*4, y) << 4) | Get_pixel(context, bx*4+1, y);
|
|
vram_couleur[y*(context->Width/4)+bx] = (Get_pixel(context, bx*4+2, y) << 4) | Get_pixel(context, bx*4+3, y);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
// palette
|
|
for (i = 0; i < 16; i++)
|
|
{
|
|
word to8color = MOTO_gamma_correct_RGB_to_MOTO(context->Palette + i);
|
|
vram_forme[8000+i*2] = to8color >> 8;
|
|
vram_forme[8000+i*2+1] = to8color & 0xFF;
|
|
}
|
|
|
|
file = Open_file_write(context);
|
|
if (file == NULL)
|
|
goto error;
|
|
|
|
if (format == 0) // BIN
|
|
{
|
|
word chunk_length;
|
|
|
|
if (target_machine == MACHINE_TO7 || target_machine == MACHINE_TO770 || target_machine == MACHINE_MO5)
|
|
chunk_length = 8000; // Do not save palette
|
|
else
|
|
{
|
|
chunk_length = 8000 + 32 + 32; // data + palette + comment
|
|
// Commentaire
|
|
if (context->Comment[0] != '\0')
|
|
strncpy((char *)vram_forme + 8032, context->Comment, 32);
|
|
else
|
|
snprintf((char *)vram_forme + 8032, 32, "GrafX2 %s.%s", Program_version, SVN_revision);
|
|
// also saves the video mode
|
|
vram_forme[8063] = '0' + mode;
|
|
memcpy(vram_couleur + 8000, vram_forme + 8000, 64);
|
|
}
|
|
|
|
// Format BIN
|
|
// TO8/TO9 : set LGAMOD 0xE7DC 40col=0 bm4=0x21 80col=0x2a bm16=0x7b
|
|
if (!DECB_BIN_Add_Chunk(file, 1, reg_prc, &prc_value))
|
|
goto error;
|
|
if (!DECB_BIN_Add_Chunk(file, chunk_length, vram_address, vram_forme))
|
|
goto error;
|
|
prc_value &= 0xFE; // select color data
|
|
if (!DECB_BIN_Add_Chunk(file, 1, reg_prc, &prc_value))
|
|
goto error;
|
|
if (!DECB_BIN_Add_Chunk(file, chunk_length, vram_address, vram_couleur))
|
|
goto error;
|
|
if (!DECB_BIN_Add_End(file, 0x0000))
|
|
goto error;
|
|
}
|
|
else
|
|
{
|
|
// format MAP with TO-SNAP extensions
|
|
byte * unpacked_data;
|
|
byte * packed_data;
|
|
|
|
unpacked_data = GFX2_malloc(16*1024);
|
|
packed_data = GFX2_malloc(16*1024);
|
|
if (packed_data == NULL || unpacked_data == NULL)
|
|
{
|
|
GFX2_Log(GFX2_ERROR, "Failed to allocate 2x16kB of memory\n");
|
|
free(packed_data);
|
|
free(unpacked_data);
|
|
goto error;
|
|
}
|
|
switch (mode)
|
|
{
|
|
case MOTO_MODE_40col:
|
|
case MOTO_MODE_bm4:
|
|
packed_data[0] = 0; // mode
|
|
packed_data[1] = (context->Width / 8) - 1;
|
|
break;
|
|
case MOTO_MODE_80col:
|
|
packed_data[0] = 0x80; // mode
|
|
packed_data[1] = (context->Width / 8) - 1;
|
|
break;
|
|
case MOTO_MODE_bm16:
|
|
packed_data[0] = 0x40; // mode
|
|
packed_data[1] = (context->Width / 2) - 1;
|
|
break;
|
|
}
|
|
packed_data[2] = (context->Height / 8) - 1;
|
|
// 1st step : put data to pack in a linear buffer
|
|
// 2nd step : pack data
|
|
i = 0;
|
|
switch (mode)
|
|
{
|
|
case MOTO_MODE_40col:
|
|
case MOTO_MODE_bm4:
|
|
for (bx = 0; bx <= packed_data[1]; bx++)
|
|
{
|
|
for (y = 0; y < context->Height; y++)
|
|
{
|
|
unpacked_data[i] = vram_forme[bx + y*(packed_data[1]+1)];
|
|
unpacked_data[i+8192] = vram_couleur[bx + y*(packed_data[1]+1)];
|
|
i++;
|
|
}
|
|
}
|
|
i = 3;
|
|
i += MOTO_MAP_pack(packed_data+3, unpacked_data, context->Height * (packed_data[1]+1));
|
|
packed_data[i++] = 0; // ending of VRAM forme packing
|
|
packed_data[i++] = 0;
|
|
i += MOTO_MAP_pack(packed_data+i, unpacked_data + 8192, context->Height * (packed_data[1]+1));
|
|
packed_data[i++] = 0; // ending of VRAM couleur packing
|
|
packed_data[i++] = 0;
|
|
break;
|
|
case MOTO_MODE_80col:
|
|
case MOTO_MODE_bm16:
|
|
for (bx = 0; bx < (packed_data[1] + 1) / 2; bx++)
|
|
{
|
|
for (y = 0; y < context->Height; y++)
|
|
unpacked_data[i++] = vram_forme[bx + y*(packed_data[1]+1)/2];
|
|
for (y = 0; y < context->Height; y++)
|
|
unpacked_data[i++] = vram_couleur[bx + y*(packed_data[1]+1)/2];
|
|
}
|
|
i = 3;
|
|
i += MOTO_MAP_pack(packed_data+3, unpacked_data, context->Height * (packed_data[1]+1));
|
|
packed_data[i++] = 0; // ending of VRAM forme packing
|
|
packed_data[i++] = 0;
|
|
packed_data[i++] = 0; // ending of VRAM couleur packing
|
|
packed_data[i++] = 0;
|
|
break;
|
|
}
|
|
if (i&1) // align
|
|
packed_data[i++] = 0;
|
|
if (target_machine != MACHINE_TO7 && target_machine != MACHINE_TO770 && target_machine != MACHINE_MO5)
|
|
{
|
|
// add TO-SNAP extension
|
|
// see http://collection.thomson.free.fr/code/articles/prehisto_bulletin/page.php?XI=0&XJ=13
|
|
// bytes 0-1 : Hardware video mode (value of SCRMOD 0x605F)
|
|
packed_data[i++] = 0;
|
|
switch (mode)
|
|
{
|
|
case MOTO_MODE_40col:
|
|
packed_data[i++] = 0;
|
|
break;
|
|
case MOTO_MODE_bm4:
|
|
packed_data[i++] = 0x01;
|
|
break;
|
|
case MOTO_MODE_80col:
|
|
packed_data[i++] = 0x80;
|
|
break;
|
|
case MOTO_MODE_bm16:
|
|
packed_data[i++] = 0x40;
|
|
break;
|
|
}
|
|
// bytes 2-3 : Border color
|
|
packed_data[i++] = 0;
|
|
packed_data[i++] = 0;
|
|
// bytes 4-5 : BASIC video mode (CONSOLE,,,,X)
|
|
packed_data[i++] = 0;
|
|
switch (mode)
|
|
{
|
|
case MOTO_MODE_40col:
|
|
packed_data[i++] = 0;
|
|
break;
|
|
case MOTO_MODE_bm4:
|
|
packed_data[i++] = 2;
|
|
break;
|
|
case MOTO_MODE_80col:
|
|
packed_data[i++] = 1;
|
|
break;
|
|
case MOTO_MODE_bm16:
|
|
packed_data[i++] = 3;
|
|
break;
|
|
}
|
|
// bytes 6-37 : BGR palette
|
|
for (x = 0; x < 16; x++)
|
|
{
|
|
word bgr = MOTO_gamma_correct_RGB_to_MOTO(context->Palette + x);
|
|
packed_data[i++] = bgr >> 8;
|
|
packed_data[i++] = bgr & 0xff;
|
|
}
|
|
// bytes 38-39 : TO-SNAP signature
|
|
packed_data[i++] = 0xA5;
|
|
packed_data[i++] = 0x5A;
|
|
}
|
|
|
|
free(unpacked_data);
|
|
|
|
if (!DECB_BIN_Add_Chunk(file, i, 0, packed_data) ||
|
|
!DECB_BIN_Add_End(file, 0x0000))
|
|
{
|
|
free(packed_data);
|
|
goto error;
|
|
}
|
|
free(packed_data);
|
|
}
|
|
fclose(file);
|
|
File_error = 0;
|
|
return;
|
|
|
|
error:
|
|
free(vram_forme);
|
|
free(vram_couleur);
|
|
if (file)
|
|
fclose(file);
|
|
File_error = 1;
|
|
}
|