diff --git a/src/miscfileformats.c b/src/miscfileformats.c index 10fd7c50..25515d68 100644 --- a/src/miscfileformats.c +++ b/src/miscfileformats.c @@ -51,6 +51,8 @@ #include "oldies.h" #include "pages.h" #include "keycodes.h" +#include "input.h" +#include "help.h" #include "fileformats.h" extern char Program_version[]; // generated in pversion.c @@ -4935,11 +4937,13 @@ void Load_MOTO(T_IO_Context * context) byte bpp = 4; byte code; word length, address; + int transpose = 1; enum MOTO_mode { F_40col, F_80col, F_bm4, F_bm16 } mode = F_40col; enum PIXEL_RATIO ratio = PIXEL_SIMPLE; int width = 320, height = 200, columns = 40; static const unsigned char mo5palette[48] = { // Taken from https://16couleurs.wordpress.com/2013/03/31/archeologie-infographique-le-pixel-art-pour-thomson/ + // http://pulkomandy.tk/wiki/doku.php?id=documentations:devices:gate.arrays#video_generation 0, 0, 0, 255, 85, 85, 0, 255, 0, 255, 255, 0, 85, 85, 255, 255, 0, 255, 0, 255, 255, 255, 255, 255, 170, 170, 170, 255, 170, 255, 170, 255, 170, 255, 255, 170, @@ -4952,6 +4956,8 @@ void Load_MOTO(T_IO_Context * context) return; file_size = File_length_file(file); // Load default palette + if (Config.Clear_palette) + memset(context->Palette,0,sizeof(T_Palette)); memcpy(context->Palette, mo5palette, sizeof(mo5palette)); file_type = MOTO_Check_binary_file(file); @@ -4961,7 +4967,6 @@ void Load_MOTO(T_IO_Context * context) return; } - if (file_type == 2) // MAP file { // http://collection.thomson.free.fr/code/articles/prehisto_bulletin/page.php?XI=0&XJ=13 @@ -5133,6 +5138,9 @@ void Load_MOTO(T_IO_Context * context) } else if(file_type == 3 || file_type == 4) { + if (file_type == 4) // MO file + transpose = 0; + do { if (!(Read_byte(file,&code) && Read_word_be(file,&length) && Read_word_be(file,&address))) @@ -5142,9 +5150,9 @@ void Load_MOTO(T_IO_Context * context) fclose(file); return; } - // MO5 VRAM address is &H0000 + // MO5/MO6 VRAM address is &H0000 // TO7/TO8/TO9 VRAM addres is &H4000 - if (length >= 8000 && length <= 8192 && address == 0x4000) // TO7/TO8/TO9 VRAM address + if (length >= 8000 && length <= 8192 && (address == 0x4000 || address == 0)) { if (vram_forme == NULL) { @@ -5333,11 +5341,20 @@ void Load_MOTO(T_IO_Context * context) break; case F_40col: default: - // the color plane byte is bfFFFBBB - // 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; + 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); @@ -5348,25 +5365,139 @@ void Load_MOTO(T_IO_Context * context) } } +/** + * 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" }; + + 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, NULL); + } + 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; + } + } while(button!=1 && button!=2); + + Close_window(); + Display_cursor(); + return button==1; +} + 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; byte * vram_forme; byte * vram_couleur; int i, x, y, bx; - word reg_prc = 0xE7C3; // TO8 : PRC + 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; + + if (context->Height != 200) + { + Warning_message("must be 640x200, 320x200 or 160x200"); + return; + } + + switch (context->Width) + { + case 160: + mode = MOTO_MODE_bm16; + break; + case 640: + mode = MOTO_MODE_80col; + 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; + } file = Open_file_write(context); if (file == NULL ) - { - File_error = 1; return; - } vram_forme = malloc(8192); vram_couleur = malloc(8192); + switch (mode) { - // 40col + case MOTO_MODE_40col: { /** * The 40col encoding algorithm is optimized for further vertical @@ -5413,92 +5544,172 @@ void Save_MOTO(T_IO_Context * context) } } GFX2_Log(GFX2_DEBUG, "Save_MOTO() most used color index %u, 2nd %u\n", previous_fond, previous_forme); - // encoding of each 8x1 block - for (bx = 0; bx < 40; bx++) + + 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++) { - 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) + for (bx = 0; bx < 40; bx++) { - forme_byte <<= 1; - col = Get_pixel(context, x, y); - if (col == c1) - { - forme_byte |= 1; - c1_count++; - } + 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) { - c2_count++; - if (c2 == 0xff) - c2 = col; - else if (col != c2) + byte col = Get_pixel(context, x, y); + forme_byte <<= 1; + if (col == forme) + forme_byte |= 1; + else if (col != fond) { - GFX2_Log(GFX2_WARNING, "Save_MOTO() constraint error (%d,%d)\n", x, y); - goto error; + if (forme == 0xff) + { + forme_byte |= 1; + forme = col; + } + else if (fond == 0xff) + fond = col; + else + { + GFX2_Log(GFX2_WARNING, "Save_MOTO() constraint error (%d,%d)\n", 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; } - if (c2 == 0xff) + } + } + 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++) { - // Only one color in the 8x1 block + 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 == c1) + { + forme_byte |= 1; + c1_count++; + } + else + { + c2_count++; + if (c2 == 0xff) + c2 = col; + else if (col != c2) + { + GFX2_Log(GFX2_WARNING, "Save_MOTO() constraint error (%d,%d)\n", 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) - c2 = previous_forme; + { + 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 - c2 = previous_fond; + { + 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; } - // select background color (fond) - // and foreground color (forme) - if (c1 == previous_fond) + if (transpose) { - 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; + previous_fond = (vram_couleur[bx] & 7) | (~vram_couleur[bx] & 0x80) >> 4; + previous_forme = ((vram_couleur[bx] & 0x78) >> 3) ^ 8; } else { - fond = c2; - forme = c1; + previous_fond = vram_couleur[bx] & 15; + previous_forme = vram_couleur[bx] >> 4; } - // write to VRAM - vram_forme[bx+y*40] = forme_byte; - vram_couleur[bx+y*40] = ((fond & 7) | ((fond & 8) << 4) | (forme << 3)) ^ 0xC0; - previous_fond = fond; - previous_forme = forme; } - previous_fond = (vram_couleur[bx-1] & 7) | (~vram_couleur[bx-1] & 0x80) >> 4; - previous_forme = ((vram_couleur[bx-1] & 0x78) >> 3) ^ 8; } } + break; + case MOTO_MODE_80col: + GFX2_Log(GFX2_WARNING, "80col not implemented!\n"); + break; + case MOTO_MODE_bm4: + GFX2_Log(GFX2_WARNING, "bm4 not implemented!\n"); + break; + case MOTO_MODE_bm16: + GFX2_Log(GFX2_WARNING, "bm16 not implemented!\n"); + break; } // palette for (i = 0; i < 16; i++) @@ -5507,26 +5718,41 @@ void Save_MOTO(T_IO_Context * context) vram_forme[8000+i*2] = to8color >> 8; vram_forme[8000+i*2+1] = to8color & 0xFF; } - // Commentaire - if (context->Comment[0] != '\0') - strncpy((char *)vram_forme + 8032, context->Comment, 32); + if (1) + { + word chunk_length; + + if (target_machine == MACHINE_TO7 || 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); + 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 - snprintf((char *)vram_forme + 8032, 32, "GrafX2 %s.%s", Program_version, SVN_revision); - 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, 8000+64, 0x4000, 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, 8000+64, 0x4000, vram_couleur)) - goto error; - if (!DECB_BIN_Add_End(file, 0x0000)) - goto error; - // TODO : format MAP + { + // TODO : format MAP + } fclose(file); File_error = 0; return; diff --git a/src/oldies.c b/src/oldies.c index 8913732f..68fc4c54 100644 --- a/src/oldies.c +++ b/src/oldies.c @@ -420,12 +420,11 @@ int C64_FLI_enforcer(void) return 0; } -/// @ingroup moto -int MOTO_Check_binary_file(FILE * f) +int DECB_Check_binary_file(FILE * f) { - int type = 1; // BIN byte code; word length, address; + long end; // Check end of file if (fseek(f, -5, SEEK_END) < 0) @@ -434,6 +433,36 @@ int MOTO_Check_binary_file(FILE * f) return 0; if (code != 0xff || length != 0) return 0; + end = ftell(f); + if (end < 0) + return 0; + if (fseek(f, 0, SEEK_SET) < 0) + return 0; + // Read Binary structure + do + { + if (!(Read_byte(f, &code) && Read_word_be(f, &length) && Read_word_be(f, &address))) + return 0; + // skip chunk content + if (fseek(f, length, SEEK_CUR) < 0) + return 0; + } + while(code == 0); + if (code != 0xff) + return 0; + if (ftell(f) != end) + return 0; + return 1; +} + +int MOTO_Check_binary_file(FILE * f) +{ + int type = 1; // BIN + byte code; + word length, address; + + if (!DECB_Check_binary_file(f)) + return 0; if (fseek(f, 0, SEEK_SET) < 0) return 0; // Read Binary structure @@ -452,7 +481,7 @@ int MOTO_Check_binary_file(FILE * f) if (!Read_bytes(f, map_header, 3)) return 0; length -= 3; - if ((map_header[0] & 0x3F) == 0 && (map_header[0] & 0xC0) != 0xC0) + if ((map_header[0] & 0x3F) == 0 && (map_header[0] & 0xC0) != 0xC0) // 00, 40 or 80 { GFX2_Log(GFX2_DEBUG, "Thomson MAP &H%02X %u %u\n", map_header[0], map_header[1], map_header[2]); if (((map_header[1] < 80 && map_header[0] != 0) // <= 80col in 80col and bm16 @@ -460,17 +489,24 @@ int MOTO_Check_binary_file(FILE * f) && map_header[2] < 25) // <= 200 pixels high type = 2; // MAP file (SAVEP/LOADP format) } + if (type == 1) + { + if (length == 8000 || length == 8032 || length == 8064) + type = 4; // MO autoloading picture + } + } + else if (length == 1) + { + if(address == 0xE7C3) // TO7/TO8/TO9 6846 PRC + type = 3; // TO autoloading picture + else if(address == 0xA7C0) // MO5/MO6 PRC + type = 4; // MO autoloading picture } - else if (length == 1 && address == 0xE7C3) // TO8/TO9 6846 PRC - type = 3; // TO autoloading picture - // TODO : check autoloading MO5... if (fseek(f, length, SEEK_CUR) < 0) return 0; } while(code == 0); - if (code != 0xff) - return 0; return type; } @@ -489,8 +525,6 @@ int DECB_BIN_Add_End(FILE * f, word address) && Write_word_be(f, address); } -/** @ingroup moto - * @{ */ word MOTO_gamma_correct_RGB_to_MOTO(const T_Components * color) { word r, g, b; @@ -511,4 +545,3 @@ void MOTO_gamma_correct_MOTO_to_RGB(T_Components * color, word bgr) color->G = (byte)round(pow(((bgr >> 4)& 0x0F)/15.0, inv_gamma) * 255.0); color->R = (byte)round(pow((bgr & 0x0F)/15.0, inv_gamma) * 255.0); } -/** @} */ diff --git a/src/oldies.h b/src/oldies.h index 5f2dd3f4..afb235e1 100644 --- a/src/oldies.h +++ b/src/oldies.h @@ -70,6 +70,12 @@ int DECB_BIN_Add_Chunk(FILE * f, word size, word address, const byte * data); int DECB_BIN_Add_End(FILE * f, word address); +/** + * Check if the file is in the DECB Binary format + * + * @param f open file + * @return 1 if the file is in DECB Binary format, or else 0 + */ int DECB_Check_binary_file(FILE * f); /** @}*/ @@ -88,7 +94,31 @@ int DECB_Check_binary_file(FILE * f); * - TO8/TO8D * - TO9 * - Olivetti Prodest PC128 (a Thomson MO6 clone) + * @{ */ + +/** + * to define a specific machine in the Thomson MO/TO range of machines + */ +enum MOTO_Machine_Type { + MACHINE_TO7, ///< original TO7 with 8 colors + MACHINE_TO770, ///< the TO7-70 had 16 colors + MACHINE_MO5, + MACHINE_MO6, ///< + MACHINE_TO9, + MACHINE_TO8 ///< TO8, TO8D and TO9+ are equivalent +}; + +/** + * Graphic modes available in BASIC 128/512 with CONSOLE,,,,X instruction + */ +enum MOTO_Graphic_Mode { + MOTO_MODE_40col = 0, ///< 320x200 16 colors with constraints + MOTO_MODE_80col = 1, ///< 640x200 2 colors + MOTO_MODE_bm4 = 2, ///< 320x200 4 colors without constraint + MOTO_MODE_bm16 = 3, ///< 160x200 16 colors without constraint +}; + /** * Checks if the file is a Thomson binary file (SAVEM/LOADM format) *