diff --git a/src/Makefile b/src/Makefile index fe2b9cfd..7559d1f3 100644 --- a/src/Makefile +++ b/src/Makefile @@ -809,7 +809,7 @@ OBJS = main.o init.o graph.o $(APIOBJ) misc.o special.o \ windows.o brush.o realpath.o mountlist.o input.o hotkeys.o \ transform.o pversion.o factory.o $(PLATFORMOBJ) \ loadsave.o loadsavefuncs.o \ - pngformat.o motoformats.o stformats.o c64formats.o \ + pngformat.o motoformats.o stformats.o c64formats.o cpcformats.o \ fileformats.o miscfileformats.o libraw2crtc.o \ brush_ops.o buttons_effects.o layers.o \ oldies.o tiles.o colorred.o unicode.o gfx2surface.o \ @@ -821,7 +821,7 @@ endif TESTSOBJS = $(patsubst %.c,%.o,$(wildcard tests/*.c)) \ miscfileformats.o fileformats.o oldies.o libraw2crtc.o \ loadsavefuncs.o packbits.o tifformat.o c64load.o 6502.o \ - pngformat.o motoformats.o stformats.o c64formats.o \ + pngformat.o motoformats.o stformats.o c64formats.o cpcformats.o \ op_c.o colorred.o \ unicode.o \ io.o realpath.o version.o pversion.o \ diff --git a/src/cpcformats.c b/src/cpcformats.c new file mode 100644 index 00000000..55027808 --- /dev/null +++ b/src/cpcformats.c @@ -0,0 +1,1476 @@ +/* vim:expandtab:ts=2 sw=2: +*/ +/* Grafx2 - The Ultimate 256-color bitmap paint program + + Copyright 2018-2019 Thomas Bernard + Copyright 2011 Pawel Góralski + Copyright 2009 Petter Lindquist + Copyright 2008 Yves Rizoud + Copyright 2008 Franck Charlet + Copyright 2007-2011 Adrien Destugues + Copyright 1996-2001 Sunset Design (Guillaume Dorme & Karl Maritaud) + + 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 +*/ + +///@file cpcformats.c +/// Formats for the Amstrad CPC / CPC Plus computers + +#include +#include +#include "global.h" +#include "fileformats.h" +#include "io.h" +#include "loadsavefuncs.h" +#include "libraw2crtc.h" +#include "oldies.h" +#include "gfx2mem.h" +#include "gfx2log.h" + +/** + * Test for SCR file (Amstrad CPC) + * + * SCR file format is from "Advanced OCP Art Studio" : + * http://www.cpcwiki.eu/index.php/Format:Advanced_OCP_Art_Studio_File_Formats + * + * .WIN "window" format is also supported. + * + * For now we check the presence of a valid PAL file. + * If the PAL file is not there the pixel data may still be valid. + * The file size depends on the screen resolution. + * An AMSDOS header would be a good indication but in some cases it may not + * be there. + */ +void Test_SCR(T_IO_Context * context, FILE * file) +{ + // http://orgams.wikidot.com/le-format-impdraw-v2 + // http://orgams.wikidot.com/les-fichiers-win-compatibles-ocp-art-studio + FILE * pal_file; + unsigned long pal_size, file_size; + byte mode, color_anim_flag; + word loading_address = 0; + + File_error = 1; + + if (CPC_check_AMSDOS(file, &loading_address, &file_size)) + { + if (loading_address == 0x170) // iMPdraw v2 + { + byte buffer[0x90]; + fseek(file, 128, SEEK_SET); // right after AMSDOS header + Read_bytes(file, buffer, 0x90); + GFX2_LogHexDump(GFX2_DEBUG, "", buffer, 0, 0x90); + File_error = 0; + return; + } + else if ((loading_address == 0x200 || loading_address == 0xc000) && file_size > 16000) + { + File_error = 0; + return; + } + } + else + file_size = File_length_file(file); + + if (file_size > 16384*2) + return; + + // requires the PAL file + pal_file = Open_file_read_with_alternate_ext(context, "pal"); + if (pal_file == NULL) + return; + /** @todo the palette data can be hidden in the 48 "empty" bytes + * every 2048 bytes of a standard resolution SCR file. + * So we should detect the hidden Z80 code and load them. + * Load address of file is C000. Z80 code :
+ * C7D0: 3a d0 d7 cd 1c bd 21 d1 d7 46 48 cd 38 bc af 21 | :.....!..FH.8..!
+ * C7E0: d1 d7 46 48 f5 e5 cd 32 bc e1 f1 23 3c fe 10 20 | ..FH...2...#<..
+ * C7F0: f1 c3 18 bb 00 00 00 00 00 00 00 00 00 00 00 00 | ................
+ * mode and palette :
+ * D7D0: 00 1a 00 0c 03 0b 01 0d 17 10 02 0f 09 19 06 00 | ................
+ * https://gitlab.com/GrafX2/grafX2/merge_requests/121#note_119964168 + */ + + + if (CPC_check_AMSDOS(pal_file, NULL, &pal_size)) + fseek(pal_file, 128, SEEK_SET); // right after AMSDOS header + else + { + pal_size = File_length_file(pal_file); + fseek(pal_file, 0, SEEK_SET); + } + + if (pal_size != 239) + { + fclose(pal_file); + return; + } + + if (!Read_byte(pal_file, &mode) || !Read_byte(pal_file, &color_anim_flag)) + { + fclose(pal_file); + return; + } + GFX2_Log(GFX2_DEBUG, "Test_SCR() mode=%d color animation flag %02X\n", mode, color_anim_flag); + if (mode <= 2 && (color_anim_flag == 0 || color_anim_flag == 0xff)) + File_error = 0; + fclose(pal_file); +} + +/** + * Load Advanced OCP Art Studio files (Amstrad CPC) + * + * Only standard resolution files (Mode 0 160x200, mode 1 320x200 and + * mode 2 640x200) are supported. The .PAL file presence is required. + * "MJH" RLE packing is supported. + * + * .WIN "window" format is also supported. + * + * @todo Ask user for screen size (or register values) in order to support + * non standard resolutions. + */ +void Load_SCR(T_IO_Context * context) +{ + // The Amstrad CPC screen memory is mapped in a weird mode, somewhere + // between bitmap and textmode. Basically the only way to decode this is to + // emulate the video chip and read the bytes as needed... + // Moreover, the hardware allows the screen to have any size from 8x1 to + // 800x273 pixels, and there is no indication of that in the file besides + // its size. It can also use any of the 3 screen modes. Fortunately this + // last bit of information is stored in the palette file. + // Oh, and BTW, the picture can be offset, and it's even usual to do it, + // because letting 128 pixels unused at the beginning of the file make it a + // lot easier to handle screens using more than 16K of VRam. + // The pixel encoding change with the video mode so we have to know that + // before attempting to load anything... + // As if this wasn't enough, Advanced OCP Art Studio, the reference tool on + // Amstrad, can use RLE packing when saving files, meaning we also have to + // handle that. + + // All this mess enforces us to load (and unpack if needed) the file to a + // temporary 32k buffer before actually decoding it. + FILE * pal_file, * file; + unsigned long real_file_size, file_size, amsdos_file_size = 0; + word addr; + word load_address = 0x4000; // default for OCP Art studio + word display_start = 0x4000; + byte mode, color_anim_flag, color_anim_delay; + byte pal_data[236]; // 12 palettes of 16+1 colors + 16 excluded inks + 16 protected inks + word width, height = 200; + byte bpp; + enum PIXEL_RATIO ratio; + byte * cpc_ram; + word x, y; + int i; + byte sig[3]; + word block_length; + word win_width, win_height; + int is_win = 0; + int columns = 80; + int cpc_plus = 0; + const byte * cpc_plus_pal = NULL; + + File_error = 1; + // requires the PAL file for OCP Art studio files + pal_file = Open_file_read_with_alternate_ext(context, "pal"); + if (pal_file != NULL) + { + file_size = File_length_file(pal_file); + if (CPC_check_AMSDOS(pal_file, NULL, &file_size)) + fseek(pal_file, 128, SEEK_SET); // right after AMSDOS header + else + fseek(pal_file, 0, SEEK_SET); + if (!Read_byte(pal_file, &mode) || !Read_byte(pal_file, &color_anim_flag) + || !Read_byte(pal_file, &color_anim_delay) || !Read_bytes(pal_file, pal_data, 236)) + { + GFX2_Log(GFX2_WARNING, "Load_SCR() failed to load .PAL file\n"); + fclose(pal_file); + return; + } + fclose(pal_file); + GFX2_Log(GFX2_DEBUG, "Load_SCR() mode=%d color animation flag=%02X delay=%u\n", + mode, color_anim_flag, color_anim_delay); + } + + file = Open_file_read(context); + if (file == NULL) + return; + file_size = File_length_file(file); + real_file_size = file_size; + if (CPC_check_AMSDOS(file, &load_address, &amsdos_file_size)) + { + display_start = load_address; + if (file_size < (amsdos_file_size + 128)) + { + GFX2_Log(GFX2_ERROR, "Load_SCR() mismatch in file size. AMSDOS file size %lu, should be %lu\n", amsdos_file_size, file_size - 128); + fclose(file); + return; + } + else if (file_size > (amsdos_file_size + 128)) + GFX2_Log(GFX2_INFO, "Load_SCR() %lu extra bytes at end of file\n", file_size - 128 - amsdos_file_size); + fseek(file, 128, SEEK_SET); // right after AMSDOS header + file_size = amsdos_file_size; + } + else + fseek(file, 0, SEEK_SET); + + if (!Read_bytes(file, sig, 3) || !Read_word_le(file, &block_length)) + { + fclose(file); + return; + } + fseek(file, -5, SEEK_CUR); + + cpc_ram = GFX2_malloc(64*1024); + memset(cpc_ram, 0, 64*1024); + + if (0 != memcmp(sig, "MJH", 3) || block_length > 16384) + { + // raw data + Read_bytes(file, cpc_ram + load_address, file_size); + i = file_size; + } + else + { + // MJH packed format + i = 0; + do + { + if (!Read_bytes(file, sig, 3) || !Read_word_le(file, &block_length)) + break; + if (0 != memcmp(sig, "MJH", 3)) + break; + GFX2_Log(GFX2_DEBUG, " %.3s block %u\n", sig, block_length); + file_size -= 5; + while (block_length > 0) + { + byte code; + if (!Read_byte(file, &code)) + break; + file_size--; + if (code == 1) + { + byte repeat, value; + if (!Read_byte(file, &repeat) || !Read_byte(file, &value)) + break; + file_size -= 2; + do + { + cpc_ram[load_address + i++] = value; + block_length--; + } + while(--repeat != 0); + } + else + { + cpc_ram[load_address + i++] = code; + block_length--; + } + } + GFX2_Log(GFX2_DEBUG, " unpacked %d bytes. remaining bytes in file=%lu\n", + i, file_size); + } + while(file_size > 0 && i < 16384); + } + fclose(file); + + if (i > 5) + { + win_width = cpc_ram[load_address + i - 4] + (cpc_ram[load_address + i - 3] << 8); // in bits + win_height = cpc_ram[load_address + i - 2]; + if (((win_width + 7) >> 3) * win_height + 5 == i) // that's a WIN file ! + { + width = win_width >> (2 - mode); + height = win_height; + is_win = 1; + columns = (win_width + 7) >> 3; + GFX2_Log(GFX2_DEBUG, ".WIN file detected len=%d (%d,%d) %dcols %02X %02X %02X %02X %02X\n", + i, width, height, columns, + cpc_ram[load_address + i - 5], cpc_ram[load_address + i - 4], cpc_ram[load_address + i - 3], + cpc_ram[load_address + i - 2], cpc_ram[load_address + i - 1]); + } + else + { + GFX2_Log(GFX2_DEBUG, ".SCR file. Data length %d\n", i); + if (load_address == 0x170) + { + // fichier iMPdraw v2 + // http://orgams.wikidot.com/le-format-impdraw-v2 + GFX2_Log(GFX2_DEBUG, "Detected \"%s\"\n", cpc_ram + load_address + 6); + mode = cpc_ram[load_address + 0x14] - 0x0e; + cpc_plus = cpc_ram[load_address + 0x3c]; + GFX2_Log(GFX2_DEBUG, "Mode %d CPC %d\n", (int)mode, (int)cpc_plus); + for (addr = load_address + 0x1d; cpc_ram[addr] < 16; addr += 2) + { + GFX2_Log(GFX2_DEBUG, " R%d = &H%02x = %d\n", cpc_ram[addr], cpc_ram[addr+1], cpc_ram[addr+1]); + // see http://www.cpcwiki.eu/index.php/CRTC#The_6845_Registers + switch(cpc_ram[addr]) + { + case 1: + columns = cpc_ram[addr+1] * 2; + break; + case 6: + height = cpc_ram[addr+1] * 8; + break; + case 12: + display_start = ((cpc_ram[addr+1] & 0x30) << 10) | ((cpc_ram[addr+1] & 0x03) << 9); + GFX2_Log(GFX2_DEBUG, " display_start &H%04X\n", display_start); + } + } + snprintf(context->Comment, COMMENT_SIZE, "%s mode %d %s", + cpc_ram + load_address + 7, mode, cpc_plus ? "CPC+" : ""); + if (cpc_plus) + { + // palette at 0x801 (mode at 0x800 ?) + GFX2_LogHexDump(GFX2_DEBUG, "", cpc_ram, 0x800, 0x21); + cpc_plus_pal = cpc_ram + 0x801; + } + else + { + int j; + // palette at 0x7f00 + GFX2_LogHexDump(GFX2_DEBUG, "", cpc_ram, 0x7f00, 16); + for (j = 0; j < 16; j++) + pal_data[12*j] = cpc_ram[0x7f00 + j]; + } + } + else if (load_address == 0x200) + { + /* from HARLEY.SCR : + 0800 00 = mode + 0801-0810 palette (Firmware colors) + 0811 21 47 08 LD HL,0847 ; OVERSCAN_REG_VALUES + 0814 cd 36 08 CALL 0836 ; LOAD_CRTC_REGS + 0817 3a 00 08 LD A,(0800) ; MODE + 081a cd 1c bd CALL BD1C ; Set screen mode + 081d 21 01 08 LD HL,0801 ; PALETTE + 0820 af XOR A + LOOP: + 0821 4e LD C,(HL) + 0822 41 LD B,C + 0823 f5 PUSH AF + 0824 e5 PUSH HL + 0825 cd 32 bc CALL BC32 ; SET ink A to color B,C + 0828 e1 POP HL + 0829 f1 POP AF + 082a 23 INC HL + 082b 3c INC A + 082c fe 10 CMP 10 + 082e 20 f1 JR NZ,0821 ; LOOP + 0830 cd 18 bb CALL BB18 ; Wait key press + 0833 21 55 08 LD HL,0855 ; STANDARD_REG_VALUES + LOAD_CRTC_REGS: + 0836 01 00 bc LD BC,BC00 + LOOP_CRTC: + 0839 7e LD A,(HL) + 083a a7 AND A + 083b c8 RET Z + 083c ed 79 OUT (C),A + 083e 04 INC B + 083f 23 INC HL + 0840 7e LD A,(HL) + 0841 ed 79 OUT (C),A + 0843 23 INC HL + 0844 05 DEC B + 0845 18 f2 JR 0839 ; LOOP_CRTC + OVERSCAN_REG_VALUES: + 0847 01 30 02 32 06 22 07 23 0c 0d 0d 00 00 00 + STANDARD_REG_VALUES: + 0855 01 28 02 2e 06 19 07 1e 0c 30 00 00 + */ + int j; + static const byte CPC_Firmware_Colors[] = { + 0x54, 0x44, 0x55, 0x5c, 0x58, 0x5d, 0x4c, 0x45, 0x4d, + 0x56, 0x46, 0x57, 0x5e, 0x40, 0x5f, 0x4e, 0x47, 0x4f, + 0x52, 0x42, 0x53, 0x5a, 0x59, 0x5b, 0x4a, 0x43, 0x4b }; + mode = cpc_ram[0x800]; + for (j = 0; j < 16; j++) + pal_data[12*j] = CPC_Firmware_Colors[cpc_ram[0x801 + j]]; + addr = 0x847; + if (cpc_ram[0x80bb] == 1) + addr = 0x80bb; + for (; cpc_ram[addr] > 0 && cpc_ram[addr] < 16; addr += 2) + { + GFX2_Log(GFX2_DEBUG, " R%d = &H%02x = %d\n", cpc_ram[addr], cpc_ram[addr+1], cpc_ram[addr+1]); + // see http://www.cpcwiki.eu/index.php/CRTC#The_6845_Registers + switch(cpc_ram[addr]) + { + case 1: + columns = cpc_ram[addr+1] * 2; + break; + case 6: + height = cpc_ram[addr+1] * 8; + break; + case 12: + display_start = (display_start & 0x00ff) | ((cpc_ram[addr+1] & 0x30) << 10) | ((cpc_ram[addr+1] & 0x03) << 9); + break; + case 13: + display_start = (display_start & 0xff00) | cpc_ram[addr+1]; + } + } + } + if (i >= 30000) + { + height = 272; columns = 96; + } + } + } + + switch (mode) + { + case 0: + width = columns * 2; + bpp = 4; + ratio = PIXEL_WIDE; + break; + case 1: + width = columns * 4; + bpp = 2; + ratio = PIXEL_SIMPLE; + break; + case 2: + width = columns * 8; + bpp = 1; + ratio = PIXEL_TALL; + break; + default: + return; // unsupported + } + + if (Config.Clear_palette) + memset(context->Palette,0,sizeof(T_Palette)); + // Setup the palette (amstrad hardware palette) + CPC_set_HW_palette(context->Palette + 0x40); + + // Set the palette for this picture + if (cpc_plus_pal) + { + for (i = 0; i < 16; i++) + { + context->Palette[i].G = cpc_plus_pal[i*2 + 1] * 0x11; + context->Palette[i].R = (cpc_plus_pal[i*2] >> 4) * 0x11; + context->Palette[i].B = (cpc_plus_pal[i*2] & 15) * 0x11; + } + } + else + { + for (i = 0; i < 16; i++) + context->Palette[i] = context->Palette[pal_data[12*i]]; + } + + File_error = 0; + Pre_load(context, width, height, real_file_size, FORMAT_SCR, ratio, bpp); + + if (!is_win) + { + // Standard resolution files have the 200 lines stored in block + // of 25 lines of 80 bytes = 2000 bytes every 2048 bytes. + // so there are 48 bytes unused every 2048 bytes... + for (y = 0; y < 8; y++) + { + addr = display_start + 0x800 * y; + if (y > 0 && (display_start & 0x7ff)) + { + if (!GFX2_is_mem_filled_with(cpc_ram + (addr & 0xf800), 0, display_start & 0x7ff)) + GFX2_LogHexDump(GFX2_DEBUG, "SCR1 ", cpc_ram, + addr & 0xf800, display_start & 0x7ff); + } + addr += (height >> 3) * columns; + block_length = (height >> 3) * columns + (display_start & 0x7ff); + if (block_length <= 0x800) + { + block_length = 0x800 - block_length; + if (!GFX2_is_mem_filled_with(cpc_ram + addr, 0, block_length)) + GFX2_LogHexDump(GFX2_DEBUG, "SCR2 ", cpc_ram, + addr, block_length); + } + else + { + block_length = 0x1000 - block_length; + if (!GFX2_is_mem_filled_with(cpc_ram + addr + 0x4000, 0, block_length)) + GFX2_LogHexDump(GFX2_DEBUG, "SCR2 ", cpc_ram, + addr + 0x4000, block_length); + } + } + //for (j = 0; j < i; j += 2048) + // GFX2_LogHexDump(GFX2_DEBUG, "SCR ", cpc_ram, load_address + j + 2000, 48); + } + + GFX2_Log(GFX2_DEBUG, " display_start &H%04X\n", display_start); + for (y = 0; y < height; y++) + { + const byte * line; + + if (is_win) + addr = display_start + y * columns; + else + { + addr = display_start + ((y >> 3) * columns); + addr = (addr & 0xC7FF) | ((addr & 0x800) << 3); + addr += (y & 7) << 11; + } + //GFX2_Log(GFX2_DEBUG, "line#%d &H%04X\n", y, addr); + line = cpc_ram + addr; + x = 0; + for (i = 0; i < columns; i++) + { + byte pixels = line[i]; + switch (mode) + { + case 0: + Set_pixel(context, x++, y, (pixels & 0x80) >> 7 | (pixels & 0x08) >> 2 | (pixels & 0x20) >> 3 | (pixels & 0x02) << 2); + Set_pixel(context, x++, y, (pixels & 0x40) >> 6 | (pixels & 0x04) >> 1 | (pixels & 0x10) >> 2 | (pixels & 0x01) << 3); + break; + case 1: + do { + // upper nibble is 4 lower color bits, lower nibble is 4 upper color bits + Set_pixel(context, x++, y, (pixels & 0x80) >> 7 | (pixels & 0x08) >> 2); + pixels <<= 1; + } + while ((x & 3) != 0); + break; + case 2: + do { + Set_pixel(context, x++, y, (pixels & 0x80) >> 7); + pixels <<= 1; + } + while ((x & 7) != 0); + } + } + } + + free(cpc_ram); +} + +/** + * Save Amstrad SCR file + * + * guess mode from aspect ratio : + * - normal pixels are mode 1 + * - wide pixels are mode 0 + * - tall pixels are mode 2 + * + * Mode and palette are stored in a .PAL file. + * + * The picture color index should be 0-15, + * The CPC Hardware palette is expected to be set (indexes 64 to 95) + * + * @todo Add possibility to set R9, R12, R13 values + * @todo Add OCP packing support + * @todo Add possibility to include AMSDOS header, with proper loading + * address guessed from r12/r13 values. + */ +void Save_SCR(T_IO_Context * context) +{ + int i, j; + unsigned char* output; + unsigned long outsize = 0; + unsigned char r1 = 0; + int cpc_mode; + FILE* file; + + + switch(Pixel_ratio) + { + case PIXEL_WIDE: + case PIXEL_WIDE2: + cpc_mode = 0; + break; + case PIXEL_TALL: + case PIXEL_TALL2: + case PIXEL_TALL3: + cpc_mode = 2; + break; + default: + cpc_mode = 1; + break; + } + + file = Open_file_write_with_alternate_ext(context, "pal"); + if (file == NULL) + return; + if (!Write_byte(file, cpc_mode) || !Write_byte(file, 0) || !Write_byte(file, 0)) + { + fclose(file); + return; + } + for (i = 0; i < 16; i++) + { + // search for the color in the HW palette (0x40-0x5F) + byte index = 0x40; + while ((index < 0x60) && + !CPC_compare_colors(context->Palette + i, context->Palette + index)) + index++; + if (index >= 0x60) + { + GFX2_Log(GFX2_WARNING, "Save_SCR() color #%i not found in CPC HW palette.\n", i); + index = 0x54 - i; // default + } + for (j = 0; j < 12; j++) // write the same color for the 12 frames + { + Write_byte(file, index); + } + } + // border + for (j = 0; j < 12; j++) + { + Write_byte(file, 0x54); // black + } + // excluded inks + for (i = 0; i < 16; i++) + { + Write_byte(file, 0); + } + // protected inks + for (i = 0; i < 16; i++) + { + Write_byte(file, 0); + } + fclose(file); + + output = raw2crtc(context, cpc_mode, 7, &outsize, &r1, 0x0C, 0); + GFX2_Log(GFX2_DEBUG, "Save_SCR() output=%p outsize=%lu r1=$%02X\n", output, outsize, r1); + + if (output == NULL) + return; + + file = Open_file_write(context); + if (file == NULL) + File_error = 1; + else + { + File_error = 0; + if (!Write_bytes(file, output, outsize)) + File_error = 1; + fclose(file); + } + free (output); +} + +/** + * Test for GO1/GO2/KIT - Amstrad Plus Graphos + * + * This format is made of 3 files + * .KIT hold the palette in "Kit4096" format. There are 16 colors each stored + * as 12 bit RGB in RB0G order. + * .GO1 and GO2 hold each half of the picture (top and bottom) + * The file always cover the whole display of the Plus (196*272 or so) + */ +void Test_GOS(T_IO_Context * context, FILE * file) +{ + FILE *file_oddeve; + unsigned long file_size = 0; + + if (!CPC_check_AMSDOS(file, NULL, &file_size)) + file_size = File_length_file(file); + if (file_size < 16383 || file_size > 16384) { + File_error = 1; + return; + } + + file_oddeve = Open_file_read_with_alternate_ext(context, "GO2"); + if (file_oddeve == NULL) { + File_error = 2; + return; + } + if (!CPC_check_AMSDOS(file_oddeve, NULL, &file_size)) + file_size = File_length_file(file_oddeve); + fclose(file_oddeve); + if (file_size < 16383 || file_size > 16384) { + File_error = 3; + return; + } + + File_error = 0; +} + + +/** + * Load GO1/GO2/KIT - Amstrad CPC Plus Graphos + */ +void Load_GOS(T_IO_Context* context) +{ + FILE *file; + unsigned long file_size; + int i; + int x, y; + byte * pixel_data; + + if (!(file = Open_file_read(context))) + { + File_error = 1; + return; + } + + if (CPC_check_AMSDOS(file, NULL, &file_size)) + fseek(file, 128, SEEK_SET); // right after AMSDOS header + else + file_size = File_length_file(file); + + context->Ratio = PIXEL_WIDE; + Pre_load(context, 192, 272, file_size, FORMAT_GOS, context->Ratio, 0); + context->Width = 192; + context->Height = 272; + + // load pixels + pixel_data = GFX2_malloc(16384); + memset(pixel_data, 0, 16384); + Read_bytes(file, pixel_data, file_size); + + i = 0; + for (y = 0; y < 168; y++) { + x = 0; + while (x < 192) { + byte pixels = pixel_data[i]; + Set_pixel(context, x++, y, (pixels & 0x80) >> 7 | (pixels & 0x08) >> 2 | (pixels & 0x20) >> 3 | (pixels & 0x02) << 2); + Set_pixel(context, x++, y, (pixels & 0x40) >> 6 | (pixels & 0x04) >> 1 | (pixels & 0x10) >> 2 | (pixels & 0x01) << 3); + i++; + } + + i += 0x800; + if (i > 0x3FFF) { + i -= 0x4000; + } else { + i -= 192 / 2; + } + } + + fclose(file); + + // load pixels from GO2 + file = Open_file_read_with_alternate_ext(context, "GO2"); + if (CPC_check_AMSDOS(file, NULL, &file_size)) + fseek(file, 128, SEEK_SET); // right after AMSDOS header + + Read_bytes(file, pixel_data, file_size); + i = 0; + for (y = 168; y < 272; y++) { + x = 0; + while (x < 192) { + byte pixels = pixel_data[i]; + Set_pixel(context, x++, y, (pixels & 0x80) >> 7 | (pixels & 0x08) >> 2 | (pixels & 0x20) >> 3 | (pixels & 0x02) << 2); + Set_pixel(context, x++, y, (pixels & 0x40) >> 6 | (pixels & 0x04) >> 1 | (pixels & 0x10) >> 2 | (pixels & 0x01) << 3); + i++; + } + + i += 0x800; + if (i > 0x3FFF) { + i -= 0x4000; + } else { + i -= 192 / 2; + } + } + + fclose(file); + + file = Open_file_read_with_alternate_ext(context, "KIT"); + if (file == NULL) { + // There is no palette, but that's fine, we can still load the pixels + return; + } + + if (CPC_check_AMSDOS(file, NULL, &file_size)) { + fseek(file, 128, SEEK_SET); // right after AMSDOS header + } else { + file_size = File_length_file(file); + } + + if (Config.Clear_palette) + memset(context->Palette,0,sizeof(T_Palette)); + + File_error = 0; + + if (file_size == 32) + { + for (i = 0; i < 16; i++) + { + uint16_t word; + if (!Read_word_le(file, &word)) + { + File_error = 2; + return; + } + + context->Palette[i].R = ((word >> 4) & 0xF) * 0x11; + context->Palette[i].G = ((word >> 8) & 0xF) * 0x11; + context->Palette[i].B = ((word >> 0) & 0xF) * 0x11; + } + } + else + { + // Setup the palette (amstrad hardware palette) + CPC_set_HW_palette(context->Palette + 0x40); + for (i = 0; i < 16; i++) + { + byte ink; + if (!Read_byte(file, &ink)) + { + File_error = 2; + return; + } + context->Palette[i] = context->Palette[ink]; + } + } + + fclose(file); +} + +/** + * Test for CM5 - Amstrad CPC "Mode 5" picture + * + * This is a format designed by SyX. + * There is one .GFX file in the usual amstrad format + * and a .CM5 file with the palette, which varies over time. + * + * CM5 file is 2049 bytes, GFX is 18432 bytes. + * + * @todo check CM5 contains only valid values [0x40-0x5f] + */ +void Test_CM5(T_IO_Context * context, FILE * file) +{ + // check cm5 file size == 2049 bytes + FILE *file_gfx; + long file_size; + + File_error = 1; + + file_size = File_length_file(file); + if (file_size != 2049) + return; + + // check existence of a .GFX file with the same name + file_gfx = Open_file_read_with_alternate_ext(context, "gfx"); + if (file_gfx == NULL) + return; + file_size = File_length_file(file_gfx); + fclose(file_gfx); + if (file_size != 18432) + return; + + File_error = 0; +} + + +/** + * Load Amstrad CPC "Mode 5" picture + * + * Only support 288x256 resolution as the Mode 5 Viewer app only handles this + * single resoltion. + */ +void Load_CM5(T_IO_Context* context) +{ + // Ensure "8bit" constraint mode is switched on + // Set palette to the CPC hardware colors + // Load the palette data to the 4 colorlayers + FILE *file; + byte value = 0; + int mod=0; + short line = 0; + int tx, ty; + // for preview : + byte ink0; + byte ink1[256]; + byte ink2[256]; + byte ink3[256*6]; + + if (!(file = Open_file_read(context))) + { + File_error = 1; + return; + } + + Pre_load(context, 48*6, 256, 2049, FORMAT_CM5, PIXEL_SIMPLE, 0); + + if (Config.Clear_palette) + { + memset(context->Palette,0,sizeof(T_Palette)); + // setup colors 0,1,2,3 to see something in the thumbnail preview of layer 5 + context->Palette[1].R = 60; + context->Palette[2].B = 60; + context->Palette[3].G = 60; + } + + // Setup the palette (amstrad hardware palette) + CPC_set_HW_palette(context->Palette + 0x40); + + First_color_in_palette = 64; + + if (!Read_byte(file, &ink0)) + File_error = 2; + + // This forces the creation of 5 layers total : + // Needed because the "pixel" functions will seek layer 4 + Set_loading_layer(context, 4); + // Now select layer 1 again + Set_loading_layer(context, 0); + + if (context->Type == CONTEXT_MAIN_IMAGE) + { + Set_image_mode(context, IMAGE_MODE_MODE5); + + // Fill layer with color we just read (Layer 1 - INK 0) + for(ty=0; tyHeight; ty++) + for(tx=0; txWidth; tx++) + Set_pixel(context, tx, ty, ink0); + } + + while(Read_byte(file, &value)) + { + switch(mod) + { + case 0: + // This is color for layer 2 - INK 1 + Set_loading_layer(context, 1); + for(tx=0; txWidth; tx++) + Set_pixel(context, tx, line, value); + ink1[line] = value; + break; + case 1: + // This is color for layer 3 - INK 2 + Set_loading_layer(context, 2); + for(tx=0; txWidth; tx++) + Set_pixel(context, tx, line, value); + ink2[line] = value; + break; + default: + // This is color for a block in layer 4 - INK 3 + Set_loading_layer(context, 3); + for(tx=(mod-2)*48; tx<(mod-1)*48; tx++) + Set_pixel(context, tx, line, value); + ink3[line*6+(mod-2)] = value; + break; + } + mod++; + if (mod > 7) + { + mod = 0; + line++; + } + } + + fclose(file); + + // Load the pixeldata to the 5th layer + file = Open_file_read_with_alternate_ext(context, "gfx"); + if (file == NULL) + { + File_error = 1; + return; + } + Set_loading_layer(context, 4); + + if (context->Type == CONTEXT_PREVIEW) + for (ty = 0; ty < 256; ty++) + for (tx = 0; tx < 48*6; ) + { + Read_byte(file, &value); + for (mod = 0; mod < 4; mod++, tx++, value <<= 1) + { + switch(3 ^ (((value&0x80) >> 7) | ((value&0x8)>>2))) // INK + { + case 0: + Set_pixel(context, tx, ty, ink0); + break; + case 1: + Set_pixel(context, tx, ty, ink1[ty]); + break; + case 2: + Set_pixel(context, tx, ty, ink2[ty]); + break; + default: + Set_pixel(context, tx, ty, ink3[ty*6+(tx/48)]); + } + } + } + else + for (ty = 0; ty < 256; ty++) + for (tx = 0; tx < 48*6; ) + { + Read_byte(file, &value); + Set_pixel(context, tx++, ty, 3 ^ (((value&0x80) >> 7) | ((value&0x8)>>2))); + Set_pixel(context, tx++, ty, 3 ^ (((value&0x40) >> 6) | ((value&0x4)>>1))); + Set_pixel(context, tx++, ty, 3 ^ (((value&0x20) >> 5) | ((value&0x2)>>0))); + Set_pixel(context, tx++, ty, 3 ^ (((value&0x10) >> 4) | ((value&0x1)<<1))); + } + + fclose(file); + +} + + +void Save_CM5(T_IO_Context* context) +{ + FILE* file; + int tx, ty; + + // TODO: Check picture has 5 layers + // TODO: Check the constraints on the layers + // Layer 1 : 1 color Only + // Layer 2 and 3 : 1 color/line + // Layer 4 : 1 color / 48x1 block + // TODO: handle filesize + + if (!(file = Open_file_write(context))) + { + File_error = 1; + return; + } + setvbuf(file, NULL, _IOFBF, 64*1024); + + // Write layer 0 + Set_saving_layer(context, 0); + Write_byte(file, Get_pixel(context, 0, 0)); + for(ty = 0; ty < 256; ty++) + { + Set_saving_layer(context, 1); + Write_byte(file, Get_pixel(context, 0, ty)); + Set_saving_layer(context, 2); + Write_byte(file, Get_pixel(context, 0, ty)); + Set_saving_layer(context, 3); + for(tx = 0; tx < 6; tx++) + { + Write_byte(file, Get_pixel(context, tx*48, ty)); + } + } + + fclose(file); + + // Now the pixeldata + if (!(file = Open_file_write_with_alternate_ext(context, "gfx"))) + { + File_error = 2; + return; + } + setvbuf(file, NULL, _IOFBF, 64*1024); + + Set_saving_layer(context, 4); + + for (ty = 0; ty < 256; ty++) + { + for (tx = 0; tx < 48*6; tx+=4) + { + byte code = 0; + byte pixel; + + pixel = 3-Get_pixel(context, tx+3, ty); + code |= (pixel&2)>>1 | ((pixel & 1)<<4); + pixel = 3-Get_pixel(context, tx+2, ty); + code |= ((pixel&2)<<0) | ((pixel & 1)<<5); + pixel = 3-Get_pixel(context, tx+1, ty); + code |= ((pixel&2)<<1) | ((pixel & 1)<<6); + pixel = 3-Get_pixel(context, tx, ty); + code |= ((pixel&2)<<2) | ((pixel & 1)<<7); + Write_byte(file, code); + } + } + + fclose(file); + File_error = 0; + +} + + +/* Amstrad CPC 'PPH' for Perfect Pix. +// This is a format designed by Rhino. +// There are 3 modes: +// - Mode 'R': 1:1 pixels, 16 colors from the CPC 27 color palette. +// (this is implemented on CPC as two pictures with wide pixels, the "odd" one +// being shifted half a pixel to the right), and flipping) +// - Mode 'B0': wide pixels, up to 126 out of 378 colors. +// (this is implemented as two pictures with wide pixels, sharing the same 16 +// color palette, and flipping) +// - Mode 'B1': 1:1 pixels, 1 fixed color, up to 34 palettes of 9 colors +// (actually 4 colors + flipping) +// +// - The standard CPC formats can also be encapsulated into a PPH file. +// +// http://www.pouet.net/prod.php?which=67770#c766959 +*/ +void Test_PPH(T_IO_Context * context, FILE * file) +{ + FILE *file_oddeve; + byte buffer[6]; + unsigned long file_size; + unsigned int w, h; + unsigned int expected; + + File_error = 1; + + // First check file size is large enough to hold the header + file_size = File_length_file(file); + if (file_size < 11) { + File_error = 1; + return; + } + + // File is large enough for the header, now check if the data makes some sense + if (!Read_bytes(file, buffer, 6)) + return; + if (buffer[0] > 5) { + // Unknown mode + File_error = 2; + return; + } + + w = buffer[1] | (buffer[2] << 8); + if (w < 2 || w > 384) { + // Invalid width + File_error = 3; + return; + } + + h = buffer[3] | (buffer[4] << 8); + if (h < 1 || h > 272) { + // Invalid height + File_error = 4; + return; + } + + if (buffer[5] < 1 || buffer[5] > 28) + { + // Invalid palettes count + File_error = 5; + return; + } + expected = 6; // Size of header + switch(buffer[0]) + { + case 0: + case 3: + case 4: + // Palette size should be 16 bytes, only 1 palette. + if (buffer[5] != 1) { + File_error = 7; + return; + } + expected += 16; + break; + + case 1: + case 5: + expected += buffer[5] * 5 - 1; + break; + + case 2: + // Palette size should be 2 bytes + if (buffer[5] != 1) { + File_error = 7; + return; + } + expected += 2; + break; + } + + if (file_size != expected) + { + File_error = 6; + return; + } + + // check existence of .ODD/.EVE files with the same name + // and the right size + expected = w * h / 4; + file_oddeve = Open_file_read_with_alternate_ext(context, "odd"); + if (file_oddeve == NULL) + return; + file_size = File_length_file(file_oddeve); + fclose (file_oddeve); + if (file_size != expected) + { + File_error = 8; + return; + } + file_oddeve = Open_file_read_with_alternate_ext(context, "eve"); + if (file_oddeve == NULL) + return; + file_size = File_length_file(file_oddeve); + fclose(file_oddeve); + if (file_size != expected) + { + File_error = 8; + return; + } + File_error = 0; +} + + +static uint8_t pph_blend(uint8_t a, uint8_t b) +{ + uint32_t h,l; + if (a > b) { h = a; l = b; } + else { h = b; l = a; } + + return (23 * h + 9 * l) / 32; +} + + +void Load_PPH(T_IO_Context* context) +{ + FILE *file; + FILE *feven; + + // Read in the header + uint8_t mode; + uint16_t width; + uint16_t height; + uint8_t npal; + int i,j; + uint8_t a,b,c,d; + int file_size; + uint8_t pl[16]; + + static const T_Components CPCPAL[27] = + { + { 0x00, 0x02, 0x01 }, { 0x00, 0x02, 0x6B }, { 0x0C, 0x02, 0xF4 }, + { 0x6C, 0x02, 0x01 }, { 0x69, 0x02, 0x68 }, { 0x6C, 0x02, 0xF2 }, + { 0xF3, 0x05, 0x06 }, { 0xF0, 0x02, 0x68 }, { 0xF3, 0x02, 0xF4 }, + { 0x02, 0x78, 0x01 }, { 0x00, 0x78, 0x68 }, { 0x0C, 0x7B, 0xF4 }, + { 0x6E, 0x7B, 0x01 }, { 0x6E, 0x7D, 0x6B }, { 0x6E, 0x7B, 0xF6 }, + { 0xF3, 0x7D, 0x0D }, { 0xF3, 0x7D, 0x6B }, { 0xFA, 0x80, 0xF9 }, + { 0x02, 0xF0, 0x01 }, { 0x00, 0xF3, 0x6B }, { 0x0F, 0xF3, 0xF2 }, + { 0x71, 0xF5, 0x04 }, { 0x71, 0xF3, 0x6B }, { 0x71, 0xF3, 0xF4 }, + { 0xF3, 0xF3, 0x0D }, { 0xF3, 0xF3, 0x6D }, { 0xFF, 0xF3, 0xF9 } + }; + + if (!(file = Open_file_read(context))) + { + File_error = 1; + return; + } + + file_size=File_length_file(file); + + Read_byte(file, &mode); + Read_word_le(file, &width); + Read_word_le(file, &height); + Read_byte(file, &npal); + + if (npal > 16) + npal = 16; + + // Switch to the proper aspect ratio + switch (mode) + { + case 0: + case 4: + context->Ratio = PIXEL_WIDE; + width /= 2; + break; + + case 2: + context->Ratio = PIXEL_TALL; + break; + + case 1: + case 5: + case 3: + context->Ratio = PIXEL_SIMPLE; + break; + } + + Pre_load(context, width, height, file_size, FORMAT_PPH, context->Ratio, 0); + + context->Width = width; + context->Height = height; + + // First of all, detect the mode + // 0, 1, 2 > Load as with SCR files? + // R(3) > Load as single layer, square pixels, 16 colors + // B0(4) > Load as single layer, wide pixels, expand palette with colorcycling + // B1(5) > Load as ??? + // Maybe special mode similar to mode5, with 2 layers + auto-flicker? + + switch (mode) + { + case 0: + case 3: // R + // 16-color palette + for (i = 0; i < 16; i++) + { + uint8_t color; + Read_byte(file, &color); + context->Palette[i] = CPCPAL[color]; + } + break; + + case 1: + case 5: // B1 + { + // Single or multiple 4-color palettes + uint8_t base[4]; + for (j = 0; j < npal; j++) + { + for (i = 0; i < 4; i++) + { + Read_byte(file,&base[i]); + } + for (i = 0; i < 16; i++) + { + context->Palette[i + 16*j].R = pph_blend( + CPCPAL[base[i & 3]].R, CPCPAL[base[i >> 2]].R); + context->Palette[i + 16*j].G = pph_blend( + CPCPAL[base[i & 3]].G, CPCPAL[base[i >> 2]].G); + context->Palette[i + 16*j].B = pph_blend( + CPCPAL[base[i & 3]].B, CPCPAL[base[i >> 2]].B); + } + // TODO this byte marks where this palette stops being used and the + // next starts. We must handle this! + Read_byte(file,&pl[j]); + } + pl[npal - 1] = 255; + break; + } + + case 2: + // Single 2-color palette + break; + + case 4: // B0 + { + // Single 16-color palette + flipping, need to expand palette and + // setup colorcycling ranges. + uint8_t base[16]; + for (i = 0; i < 16; i++) + { + Read_byte(file,&base[i]); + } + + for (i = 0; i < 256; i++) + { + context->Palette[i].R = pph_blend( + CPCPAL[base[i & 15]].R, CPCPAL[base[i >> 4]].R); + context->Palette[i].G = pph_blend( + CPCPAL[base[i & 15]].G, CPCPAL[base[i >> 4]].G); + context->Palette[i].B = pph_blend( + CPCPAL[base[i & 15]].B, CPCPAL[base[i >> 4]].B); + } + } + break; + } + + fclose(file); + + // Load the picture data + // There are two pages, each storing bytes in the CPC vram format but lines in + // linear order. + file = Open_file_read_with_alternate_ext(context, "odd"); + if (file == NULL) + { + File_error = 3; + return; + } + feven = Open_file_read_with_alternate_ext(context, "eve"); + if (feven == NULL) + { + File_error = 4; + fclose(file); + return; + } + + c = 0; + d = 0; + + for (j = 0; j < height; j++) + { + for (i = 0; i < width;) + { + uint8_t even, odd; + Read_byte(feven, &even); + Read_byte(file, &odd); + + switch (mode) + { + case 4: + a = ((even & 0x02) << 2) | ((even & 0x08) >> 2) + | ((even & 0x20) >> 3) | ((even & 0x80) >> 7); + a <<= 4; + a |= ((odd & 0x02) << 2) | (( odd & 0x08) >> 2) + | (( odd & 0x20) >> 3) | (( odd & 0x80) >> 7); + + b = ((even & 0x01) << 3) | ((even & 0x04) >> 1) + | ((even & 0x10) >> 2) | ((even & 0x40) >> 6); + b <<= 4; + b |= ((odd & 0x01) << 3) | (( odd & 0x04) >> 1) + | (( odd & 0x10) >> 2) | (( odd & 0x40) >> 6); + + Set_pixel(context, i++, j, a); + Set_pixel(context, i++, j, b); + break; + + case 3: + a = ((even & 0x02) << 2) | ((even & 0x08) >> 2) + | ((even & 0x20) >> 3) | ((even & 0x80) >> 7); + b = (( odd & 0x02) << 2) | (( odd & 0x08) >> 2) + | (( odd & 0x20) >> 3) | (( odd & 0x80) >> 7); + c = ((even & 0x01) << 3) | ((even & 0x04) >> 1) + | ((even & 0x10) >> 2) | ((even & 0x40) >> 6); + d = (( odd & 0x01) << 3) | (( odd & 0x04) >> 1) + | (( odd & 0x10) >> 2) | (( odd & 0x40) >> 6); + Set_pixel(context, i++, j, j & 1 ? b : a); + Set_pixel(context, i++, j, j & 1 ? a : b); + Set_pixel(context, i++, j, j & 1 ? d : c); + Set_pixel(context, i++, j, j & 1 ? c : d); + break; + + case 5: + if (d >= pl[c]) + { + d = 0; + c++; + } + a = ((even & 0x80) >> 6) | ((even & 0x08) >> 3); + b = (( odd & 0x80) >> 6) | (( odd & 0x08) >> 3); + Set_pixel(context, i++, j, a + (b << 2) + c * 16); + a = ((even & 0x40) >> 5) | ((even & 0x04) >> 2); + b = (( odd & 0x40) >> 5) | (( odd & 0x04) >> 2); + Set_pixel(context, i++, j, a + (b << 2) + c * 16); + a = ((even & 0x20) >> 4) | ((even & 0x02) >> 1); + b = (( odd & 0x20) >> 4) | (( odd & 0x02) >> 1); + Set_pixel(context, i++, j, a + (b << 2) + c * 16); + a = ((even & 0x10) >> 3) | ((even & 0x01) >> 0); + b = (( odd & 0x10) >> 3) | (( odd & 0x01) >> 0); + Set_pixel(context, i++, j, a + (b << 2) + c * 16); + + break; + + default: + File_error = 2; + return; + } + + } + d++; + } + fclose(file); + fclose(feven); + + File_error = 0; +} + +void Save_PPH(T_IO_Context* context) +{ + (void)context; // unused + // TODO + + // Detect mode + // Wide pixels => B0 (4) + // Square pixels: + // - 16 colors used => R + // - more colors used => B1 (if <16 colors per line) + + // Check palette + // B0: use diagonal: 0, 17, 34, ... (assume the other are mixes) + // R: use 16 used colors (or 16 first?) + // B1: find the 16 colors used in a line? Or assume they are in-order already? +} diff --git a/src/miscfileformats.c b/src/miscfileformats.c index 26b693b3..88558348 100644 --- a/src/miscfileformats.c +++ b/src/miscfileformats.c @@ -41,7 +41,6 @@ #include "global.h" #include "io.h" -#include "libraw2crtc.h" #include "loadsave.h" #include "loadsavefuncs.h" #include "misc.h" @@ -1441,1444 +1440,6 @@ void Save_KCF(T_IO_Context * context) } -/** - * Test for SCR file (Amstrad CPC) - * - * SCR file format is from "Advanced OCP Art Studio" : - * http://www.cpcwiki.eu/index.php/Format:Advanced_OCP_Art_Studio_File_Formats - * - * .WIN "window" format is also supported. - * - * For now we check the presence of a valid PAL file. - * If the PAL file is not there the pixel data may still be valid. - * The file size depends on the screen resolution. - * An AMSDOS header would be a good indication but in some cases it may not - * be there. - */ -void Test_SCR(T_IO_Context * context, FILE * file) -{ - // http://orgams.wikidot.com/le-format-impdraw-v2 - // http://orgams.wikidot.com/les-fichiers-win-compatibles-ocp-art-studio - FILE * pal_file; - unsigned long pal_size, file_size; - byte mode, color_anim_flag; - word loading_address = 0; - - File_error = 1; - - if (CPC_check_AMSDOS(file, &loading_address, &file_size)) - { - if (loading_address == 0x170) // iMPdraw v2 - { - byte buffer[0x90]; - fseek(file, 128, SEEK_SET); // right after AMSDOS header - Read_bytes(file, buffer, 0x90); - GFX2_LogHexDump(GFX2_DEBUG, "", buffer, 0, 0x90); - File_error = 0; - return; - } - else if ((loading_address == 0x200 || loading_address == 0xc000) && file_size > 16000) - { - File_error = 0; - return; - } - } - else - file_size = File_length_file(file); - - if (file_size > 16384*2) - return; - - // requires the PAL file - pal_file = Open_file_read_with_alternate_ext(context, "pal"); - if (pal_file == NULL) - return; - /** @todo the palette data can be hidden in the 48 "empty" bytes - * every 2048 bytes of a standard resolution SCR file. - * So we should detect the hidden Z80 code and load them. - * Load address of file is C000. Z80 code :
- * C7D0: 3a d0 d7 cd 1c bd 21 d1 d7 46 48 cd 38 bc af 21 | :.....!..FH.8..!
- * C7E0: d1 d7 46 48 f5 e5 cd 32 bc e1 f1 23 3c fe 10 20 | ..FH...2...#<..
- * C7F0: f1 c3 18 bb 00 00 00 00 00 00 00 00 00 00 00 00 | ................
- * mode and palette :
- * D7D0: 00 1a 00 0c 03 0b 01 0d 17 10 02 0f 09 19 06 00 | ................
- * https://gitlab.com/GrafX2/grafX2/merge_requests/121#note_119964168 - */ - - - if (CPC_check_AMSDOS(pal_file, NULL, &pal_size)) - fseek(pal_file, 128, SEEK_SET); // right after AMSDOS header - else - { - pal_size = File_length_file(pal_file); - fseek(pal_file, 0, SEEK_SET); - } - - if (pal_size != 239) - { - fclose(pal_file); - return; - } - - if (!Read_byte(pal_file, &mode) || !Read_byte(pal_file, &color_anim_flag)) - { - fclose(pal_file); - return; - } - GFX2_Log(GFX2_DEBUG, "Test_SCR() mode=%d color animation flag %02X\n", mode, color_anim_flag); - if (mode <= 2 && (color_anim_flag == 0 || color_anim_flag == 0xff)) - File_error = 0; - fclose(pal_file); -} - -/** - * Load Advanced OCP Art Studio files (Amstrad CPC) - * - * Only standard resolution files (Mode 0 160x200, mode 1 320x200 and - * mode 2 640x200) are supported. The .PAL file presence is required. - * "MJH" RLE packing is supported. - * - * .WIN "window" format is also supported. - * - * @todo Ask user for screen size (or register values) in order to support - * non standard resolutions. - */ -void Load_SCR(T_IO_Context * context) -{ - // The Amstrad CPC screen memory is mapped in a weird mode, somewhere - // between bitmap and textmode. Basically the only way to decode this is to - // emulate the video chip and read the bytes as needed... - // Moreover, the hardware allows the screen to have any size from 8x1 to - // 800x273 pixels, and there is no indication of that in the file besides - // its size. It can also use any of the 3 screen modes. Fortunately this - // last bit of information is stored in the palette file. - // Oh, and BTW, the picture can be offset, and it's even usual to do it, - // because letting 128 pixels unused at the beginning of the file make it a - // lot easier to handle screens using more than 16K of VRam. - // The pixel encoding change with the video mode so we have to know that - // before attempting to load anything... - // As if this wasn't enough, Advanced OCP Art Studio, the reference tool on - // Amstrad, can use RLE packing when saving files, meaning we also have to - // handle that. - - // All this mess enforces us to load (and unpack if needed) the file to a - // temporary 32k buffer before actually decoding it. - FILE * pal_file, * file; - unsigned long real_file_size, file_size, amsdos_file_size = 0; - word addr; - word load_address = 0x4000; // default for OCP Art studio - word display_start = 0x4000; - byte mode, color_anim_flag, color_anim_delay; - byte pal_data[236]; // 12 palettes of 16+1 colors + 16 excluded inks + 16 protected inks - word width, height = 200; - byte bpp; - enum PIXEL_RATIO ratio; - byte * cpc_ram; - word x, y; - int i; - byte sig[3]; - word block_length; - word win_width, win_height; - int is_win = 0; - int columns = 80; - int cpc_plus = 0; - const byte * cpc_plus_pal = NULL; - - File_error = 1; - // requires the PAL file for OCP Art studio files - pal_file = Open_file_read_with_alternate_ext(context, "pal"); - if (pal_file != NULL) - { - file_size = File_length_file(pal_file); - if (CPC_check_AMSDOS(pal_file, NULL, &file_size)) - fseek(pal_file, 128, SEEK_SET); // right after AMSDOS header - else - fseek(pal_file, 0, SEEK_SET); - if (!Read_byte(pal_file, &mode) || !Read_byte(pal_file, &color_anim_flag) - || !Read_byte(pal_file, &color_anim_delay) || !Read_bytes(pal_file, pal_data, 236)) - { - GFX2_Log(GFX2_WARNING, "Load_SCR() failed to load .PAL file\n"); - fclose(pal_file); - return; - } - fclose(pal_file); - GFX2_Log(GFX2_DEBUG, "Load_SCR() mode=%d color animation flag=%02X delay=%u\n", - mode, color_anim_flag, color_anim_delay); - } - - file = Open_file_read(context); - if (file == NULL) - return; - file_size = File_length_file(file); - real_file_size = file_size; - if (CPC_check_AMSDOS(file, &load_address, &amsdos_file_size)) - { - display_start = load_address; - if (file_size < (amsdos_file_size + 128)) - { - GFX2_Log(GFX2_ERROR, "Load_SCR() mismatch in file size. AMSDOS file size %lu, should be %lu\n", amsdos_file_size, file_size - 128); - fclose(file); - return; - } - else if (file_size > (amsdos_file_size + 128)) - GFX2_Log(GFX2_INFO, "Load_SCR() %lu extra bytes at end of file\n", file_size - 128 - amsdos_file_size); - fseek(file, 128, SEEK_SET); // right after AMSDOS header - file_size = amsdos_file_size; - } - else - fseek(file, 0, SEEK_SET); - - if (!Read_bytes(file, sig, 3) || !Read_word_le(file, &block_length)) - { - fclose(file); - return; - } - fseek(file, -5, SEEK_CUR); - - cpc_ram = GFX2_malloc(64*1024); - memset(cpc_ram, 0, 64*1024); - - if (0 != memcmp(sig, "MJH", 3) || block_length > 16384) - { - // raw data - Read_bytes(file, cpc_ram + load_address, file_size); - i = file_size; - } - else - { - // MJH packed format - i = 0; - do - { - if (!Read_bytes(file, sig, 3) || !Read_word_le(file, &block_length)) - break; - if (0 != memcmp(sig, "MJH", 3)) - break; - GFX2_Log(GFX2_DEBUG, " %.3s block %u\n", sig, block_length); - file_size -= 5; - while (block_length > 0) - { - byte code; - if (!Read_byte(file, &code)) - break; - file_size--; - if (code == 1) - { - byte repeat, value; - if (!Read_byte(file, &repeat) || !Read_byte(file, &value)) - break; - file_size -= 2; - do - { - cpc_ram[load_address + i++] = value; - block_length--; - } - while(--repeat != 0); - } - else - { - cpc_ram[load_address + i++] = code; - block_length--; - } - } - GFX2_Log(GFX2_DEBUG, " unpacked %d bytes. remaining bytes in file=%lu\n", - i, file_size); - } - while(file_size > 0 && i < 16384); - } - fclose(file); - - if (i > 5) - { - win_width = cpc_ram[load_address + i - 4] + (cpc_ram[load_address + i - 3] << 8); // in bits - win_height = cpc_ram[load_address + i - 2]; - if (((win_width + 7) >> 3) * win_height + 5 == i) // that's a WIN file ! - { - width = win_width >> (2 - mode); - height = win_height; - is_win = 1; - columns = (win_width + 7) >> 3; - GFX2_Log(GFX2_DEBUG, ".WIN file detected len=%d (%d,%d) %dcols %02X %02X %02X %02X %02X\n", - i, width, height, columns, - cpc_ram[load_address + i - 5], cpc_ram[load_address + i - 4], cpc_ram[load_address + i - 3], - cpc_ram[load_address + i - 2], cpc_ram[load_address + i - 1]); - } - else - { - GFX2_Log(GFX2_DEBUG, ".SCR file. Data length %d\n", i); - if (load_address == 0x170) - { - // fichier iMPdraw v2 - // http://orgams.wikidot.com/le-format-impdraw-v2 - GFX2_Log(GFX2_DEBUG, "Detected \"%s\"\n", cpc_ram + load_address + 6); - mode = cpc_ram[load_address + 0x14] - 0x0e; - cpc_plus = cpc_ram[load_address + 0x3c]; - GFX2_Log(GFX2_DEBUG, "Mode %d CPC %d\n", (int)mode, (int)cpc_plus); - for (addr = load_address + 0x1d; cpc_ram[addr] < 16; addr += 2) - { - GFX2_Log(GFX2_DEBUG, " R%d = &H%02x = %d\n", cpc_ram[addr], cpc_ram[addr+1], cpc_ram[addr+1]); - // see http://www.cpcwiki.eu/index.php/CRTC#The_6845_Registers - switch(cpc_ram[addr]) - { - case 1: - columns = cpc_ram[addr+1] * 2; - break; - case 6: - height = cpc_ram[addr+1] * 8; - break; - case 12: - display_start = ((cpc_ram[addr+1] & 0x30) << 10) | ((cpc_ram[addr+1] & 0x03) << 9); - GFX2_Log(GFX2_DEBUG, " display_start &H%04X\n", display_start); - } - } - snprintf(context->Comment, COMMENT_SIZE, "%s mode %d %s", - cpc_ram + load_address + 7, mode, cpc_plus ? "CPC+" : ""); - if (cpc_plus) - { - // palette at 0x801 (mode at 0x800 ?) - GFX2_LogHexDump(GFX2_DEBUG, "", cpc_ram, 0x800, 0x21); - cpc_plus_pal = cpc_ram + 0x801; - } - else - { - int j; - // palette at 0x7f00 - GFX2_LogHexDump(GFX2_DEBUG, "", cpc_ram, 0x7f00, 16); - for (j = 0; j < 16; j++) - pal_data[12*j] = cpc_ram[0x7f00 + j]; - } - } - else if (load_address == 0x200) - { - /* from HARLEY.SCR : - 0800 00 = mode - 0801-0810 palette (Firmware colors) - 0811 21 47 08 LD HL,0847 ; OVERSCAN_REG_VALUES - 0814 cd 36 08 CALL 0836 ; LOAD_CRTC_REGS - 0817 3a 00 08 LD A,(0800) ; MODE - 081a cd 1c bd CALL BD1C ; Set screen mode - 081d 21 01 08 LD HL,0801 ; PALETTE - 0820 af XOR A - LOOP: - 0821 4e LD C,(HL) - 0822 41 LD B,C - 0823 f5 PUSH AF - 0824 e5 PUSH HL - 0825 cd 32 bc CALL BC32 ; SET ink A to color B,C - 0828 e1 POP HL - 0829 f1 POP AF - 082a 23 INC HL - 082b 3c INC A - 082c fe 10 CMP 10 - 082e 20 f1 JR NZ,0821 ; LOOP - 0830 cd 18 bb CALL BB18 ; Wait key press - 0833 21 55 08 LD HL,0855 ; STANDARD_REG_VALUES - LOAD_CRTC_REGS: - 0836 01 00 bc LD BC,BC00 - LOOP_CRTC: - 0839 7e LD A,(HL) - 083a a7 AND A - 083b c8 RET Z - 083c ed 79 OUT (C),A - 083e 04 INC B - 083f 23 INC HL - 0840 7e LD A,(HL) - 0841 ed 79 OUT (C),A - 0843 23 INC HL - 0844 05 DEC B - 0845 18 f2 JR 0839 ; LOOP_CRTC - OVERSCAN_REG_VALUES: - 0847 01 30 02 32 06 22 07 23 0c 0d 0d 00 00 00 - STANDARD_REG_VALUES: - 0855 01 28 02 2e 06 19 07 1e 0c 30 00 00 - */ - int j; - static const byte CPC_Firmware_Colors[] = { - 0x54, 0x44, 0x55, 0x5c, 0x58, 0x5d, 0x4c, 0x45, 0x4d, - 0x56, 0x46, 0x57, 0x5e, 0x40, 0x5f, 0x4e, 0x47, 0x4f, - 0x52, 0x42, 0x53, 0x5a, 0x59, 0x5b, 0x4a, 0x43, 0x4b }; - mode = cpc_ram[0x800]; - for (j = 0; j < 16; j++) - pal_data[12*j] = CPC_Firmware_Colors[cpc_ram[0x801 + j]]; - addr = 0x847; - if (cpc_ram[0x80bb] == 1) - addr = 0x80bb; - for (; cpc_ram[addr] > 0 && cpc_ram[addr] < 16; addr += 2) - { - GFX2_Log(GFX2_DEBUG, " R%d = &H%02x = %d\n", cpc_ram[addr], cpc_ram[addr+1], cpc_ram[addr+1]); - // see http://www.cpcwiki.eu/index.php/CRTC#The_6845_Registers - switch(cpc_ram[addr]) - { - case 1: - columns = cpc_ram[addr+1] * 2; - break; - case 6: - height = cpc_ram[addr+1] * 8; - break; - case 12: - display_start = (display_start & 0x00ff) | ((cpc_ram[addr+1] & 0x30) << 10) | ((cpc_ram[addr+1] & 0x03) << 9); - break; - case 13: - display_start = (display_start & 0xff00) | cpc_ram[addr+1]; - } - } - } - if (i >= 30000) - { - height = 272; columns = 96; - } - } - } - - switch (mode) - { - case 0: - width = columns * 2; - bpp = 4; - ratio = PIXEL_WIDE; - break; - case 1: - width = columns * 4; - bpp = 2; - ratio = PIXEL_SIMPLE; - break; - case 2: - width = columns * 8; - bpp = 1; - ratio = PIXEL_TALL; - break; - default: - return; // unsupported - } - - if (Config.Clear_palette) - memset(context->Palette,0,sizeof(T_Palette)); - // Setup the palette (amstrad hardware palette) - CPC_set_HW_palette(context->Palette + 0x40); - - // Set the palette for this picture - if (cpc_plus_pal) - { - for (i = 0; i < 16; i++) - { - context->Palette[i].G = cpc_plus_pal[i*2 + 1] * 0x11; - context->Palette[i].R = (cpc_plus_pal[i*2] >> 4) * 0x11; - context->Palette[i].B = (cpc_plus_pal[i*2] & 15) * 0x11; - } - } - else - { - for (i = 0; i < 16; i++) - context->Palette[i] = context->Palette[pal_data[12*i]]; - } - - File_error = 0; - Pre_load(context, width, height, real_file_size, FORMAT_SCR, ratio, bpp); - - if (!is_win) - { - // Standard resolution files have the 200 lines stored in block - // of 25 lines of 80 bytes = 2000 bytes every 2048 bytes. - // so there are 48 bytes unused every 2048 bytes... - for (y = 0; y < 8; y++) - { - addr = display_start + 0x800 * y; - if (y > 0 && (display_start & 0x7ff)) - { - if (!GFX2_is_mem_filled_with(cpc_ram + (addr & 0xf800), 0, display_start & 0x7ff)) - GFX2_LogHexDump(GFX2_DEBUG, "SCR1 ", cpc_ram, - addr & 0xf800, display_start & 0x7ff); - } - addr += (height >> 3) * columns; - block_length = (height >> 3) * columns + (display_start & 0x7ff); - if (block_length <= 0x800) - { - block_length = 0x800 - block_length; - if (!GFX2_is_mem_filled_with(cpc_ram + addr, 0, block_length)) - GFX2_LogHexDump(GFX2_DEBUG, "SCR2 ", cpc_ram, - addr, block_length); - } - else - { - block_length = 0x1000 - block_length; - if (!GFX2_is_mem_filled_with(cpc_ram + addr + 0x4000, 0, block_length)) - GFX2_LogHexDump(GFX2_DEBUG, "SCR2 ", cpc_ram, - addr + 0x4000, block_length); - } - } - //for (j = 0; j < i; j += 2048) - // GFX2_LogHexDump(GFX2_DEBUG, "SCR ", cpc_ram, load_address + j + 2000, 48); - } - - GFX2_Log(GFX2_DEBUG, " display_start &H%04X\n", display_start); - for (y = 0; y < height; y++) - { - const byte * line; - - if (is_win) - addr = display_start + y * columns; - else - { - addr = display_start + ((y >> 3) * columns); - addr = (addr & 0xC7FF) | ((addr & 0x800) << 3); - addr += (y & 7) << 11; - } - //GFX2_Log(GFX2_DEBUG, "line#%d &H%04X\n", y, addr); - line = cpc_ram + addr; - x = 0; - for (i = 0; i < columns; i++) - { - byte pixels = line[i]; - switch (mode) - { - case 0: - Set_pixel(context, x++, y, (pixels & 0x80) >> 7 | (pixels & 0x08) >> 2 | (pixels & 0x20) >> 3 | (pixels & 0x02) << 2); - Set_pixel(context, x++, y, (pixels & 0x40) >> 6 | (pixels & 0x04) >> 1 | (pixels & 0x10) >> 2 | (pixels & 0x01) << 3); - break; - case 1: - do { - // upper nibble is 4 lower color bits, lower nibble is 4 upper color bits - Set_pixel(context, x++, y, (pixels & 0x80) >> 7 | (pixels & 0x08) >> 2); - pixels <<= 1; - } - while ((x & 3) != 0); - break; - case 2: - do { - Set_pixel(context, x++, y, (pixels & 0x80) >> 7); - pixels <<= 1; - } - while ((x & 7) != 0); - } - } - } - - free(cpc_ram); -} - -/** - * Save Amstrad SCR file - * - * guess mode from aspect ratio : - * - normal pixels are mode 1 - * - wide pixels are mode 0 - * - tall pixels are mode 2 - * - * Mode and palette are stored in a .PAL file. - * - * The picture color index should be 0-15, - * The CPC Hardware palette is expected to be set (indexes 64 to 95) - * - * @todo Add possibility to set R9, R12, R13 values - * @todo Add OCP packing support - * @todo Add possibility to include AMSDOS header, with proper loading - * address guessed from r12/r13 values. - */ -void Save_SCR(T_IO_Context * context) -{ - int i, j; - unsigned char* output; - unsigned long outsize = 0; - unsigned char r1 = 0; - int cpc_mode; - FILE* file; - - - switch(Pixel_ratio) - { - case PIXEL_WIDE: - case PIXEL_WIDE2: - cpc_mode = 0; - break; - case PIXEL_TALL: - case PIXEL_TALL2: - case PIXEL_TALL3: - cpc_mode = 2; - break; - default: - cpc_mode = 1; - break; - } - - file = Open_file_write_with_alternate_ext(context, "pal"); - if (file == NULL) - return; - if (!Write_byte(file, cpc_mode) || !Write_byte(file, 0) || !Write_byte(file, 0)) - { - fclose(file); - return; - } - for (i = 0; i < 16; i++) - { - // search for the color in the HW palette (0x40-0x5F) - byte index = 0x40; - while ((index < 0x60) && - !CPC_compare_colors(context->Palette + i, context->Palette + index)) - index++; - if (index >= 0x60) - { - GFX2_Log(GFX2_WARNING, "Save_SCR() color #%i not found in CPC HW palette.\n", i); - index = 0x54 - i; // default - } - for (j = 0; j < 12; j++) // write the same color for the 12 frames - { - Write_byte(file, index); - } - } - // border - for (j = 0; j < 12; j++) - { - Write_byte(file, 0x54); // black - } - // excluded inks - for (i = 0; i < 16; i++) - { - Write_byte(file, 0); - } - // protected inks - for (i = 0; i < 16; i++) - { - Write_byte(file, 0); - } - fclose(file); - - output = raw2crtc(context, cpc_mode, 7, &outsize, &r1, 0x0C, 0); - GFX2_Log(GFX2_DEBUG, "Save_SCR() output=%p outsize=%lu r1=$%02X\n", output, outsize, r1); - - if (output == NULL) - return; - - file = Open_file_write(context); - if (file == NULL) - File_error = 1; - else - { - File_error = 0; - if (!Write_bytes(file, output, outsize)) - File_error = 1; - fclose(file); - } - free (output); -} - -/** - * Test for GO1/GO2/KIT - Amstrad Plus Graphos - * - * This format is made of 3 files - * .KIT hold the palette in "Kit4096" format. There are 16 colors each stored - * as 12 bit RGB in RB0G order. - * .GO1 and GO2 hold each half of the picture (top and bottom) - * The file always cover the whole display of the Plus (196*272 or so) - */ -void Test_GOS(T_IO_Context * context, FILE * file) -{ - FILE *file_oddeve; - unsigned long file_size = 0; - - if (!CPC_check_AMSDOS(file, NULL, &file_size)) - file_size = File_length_file(file); - if (file_size < 16383 || file_size > 16384) { - File_error = 1; - return; - } - - file_oddeve = Open_file_read_with_alternate_ext(context, "GO2"); - if (file_oddeve == NULL) { - File_error = 2; - return; - } - if (!CPC_check_AMSDOS(file_oddeve, NULL, &file_size)) - file_size = File_length_file(file_oddeve); - fclose(file_oddeve); - if (file_size < 16383 || file_size > 16384) { - File_error = 3; - return; - } - - File_error = 0; -} - - -/** - * Load GO1/GO2/KIT - Amstrad CPC Plus Graphos - */ -void Load_GOS(T_IO_Context* context) -{ - FILE *file; - unsigned long file_size; - int i; - int x, y; - byte * pixel_data; - - if (!(file = Open_file_read(context))) - { - File_error = 1; - return; - } - - if (CPC_check_AMSDOS(file, NULL, &file_size)) - fseek(file, 128, SEEK_SET); // right after AMSDOS header - else - file_size = File_length_file(file); - - context->Ratio = PIXEL_WIDE; - Pre_load(context, 192, 272, file_size, FORMAT_GOS, context->Ratio, 0); - context->Width = 192; - context->Height = 272; - - // load pixels - pixel_data = GFX2_malloc(16384); - memset(pixel_data, 0, 16384); - Read_bytes(file, pixel_data, file_size); - - i = 0; - for (y = 0; y < 168; y++) { - x = 0; - while (x < 192) { - byte pixels = pixel_data[i]; - Set_pixel(context, x++, y, (pixels & 0x80) >> 7 | (pixels & 0x08) >> 2 | (pixels & 0x20) >> 3 | (pixels & 0x02) << 2); - Set_pixel(context, x++, y, (pixels & 0x40) >> 6 | (pixels & 0x04) >> 1 | (pixels & 0x10) >> 2 | (pixels & 0x01) << 3); - i++; - } - - i += 0x800; - if (i > 0x3FFF) { - i -= 0x4000; - } else { - i -= 192 / 2; - } - } - - fclose(file); - - // load pixels from GO2 - file = Open_file_read_with_alternate_ext(context, "GO2"); - if (CPC_check_AMSDOS(file, NULL, &file_size)) - fseek(file, 128, SEEK_SET); // right after AMSDOS header - - Read_bytes(file, pixel_data, file_size); - i = 0; - for (y = 168; y < 272; y++) { - x = 0; - while (x < 192) { - byte pixels = pixel_data[i]; - Set_pixel(context, x++, y, (pixels & 0x80) >> 7 | (pixels & 0x08) >> 2 | (pixels & 0x20) >> 3 | (pixels & 0x02) << 2); - Set_pixel(context, x++, y, (pixels & 0x40) >> 6 | (pixels & 0x04) >> 1 | (pixels & 0x10) >> 2 | (pixels & 0x01) << 3); - i++; - } - - i += 0x800; - if (i > 0x3FFF) { - i -= 0x4000; - } else { - i -= 192 / 2; - } - } - - fclose(file); - - file = Open_file_read_with_alternate_ext(context, "KIT"); - if (file == NULL) { - // There is no palette, but that's fine, we can still load the pixels - return; - } - - if (CPC_check_AMSDOS(file, NULL, &file_size)) { - fseek(file, 128, SEEK_SET); // right after AMSDOS header - } else { - file_size = File_length_file(file); - } - - if (Config.Clear_palette) - memset(context->Palette,0,sizeof(T_Palette)); - - File_error = 0; - - if (file_size == 32) - { - for (i = 0; i < 16; i++) - { - uint16_t word; - if (!Read_word_le(file, &word)) - { - File_error = 2; - return; - } - - context->Palette[i].R = ((word >> 4) & 0xF) * 0x11; - context->Palette[i].G = ((word >> 8) & 0xF) * 0x11; - context->Palette[i].B = ((word >> 0) & 0xF) * 0x11; - } - } - else - { - // Setup the palette (amstrad hardware palette) - CPC_set_HW_palette(context->Palette + 0x40); - for (i = 0; i < 16; i++) - { - byte ink; - if (!Read_byte(file, &ink)) - { - File_error = 2; - return; - } - context->Palette[i] = context->Palette[ink]; - } - } - - fclose(file); -} - -/** - * Test for CM5 - Amstrad CPC "Mode 5" picture - * - * This is a format designed by SyX. - * There is one .GFX file in the usual amstrad format - * and a .CM5 file with the palette, which varies over time. - * - * CM5 file is 2049 bytes, GFX is 18432 bytes. - * - * @todo check CM5 contains only valid values [0x40-0x5f] - */ -void Test_CM5(T_IO_Context * context, FILE * file) -{ - // check cm5 file size == 2049 bytes - FILE *file_gfx; - long file_size; - - File_error = 1; - - file_size = File_length_file(file); - if (file_size != 2049) - return; - - // check existence of a .GFX file with the same name - file_gfx = Open_file_read_with_alternate_ext(context, "gfx"); - if (file_gfx == NULL) - return; - file_size = File_length_file(file_gfx); - fclose(file_gfx); - if (file_size != 18432) - return; - - File_error = 0; -} - - -/** - * Load Amstrad CPC "Mode 5" picture - * - * Only support 288x256 resolution as the Mode 5 Viewer app only handles this - * single resoltion. - */ -void Load_CM5(T_IO_Context* context) -{ - // Ensure "8bit" constraint mode is switched on - // Set palette to the CPC hardware colors - // Load the palette data to the 4 colorlayers - FILE *file; - byte value = 0; - int mod=0; - short line = 0; - int tx, ty; - // for preview : - byte ink0; - byte ink1[256]; - byte ink2[256]; - byte ink3[256*6]; - - if (!(file = Open_file_read(context))) - { - File_error = 1; - return; - } - - Pre_load(context, 48*6, 256, 2049, FORMAT_CM5, PIXEL_SIMPLE, 0); - - if (Config.Clear_palette) - { - memset(context->Palette,0,sizeof(T_Palette)); - // setup colors 0,1,2,3 to see something in the thumbnail preview of layer 5 - context->Palette[1].R = 60; - context->Palette[2].B = 60; - context->Palette[3].G = 60; - } - - // Setup the palette (amstrad hardware palette) - CPC_set_HW_palette(context->Palette + 0x40); - - First_color_in_palette = 64; - - if (!Read_byte(file, &ink0)) - File_error = 2; - - // This forces the creation of 5 layers total : - // Needed because the "pixel" functions will seek layer 4 - Set_loading_layer(context, 4); - // Now select layer 1 again - Set_loading_layer(context, 0); - - if (context->Type == CONTEXT_MAIN_IMAGE) - { - Set_image_mode(context, IMAGE_MODE_MODE5); - - // Fill layer with color we just read (Layer 1 - INK 0) - for(ty=0; tyHeight; ty++) - for(tx=0; txWidth; tx++) - Set_pixel(context, tx, ty, ink0); - } - - while(Read_byte(file, &value)) - { - switch(mod) - { - case 0: - // This is color for layer 2 - INK 1 - Set_loading_layer(context, 1); - for(tx=0; txWidth; tx++) - Set_pixel(context, tx, line, value); - ink1[line] = value; - break; - case 1: - // This is color for layer 3 - INK 2 - Set_loading_layer(context, 2); - for(tx=0; txWidth; tx++) - Set_pixel(context, tx, line, value); - ink2[line] = value; - break; - default: - // This is color for a block in layer 4 - INK 3 - Set_loading_layer(context, 3); - for(tx=(mod-2)*48; tx<(mod-1)*48; tx++) - Set_pixel(context, tx, line, value); - ink3[line*6+(mod-2)] = value; - break; - } - mod++; - if (mod > 7) - { - mod = 0; - line++; - } - } - - fclose(file); - - // Load the pixeldata to the 5th layer - file = Open_file_read_with_alternate_ext(context, "gfx"); - if (file == NULL) - { - File_error = 1; - return; - } - Set_loading_layer(context, 4); - - if (context->Type == CONTEXT_PREVIEW) - for (ty = 0; ty < 256; ty++) - for (tx = 0; tx < 48*6; ) - { - Read_byte(file, &value); - for (mod = 0; mod < 4; mod++, tx++, value <<= 1) - { - switch(3 ^ (((value&0x80) >> 7) | ((value&0x8)>>2))) // INK - { - case 0: - Set_pixel(context, tx, ty, ink0); - break; - case 1: - Set_pixel(context, tx, ty, ink1[ty]); - break; - case 2: - Set_pixel(context, tx, ty, ink2[ty]); - break; - default: - Set_pixel(context, tx, ty, ink3[ty*6+(tx/48)]); - } - } - } - else - for (ty = 0; ty < 256; ty++) - for (tx = 0; tx < 48*6; ) - { - Read_byte(file, &value); - Set_pixel(context, tx++, ty, 3 ^ (((value&0x80) >> 7) | ((value&0x8)>>2))); - Set_pixel(context, tx++, ty, 3 ^ (((value&0x40) >> 6) | ((value&0x4)>>1))); - Set_pixel(context, tx++, ty, 3 ^ (((value&0x20) >> 5) | ((value&0x2)>>0))); - Set_pixel(context, tx++, ty, 3 ^ (((value&0x10) >> 4) | ((value&0x1)<<1))); - } - - fclose(file); - -} - - -void Save_CM5(T_IO_Context* context) -{ - FILE* file; - int tx, ty; - - // TODO: Check picture has 5 layers - // TODO: Check the constraints on the layers - // Layer 1 : 1 color Only - // Layer 2 and 3 : 1 color/line - // Layer 4 : 1 color / 48x1 block - // TODO: handle filesize - - if (!(file = Open_file_write(context))) - { - File_error = 1; - return; - } - setvbuf(file, NULL, _IOFBF, 64*1024); - - // Write layer 0 - Set_saving_layer(context, 0); - Write_byte(file, Get_pixel(context, 0, 0)); - for(ty = 0; ty < 256; ty++) - { - Set_saving_layer(context, 1); - Write_byte(file, Get_pixel(context, 0, ty)); - Set_saving_layer(context, 2); - Write_byte(file, Get_pixel(context, 0, ty)); - Set_saving_layer(context, 3); - for(tx = 0; tx < 6; tx++) - { - Write_byte(file, Get_pixel(context, tx*48, ty)); - } - } - - fclose(file); - - // Now the pixeldata - if (!(file = Open_file_write_with_alternate_ext(context, "gfx"))) - { - File_error = 2; - return; - } - setvbuf(file, NULL, _IOFBF, 64*1024); - - Set_saving_layer(context, 4); - - for (ty = 0; ty < 256; ty++) - { - for (tx = 0; tx < 48*6; tx+=4) - { - byte code = 0; - byte pixel; - - pixel = 3-Get_pixel(context, tx+3, ty); - code |= (pixel&2)>>1 | ((pixel & 1)<<4); - pixel = 3-Get_pixel(context, tx+2, ty); - code |= ((pixel&2)<<0) | ((pixel & 1)<<5); - pixel = 3-Get_pixel(context, tx+1, ty); - code |= ((pixel&2)<<1) | ((pixel & 1)<<6); - pixel = 3-Get_pixel(context, tx, ty); - code |= ((pixel&2)<<2) | ((pixel & 1)<<7); - Write_byte(file, code); - } - } - - fclose(file); - File_error = 0; - -} - - -/* Amstrad CPC 'PPH' for Perfect Pix. -// This is a format designed by Rhino. -// There are 3 modes: -// - Mode 'R': 1:1 pixels, 16 colors from the CPC 27 color palette. -// (this is implemented on CPC as two pictures with wide pixels, the "odd" one -// being shifted half a pixel to the right), and flipping) -// - Mode 'B0': wide pixels, up to 126 out of 378 colors. -// (this is implemented as two pictures with wide pixels, sharing the same 16 -// color palette, and flipping) -// - Mode 'B1': 1:1 pixels, 1 fixed color, up to 34 palettes of 9 colors -// (actually 4 colors + flipping) -// -// - The standard CPC formats can also be encapsulated into a PPH file. -// -// http://www.pouet.net/prod.php?which=67770#c766959 -*/ -void Test_PPH(T_IO_Context * context, FILE * file) -{ - FILE *file_oddeve; - byte buffer[6]; - unsigned long file_size; - unsigned int w, h; - unsigned int expected; - - File_error = 1; - - // First check file size is large enough to hold the header - file_size = File_length_file(file); - if (file_size < 11) { - File_error = 1; - return; - } - - // File is large enough for the header, now check if the data makes some sense - if (!Read_bytes(file, buffer, 6)) - return; - if (buffer[0] > 5) { - // Unknown mode - File_error = 2; - return; - } - - w = buffer[1] | (buffer[2] << 8); - if (w < 2 || w > 384) { - // Invalid width - File_error = 3; - return; - } - - h = buffer[3] | (buffer[4] << 8); - if (h < 1 || h > 272) { - // Invalid height - File_error = 4; - return; - } - - if (buffer[5] < 1 || buffer[5] > 28) - { - // Invalid palettes count - File_error = 5; - return; - } - expected = 6; // Size of header - switch(buffer[0]) - { - case 0: - case 3: - case 4: - // Palette size should be 16 bytes, only 1 palette. - if (buffer[5] != 1) { - File_error = 7; - return; - } - expected += 16; - break; - - case 1: - case 5: - expected += buffer[5] * 5 - 1; - break; - - case 2: - // Palette size should be 2 bytes - if (buffer[5] != 1) { - File_error = 7; - return; - } - expected += 2; - break; - } - - if (file_size != expected) - { - File_error = 6; - return; - } - - // check existence of .ODD/.EVE files with the same name - // and the right size - expected = w * h / 4; - file_oddeve = Open_file_read_with_alternate_ext(context, "odd"); - if (file_oddeve == NULL) - return; - file_size = File_length_file(file_oddeve); - fclose (file_oddeve); - if (file_size != expected) - { - File_error = 8; - return; - } - file_oddeve = Open_file_read_with_alternate_ext(context, "eve"); - if (file_oddeve == NULL) - return; - file_size = File_length_file(file_oddeve); - fclose(file_oddeve); - if (file_size != expected) - { - File_error = 8; - return; - } - File_error = 0; -} - - -static uint8_t pph_blend(uint8_t a, uint8_t b) -{ - uint32_t h,l; - if (a > b) { h = a; l = b; } - else { h = b; l = a; } - - return (23 * h + 9 * l) / 32; -} - - -void Load_PPH(T_IO_Context* context) -{ - FILE *file; - FILE *feven; - - // Read in the header - uint8_t mode; - uint16_t width; - uint16_t height; - uint8_t npal; - int i,j; - uint8_t a,b,c,d; - int file_size; - uint8_t pl[16]; - - static const T_Components CPCPAL[27] = - { - { 0x00, 0x02, 0x01 }, { 0x00, 0x02, 0x6B }, { 0x0C, 0x02, 0xF4 }, - { 0x6C, 0x02, 0x01 }, { 0x69, 0x02, 0x68 }, { 0x6C, 0x02, 0xF2 }, - { 0xF3, 0x05, 0x06 }, { 0xF0, 0x02, 0x68 }, { 0xF3, 0x02, 0xF4 }, - { 0x02, 0x78, 0x01 }, { 0x00, 0x78, 0x68 }, { 0x0C, 0x7B, 0xF4 }, - { 0x6E, 0x7B, 0x01 }, { 0x6E, 0x7D, 0x6B }, { 0x6E, 0x7B, 0xF6 }, - { 0xF3, 0x7D, 0x0D }, { 0xF3, 0x7D, 0x6B }, { 0xFA, 0x80, 0xF9 }, - { 0x02, 0xF0, 0x01 }, { 0x00, 0xF3, 0x6B }, { 0x0F, 0xF3, 0xF2 }, - { 0x71, 0xF5, 0x04 }, { 0x71, 0xF3, 0x6B }, { 0x71, 0xF3, 0xF4 }, - { 0xF3, 0xF3, 0x0D }, { 0xF3, 0xF3, 0x6D }, { 0xFF, 0xF3, 0xF9 } - }; - - if (!(file = Open_file_read(context))) - { - File_error = 1; - return; - } - - file_size=File_length_file(file); - - Read_byte(file, &mode); - Read_word_le(file, &width); - Read_word_le(file, &height); - Read_byte(file, &npal); - - if (npal > 16) - npal = 16; - - // Switch to the proper aspect ratio - switch (mode) - { - case 0: - case 4: - context->Ratio = PIXEL_WIDE; - width /= 2; - break; - - case 2: - context->Ratio = PIXEL_TALL; - break; - - case 1: - case 5: - case 3: - context->Ratio = PIXEL_SIMPLE; - break; - } - - Pre_load(context, width, height, file_size, FORMAT_PPH, context->Ratio, 0); - - context->Width = width; - context->Height = height; - - // First of all, detect the mode - // 0, 1, 2 > Load as with SCR files? - // R(3) > Load as single layer, square pixels, 16 colors - // B0(4) > Load as single layer, wide pixels, expand palette with colorcycling - // B1(5) > Load as ??? - // Maybe special mode similar to mode5, with 2 layers + auto-flicker? - - switch (mode) - { - case 0: - case 3: // R - // 16-color palette - for (i = 0; i < 16; i++) - { - uint8_t color; - Read_byte(file, &color); - context->Palette[i] = CPCPAL[color]; - } - break; - - case 1: - case 5: // B1 - { - // Single or multiple 4-color palettes - uint8_t base[4]; - for (j = 0; j < npal; j++) - { - for (i = 0; i < 4; i++) - { - Read_byte(file,&base[i]); - } - for (i = 0; i < 16; i++) - { - context->Palette[i + 16*j].R = pph_blend( - CPCPAL[base[i & 3]].R, CPCPAL[base[i >> 2]].R); - context->Palette[i + 16*j].G = pph_blend( - CPCPAL[base[i & 3]].G, CPCPAL[base[i >> 2]].G); - context->Palette[i + 16*j].B = pph_blend( - CPCPAL[base[i & 3]].B, CPCPAL[base[i >> 2]].B); - } - // TODO this byte marks where this palette stops being used and the - // next starts. We must handle this! - Read_byte(file,&pl[j]); - } - pl[npal - 1] = 255; - break; - } - - case 2: - // Single 2-color palette - break; - - case 4: // B0 - { - // Single 16-color palette + flipping, need to expand palette and - // setup colorcycling ranges. - uint8_t base[16]; - for (i = 0; i < 16; i++) - { - Read_byte(file,&base[i]); - } - - for (i = 0; i < 256; i++) - { - context->Palette[i].R = pph_blend( - CPCPAL[base[i & 15]].R, CPCPAL[base[i >> 4]].R); - context->Palette[i].G = pph_blend( - CPCPAL[base[i & 15]].G, CPCPAL[base[i >> 4]].G); - context->Palette[i].B = pph_blend( - CPCPAL[base[i & 15]].B, CPCPAL[base[i >> 4]].B); - } - } - break; - } - - fclose(file); - - // Load the picture data - // There are two pages, each storing bytes in the CPC vram format but lines in - // linear order. - file = Open_file_read_with_alternate_ext(context, "odd"); - if (file == NULL) - { - File_error = 3; - return; - } - feven = Open_file_read_with_alternate_ext(context, "eve"); - if (feven == NULL) - { - File_error = 4; - fclose(file); - return; - } - - c = 0; - d = 0; - - for (j = 0; j < height; j++) - { - for (i = 0; i < width;) - { - uint8_t even, odd; - Read_byte(feven, &even); - Read_byte(file, &odd); - - switch (mode) - { - case 4: - a = ((even & 0x02) << 2) | ((even & 0x08) >> 2) - | ((even & 0x20) >> 3) | ((even & 0x80) >> 7); - a <<= 4; - a |= ((odd & 0x02) << 2) | (( odd & 0x08) >> 2) - | (( odd & 0x20) >> 3) | (( odd & 0x80) >> 7); - - b = ((even & 0x01) << 3) | ((even & 0x04) >> 1) - | ((even & 0x10) >> 2) | ((even & 0x40) >> 6); - b <<= 4; - b |= ((odd & 0x01) << 3) | (( odd & 0x04) >> 1) - | (( odd & 0x10) >> 2) | (( odd & 0x40) >> 6); - - Set_pixel(context, i++, j, a); - Set_pixel(context, i++, j, b); - break; - - case 3: - a = ((even & 0x02) << 2) | ((even & 0x08) >> 2) - | ((even & 0x20) >> 3) | ((even & 0x80) >> 7); - b = (( odd & 0x02) << 2) | (( odd & 0x08) >> 2) - | (( odd & 0x20) >> 3) | (( odd & 0x80) >> 7); - c = ((even & 0x01) << 3) | ((even & 0x04) >> 1) - | ((even & 0x10) >> 2) | ((even & 0x40) >> 6); - d = (( odd & 0x01) << 3) | (( odd & 0x04) >> 1) - | (( odd & 0x10) >> 2) | (( odd & 0x40) >> 6); - Set_pixel(context, i++, j, j & 1 ? b : a); - Set_pixel(context, i++, j, j & 1 ? a : b); - Set_pixel(context, i++, j, j & 1 ? d : c); - Set_pixel(context, i++, j, j & 1 ? c : d); - break; - - case 5: - if (d >= pl[c]) - { - d = 0; - c++; - } - a = ((even & 0x80) >> 6) | ((even & 0x08) >> 3); - b = (( odd & 0x80) >> 6) | (( odd & 0x08) >> 3); - Set_pixel(context, i++, j, a + (b << 2) + c * 16); - a = ((even & 0x40) >> 5) | ((even & 0x04) >> 2); - b = (( odd & 0x40) >> 5) | (( odd & 0x04) >> 2); - Set_pixel(context, i++, j, a + (b << 2) + c * 16); - a = ((even & 0x20) >> 4) | ((even & 0x02) >> 1); - b = (( odd & 0x20) >> 4) | (( odd & 0x02) >> 1); - Set_pixel(context, i++, j, a + (b << 2) + c * 16); - a = ((even & 0x10) >> 3) | ((even & 0x01) >> 0); - b = (( odd & 0x10) >> 3) | (( odd & 0x01) >> 0); - Set_pixel(context, i++, j, a + (b << 2) + c * 16); - - break; - - default: - File_error = 2; - return; - } - - } - d++; - } - fclose(file); - fclose(feven); - - File_error = 0; -} - -void Save_PPH(T_IO_Context* context) -{ - (void)context; // unused - // TODO - - // Detect mode - // Wide pixels => B0 (4) - // Square pixels: - // - 16 colors used => R - // - more colors used => B1 (if <16 colors per line) - - // Check palette - // B0: use diagonal: 0, 17, 34, ... (assume the other are mixes) - // R: use 16 used colors (or 16 first?) - // B1: find the 16 colors used in a line? Or assume they are in-order already? -} - - /////////////////////////////////// FLI/FLC ///////////////////////////////// typedef struct { dword size; /* Size of FLIC including this header */