From 7f76030c9cd9c6280c55ae6bc96efef6868f0fd2 Mon Sep 17 00:00:00 2001 From: leiradel Date: Thu, 8 Dec 2022 12:32:55 +0000 Subject: [PATCH 01/11] Implemented uncompressed state save --- systems/zx.h | 121 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 117 insertions(+), 4 deletions(-) diff --git a/systems/zx.h b/systems/zx.h index 01cd7ed4..a31dc9e1 100644 --- a/systems/zx.h +++ b/systems/zx.h @@ -155,6 +155,7 @@ typedef struct { uint32_t tick_count; uint8_t last_mem_config; // last out to 0x7FFD uint8_t last_fe_out; // last out value to 0xFE port + uint8_t last_7ffd_out; // last out value to 0x7FFD port uint8_t blink_counter; // incremented on each vblank int frame_scan_lines; int top_border_scanlines; @@ -200,8 +201,11 @@ void zx_set_joystick_type(zx_t* sys, zx_joystick_type_t type); zx_joystick_type_t zx_joystick_type(zx_t* sys); // set joystick mask (combination of ZX_JOYSTICK_*) void zx_joystick(zx_t* sys, uint8_t mask); +// save a ZX Z80 file with the emulator state +int zx_quicksave_size(zx_t* sys, bool compress); +bool zx_quicksave(zx_t* sys, uint8_t* ptr, int num_bytes, bool compress); // load a ZX Z80 file into the emulator -bool zx_quickload(zx_t* sys, const uint8_t* ptr, int num_bytes); +bool zx_quickload(zx_t* sys, const uint8_t* ptr, int num_bytes); // get the standard framebuffer width and height in pixels int zx_std_display_width(void); int zx_std_display_height(void); @@ -516,7 +520,9 @@ static uint64_t _zx_tick(zx_t* sys, uint64_t pins) { /* Spectrum 128 memory control (0.............0.) http://8bit.yarek.pl/computer/zx.128/ */ - _zx_update_memory_map_zx128(sys, Z80_GET_DATA(pins)); + const uint8_t data = Z80_GET_DATA(pins); + sys->last_7ffd_out = data; + _zx_update_memory_map_zx128(sys, data); } else if (((pins & (Z80_A15|Z80_A1)) == Z80_A15) && (sys->type == ZX_TYPE_128)) { // AY-3-8912 access (1*............0.) @@ -829,10 +835,114 @@ typedef struct { uint8_t page_nr; } _zx_z80_page_header; +int zx_quicksave_size(zx_t* sys, bool compress) { + if (compress) { + // FIXME: compressed not supported yet + return false; + } + int size = sizeof(_zx_z80_header); + if (sys->type == ZX_TYPE_48K) { + size += 0x4000 * 3; + } + else if (sys->type == ZX_TYPE_128) { + size += sizeof(_zx_z80_ext_header) + (sizeof(_zx_z80_page_header) + 0x4000) * 8; + } + return size; +} + static bool _zx_overflow(const uint8_t* ptr, intptr_t num_bytes, const uint8_t* end_ptr) { return (ptr + num_bytes) > end_ptr; } +bool zx_quicksave(zx_t* sys, uint8_t* ptr, int num_bytes, bool compress) { + if (compress) { + // FIXME: compressed not supported yet + return false; + } + const uint8_t* end_ptr = ptr + num_bytes; + if (_zx_overflow(ptr, sizeof(_zx_z80_header), end_ptr)) { + return false; + } + _zx_z80_header* hdr = (_zx_z80_header*) ptr; + ptr += sizeof(*hdr); + memset(hdr, 0, sizeof(*hdr)); + hdr->A = sys->cpu.a; hdr->F = sys->cpu.f; + hdr->B = sys->cpu.b; hdr->C = sys->cpu.c; + hdr->D = sys->cpu.d; hdr->E = sys->cpu.e; + hdr->H = sys->cpu.h; hdr->L = sys->cpu.l; + hdr->IX_h = sys->cpu.ix >> 8; hdr->IX_l = sys->cpu.ix; + hdr->IY_h = sys->cpu.iy >> 8; hdr->IY_l = sys->cpu.iy; + hdr->A_ = sys->cpu.af2 >> 8; hdr->F_ = sys->cpu.af2; + hdr->B_ = sys->cpu.bc2 >> 8; hdr->C_ = sys->cpu.bc2; + hdr->D_ = sys->cpu.de2 >> 8; hdr->E_ = sys->cpu.de2; + hdr->H_ = sys->cpu.hl2 >> 8; hdr->L_ = sys->cpu.hl2; + hdr->SP_h = sys->cpu.sp >> 8; hdr->SP_l = sys->cpu.sp; + hdr->I = sys->cpu.i; + hdr->R = sys->cpu.r & 0x7F; hdr->flags0 |= (sys->cpu.r>>7) & 1; + hdr->IFF2 = sys->cpu.iff2; + hdr->EI = sys->cpu.iff1; + hdr->flags1 |= sys->cpu.im; + switch (sys->border_color) { + default: + case 0xFF000000 & 0xFFD7D7D7: hdr->flags0 |= 0<<1; break; + case 0xFFFF0000 & 0xFFD7D7D7: hdr->flags0 |= 1<<1; break; + case 0xFF0000FF & 0xFFD7D7D7: hdr->flags0 |= 2<<1; break; + case 0xFFFF00FF & 0xFFD7D7D7: hdr->flags0 |= 3<<1; break; + case 0xFF00FF00 & 0xFFD7D7D7: hdr->flags0 |= 4<<1; break; + case 0xFFFFFF00 & 0xFFD7D7D7: hdr->flags0 |= 5<<1; break; + case 0xFF00FFFF & 0xFFD7D7D7: hdr->flags0 |= 6<<1; break; + case 0xFFFFFFFF & 0xFFD7D7D7: hdr->flags0 |= 7<<1; break; + } + if (sys->type == ZX_TYPE_48K) { + hdr->PC_h = sys->cpu.pc >> 8; hdr->PC_l = sys->cpu.pc; + if (_zx_overflow(ptr, 0x4000 * 3, end_ptr)) { + return false; + } + memcpy(ptr, sys->ram[0], 0x4000); + memcpy(ptr + 0x4000 * 1, sys->ram[1], 0x4000); + memcpy(ptr + 0x4000 * 2, sys->ram[2], 0x4000); + ptr += 0x4000 * 3; + } + else if (sys->type == ZX_TYPE_128) { + if (_zx_overflow(ptr, sizeof(_zx_z80_ext_header), end_ptr)) { + return false; + } + _zx_z80_ext_header* ext_hdr = (_zx_z80_ext_header*) ptr; + ptr += sizeof(*ext_hdr); + memset(ext_hdr, 0, sizeof(*ext_hdr)); + ext_hdr->len_h = sizeof(*ext_hdr)>>8; + ext_hdr->len_l = sizeof(*ext_hdr); + ext_hdr->PC_h = sys->cpu.pc >> 8; ext_hdr->PC_l = sys->cpu.pc; + ext_hdr->hw_mode = 4; + ext_hdr->out_7ffd = sys->last_7ffd_out; + ext_hdr->flags = 0x07; + ext_hdr->out_fffd = sys->ay.addr; + for (uint8_t i = 0; i < AY38910_NUM_REGISTERS; i++) { + ext_hdr->audio[i] = sys->ay.reg[i]; + } + ext_hdr->rom_2000_3fff = 0xFF; + for (uint8_t i = 0; i < 8; i++) { + if (_zx_overflow(ptr, sizeof(_zx_z80_page_header), end_ptr)) { + return false; + } + _zx_z80_page_header* page_hdr = (_zx_z80_page_header*) ptr; + ptr += sizeof(*page_hdr); + page_hdr->page_nr = i + 3; + if (_zx_overflow(ptr, 0x4000, end_ptr)) { + return false; + } + memcpy(ptr, sys->ram[i], 0x4000); + page_hdr->len_h = 0xFF; page_hdr->len_l = 0xFF; + ptr += 0x4000; + } + } + else { + return false; + } + + return true; +} + bool zx_quickload(zx_t* sys, const uint8_t* ptr, int num_bytes) { const uint8_t* end_ptr = ptr + num_bytes; if (_zx_overflow(ptr, sizeof(_zx_z80_header), end_ptr)) { @@ -896,8 +1006,10 @@ bool zx_quickload(zx_t* sys, const uint8_t* ptr, int num_bytes) { dst_ptr = sys->ram[page_index]; } if (0xFFFF == src_len) { - // FIXME: uncompressed not supported yet - return false; + if (_zx_overflow(ptr, 0x4000, end_ptr)) { + return false; + } + memcpy(dst_ptr, ptr, 0x4000); } else { // compressed @@ -978,6 +1090,7 @@ bool zx_quickload(zx_t* sys, const uint8_t* ptr, int num_bytes) { } ay38910_set_addr_latch(&sys->ay, ext_hdr->out_fffd); _zx_update_memory_map_zx128(sys, ext_hdr->out_7ffd); + sys->last_7ffd_out = ext_hdr->out_7ffd; } } else { From 2e353f781472ab8894943aa7063027d7e0c7f344 Mon Sep 17 00:00:00 2001 From: leiradel Date: Thu, 8 Dec 2022 13:06:22 +0000 Subject: [PATCH 02/11] Added missing high T state counter --- systems/zx.h | 1 + 1 file changed, 1 insertion(+) diff --git a/systems/zx.h b/systems/zx.h index a31dc9e1..b5bcbf50 100644 --- a/systems/zx.h +++ b/systems/zx.h @@ -816,6 +816,7 @@ typedef struct { uint8_t audio[16]; uint8_t tlow_l; uint8_t tlow_h; + uint8_t thi; uint8_t spectator_flags; uint8_t mgt_rom_paged; uint8_t multiface_rom_paged; From 8b99d6c25af6e358055bdbf0d2b08b41d9c5043b Mon Sep 17 00:00:00 2001 From: leiradel Date: Thu, 8 Dec 2022 13:06:39 +0000 Subject: [PATCH 03/11] The extended header length doesn't include the length word --- systems/zx.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/systems/zx.h b/systems/zx.h index b5bcbf50..d5a6fa63 100644 --- a/systems/zx.h +++ b/systems/zx.h @@ -911,8 +911,8 @@ bool zx_quicksave(zx_t* sys, uint8_t* ptr, int num_bytes, bool compress) { _zx_z80_ext_header* ext_hdr = (_zx_z80_ext_header*) ptr; ptr += sizeof(*ext_hdr); memset(ext_hdr, 0, sizeof(*ext_hdr)); - ext_hdr->len_h = sizeof(*ext_hdr)>>8; - ext_hdr->len_l = sizeof(*ext_hdr); + ext_hdr->len_h = (sizeof(*ext_hdr) - 2)>>8; + ext_hdr->len_l = sizeof(*ext_hdr) - 2; ext_hdr->PC_h = sys->cpu.pc >> 8; ext_hdr->PC_l = sys->cpu.pc; ext_hdr->hw_mode = 4; ext_hdr->out_7ffd = sys->last_7ffd_out; From e0d635343edc635b7ecafc0be1d6eab78d604ee8 Mon Sep 17 00:00:00 2001 From: leiradel Date: Thu, 8 Dec 2022 13:07:04 +0000 Subject: [PATCH 04/11] Addresses from 0x0000 to 0x1fff are also ROM --- systems/zx.h | 1 + 1 file changed, 1 insertion(+) diff --git a/systems/zx.h b/systems/zx.h index d5a6fa63..2ba51a27 100644 --- a/systems/zx.h +++ b/systems/zx.h @@ -921,6 +921,7 @@ bool zx_quicksave(zx_t* sys, uint8_t* ptr, int num_bytes, bool compress) { for (uint8_t i = 0; i < AY38910_NUM_REGISTERS; i++) { ext_hdr->audio[i] = sys->ay.reg[i]; } + ext_hdr->rom_0000_1fff = 0xFF; ext_hdr->rom_2000_3fff = 0xFF; for (uint8_t i = 0; i < 8; i++) { if (_zx_overflow(ptr, sizeof(_zx_z80_page_header), end_ptr)) { From 4e228abf2dd7714a8899c631875f163445458604 Mon Sep 17 00:00:00 2001 From: leiradel Date: Thu, 8 Dec 2022 13:07:44 +0000 Subject: [PATCH 05/11] No need to test the overflow twice --- systems/zx.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/systems/zx.h b/systems/zx.h index 2ba51a27..9c7f67ae 100644 --- a/systems/zx.h +++ b/systems/zx.h @@ -924,15 +924,12 @@ bool zx_quicksave(zx_t* sys, uint8_t* ptr, int num_bytes, bool compress) { ext_hdr->rom_0000_1fff = 0xFF; ext_hdr->rom_2000_3fff = 0xFF; for (uint8_t i = 0; i < 8; i++) { - if (_zx_overflow(ptr, sizeof(_zx_z80_page_header), end_ptr)) { + if (_zx_overflow(ptr, sizeof(_zx_z80_page_header) + 0x4000, end_ptr)) { return false; } _zx_z80_page_header* page_hdr = (_zx_z80_page_header*) ptr; ptr += sizeof(*page_hdr); page_hdr->page_nr = i + 3; - if (_zx_overflow(ptr, 0x4000, end_ptr)) { - return false; - } memcpy(ptr, sys->ram[i], 0x4000); page_hdr->len_h = 0xFF; page_hdr->len_l = 0xFF; ptr += 0x4000; From 578cfafa772d218278b1a64e27d9767867867fea Mon Sep 17 00:00:00 2001 From: leiradel Date: Thu, 8 Dec 2022 13:08:14 +0000 Subject: [PATCH 06/11] All page header initialization in one place --- systems/zx.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/systems/zx.h b/systems/zx.h index 9c7f67ae..374dcdd4 100644 --- a/systems/zx.h +++ b/systems/zx.h @@ -929,9 +929,9 @@ bool zx_quicksave(zx_t* sys, uint8_t* ptr, int num_bytes, bool compress) { } _zx_z80_page_header* page_hdr = (_zx_z80_page_header*) ptr; ptr += sizeof(*page_hdr); + page_hdr->len_h = 0xFF; page_hdr->len_l = 0xFF; page_hdr->page_nr = i + 3; memcpy(ptr, sys->ram[i], 0x4000); - page_hdr->len_h = 0xFF; page_hdr->len_l = 0xFF; ptr += 0x4000; } } From 10526d7290bb549cfc79834e45f62e027a11c432 Mon Sep 17 00:00:00 2001 From: leiradel Date: Thu, 8 Dec 2022 13:19:27 +0000 Subject: [PATCH 07/11] Added _zx_compress; use it to compute the state size --- systems/zx.h | 81 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 75 insertions(+), 6 deletions(-) diff --git a/systems/zx.h b/systems/zx.h index 374dcdd4..eefa56de 100644 --- a/systems/zx.h +++ b/systems/zx.h @@ -836,17 +836,86 @@ typedef struct { uint8_t page_nr; } _zx_z80_page_header; -int zx_quicksave_size(zx_t* sys, bool compress) { - if (compress) { - // FIXME: compressed not supported yet - return false; +static int _zx_compress(zx_t* sys, int read, int num_bytes, uint8_t* dest, int dest_bytes) { + const int read_end = read + num_bytes; + int write = 0; + bool was_ed = false; + while (read < read_end) { + const uint8_t byte = sys->ram[read >> 14][read & 0x3FFF]; + read++; + uint8_t count = 1; + while (read < read_end && sys->ram[read >> 14][read & 0x3FFF] == byte && count < 255) { + read++; + count++; + } + if ((byte == 0xED && count >= 2) || (byte != 0xED && count >= 5)) { + if (was_ed) { + if (write + 5 > dest_bytes) { + return 0; + } + if (dest != NULL) { + dest[write] = byte; + dest[write + 1] = 0xED; dest[write + 2] = 0xED; + dest[write + 3] = count - 1; dest[write + 4] = byte; + } + write += 5; + was_ed = false; + } + else { + if (write + 4 > dest_bytes) { + return 0; + } + if (dest != NULL) { + dest[write] = 0xED; dest[write + 1] = 0xED; + dest[write + 2] = count; dest[write + 3] = byte; + } + write += 4; + } + } + else { + if (write + count > dest_bytes) { + return 0; + } + if (dest != NULL) { + for (uint8_t i = 0; i < count; i++) { + dest[write + i] = byte; + } + } + write += count; + was_ed = byte == 0xED; + } } + return write; +} + +int zx_quicksave_size(zx_t* sys, bool compress) { int size = sizeof(_zx_z80_header); if (sys->type == ZX_TYPE_48K) { - size += 0x4000 * 3; + if (compress) { + int comp = _zx_compress(sys, 0, 0x4000 * 3, NULL, INT_MAX); + if (comp == 0) { + return 0; + } + size += comp + 4; + } + else { + size += 0x4000 * 3; + } } else if (sys->type == ZX_TYPE_128) { - size += sizeof(_zx_z80_ext_header) + (sizeof(_zx_z80_page_header) + 0x4000) * 8; + size += sizeof(_zx_z80_ext_header) + sizeof(_zx_z80_page_header) * 8; + if (compress) { + for (uint8_t i = 0; i < 8; i++) { + int comp = _zx_compress(sys, 0x4000 * i, 0x4000, NULL, INT_MAX); + if (comp == 0) { + return 0; + } + size += comp; + } + } + else { + size += 0x4000 * 8; + } } return size; } From b140e629c860bbfa4a9b38d6ce3271492e85d2ee Mon Sep 17 00:00:00 2001 From: leiradel Date: Thu, 8 Dec 2022 13:19:59 +0000 Subject: [PATCH 08/11] Set the flag to tell the 48K RAM block in compressed --- systems/zx.h | 1 + 1 file changed, 1 insertion(+) diff --git a/systems/zx.h b/systems/zx.h index eefa56de..cfc2f437 100644 --- a/systems/zx.h +++ b/systems/zx.h @@ -949,6 +949,7 @@ bool zx_quicksave(zx_t* sys, uint8_t* ptr, int num_bytes, bool compress) { hdr->SP_h = sys->cpu.sp >> 8; hdr->SP_l = sys->cpu.sp; hdr->I = sys->cpu.i; hdr->R = sys->cpu.r & 0x7F; hdr->flags0 |= (sys->cpu.r>>7) & 1; + hdr->flags0 |= (compress && sys->type == ZX_TYPE_48K) ? 0x20 : 0x00; hdr->IFF2 = sys->cpu.iff2; hdr->EI = sys->cpu.iff1; hdr->flags1 |= sys->cpu.im; From 4a384bd84ee78825b7a9ecc014d1cd49ba33f30c Mon Sep 17 00:00:00 2001 From: leiradel Date: Thu, 8 Dec 2022 13:20:35 +0000 Subject: [PATCH 09/11] Compress the 48K RAM block if requested --- systems/zx.h | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/systems/zx.h b/systems/zx.h index cfc2f437..940a7f6e 100644 --- a/systems/zx.h +++ b/systems/zx.h @@ -966,13 +966,26 @@ bool zx_quicksave(zx_t* sys, uint8_t* ptr, int num_bytes, bool compress) { } if (sys->type == ZX_TYPE_48K) { hdr->PC_h = sys->cpu.pc >> 8; hdr->PC_l = sys->cpu.pc; - if (_zx_overflow(ptr, 0x4000 * 3, end_ptr)) { - return false; + if (compress) { + int comp = _zx_compress(sys, 0, 0x4000 * 3, ptr, (int)(end_ptr - ptr)); + if (comp == 0) { + return false; + } + ptr += comp; + if (_zx_overflow(ptr, 4, end_ptr)) { + return false; + } + *ptr++ = 0x00; *ptr++ = 0xED; *ptr++ = 0xED; *ptr++ = 0x00; + } + else { + if (_zx_overflow(ptr, 0x4000 * 3, end_ptr)) { + return false; + } + memcpy(ptr, sys->ram[0], 0x4000); + memcpy(ptr + 0x4000 * 1, sys->ram[1], 0x4000); + memcpy(ptr + 0x4000 * 2, sys->ram[2], 0x4000); + ptr += 0x4000 * 3; } - memcpy(ptr, sys->ram[0], 0x4000); - memcpy(ptr + 0x4000 * 1, sys->ram[1], 0x4000); - memcpy(ptr + 0x4000 * 2, sys->ram[2], 0x4000); - ptr += 0x4000 * 3; } else if (sys->type == ZX_TYPE_128) { if (_zx_overflow(ptr, sizeof(_zx_z80_ext_header), end_ptr)) { From 0800d1e016073b7e4b2bbc24801316c5fee32207 Mon Sep 17 00:00:00 2001 From: leiradel Date: Thu, 8 Dec 2022 13:21:09 +0000 Subject: [PATCH 10/11] Compress the 128 RAM pages if requested --- systems/zx.h | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/systems/zx.h b/systems/zx.h index 940a7f6e..92469d59 100644 --- a/systems/zx.h +++ b/systems/zx.h @@ -1007,15 +1007,28 @@ bool zx_quicksave(zx_t* sys, uint8_t* ptr, int num_bytes, bool compress) { ext_hdr->rom_0000_1fff = 0xFF; ext_hdr->rom_2000_3fff = 0xFF; for (uint8_t i = 0; i < 8; i++) { - if (_zx_overflow(ptr, sizeof(_zx_z80_page_header) + 0x4000, end_ptr)) { + if (_zx_overflow(ptr, sizeof(_zx_z80_page_header), end_ptr)) { return false; } _zx_z80_page_header* page_hdr = (_zx_z80_page_header*) ptr; ptr += sizeof(*page_hdr); - page_hdr->len_h = 0xFF; page_hdr->len_l = 0xFF; page_hdr->page_nr = i + 3; - memcpy(ptr, sys->ram[i], 0x4000); - ptr += 0x4000; + if (compress) { + int comp = _zx_compress(sys, 0x4000 * i, 0x4000, ptr, (int)(end_ptr - ptr)); + if (comp == 0) { + return false; + } + page_hdr->len_h = comp >> 8; page_hdr->len_l = comp; + ptr += comp; + } + else { + if (_zx_overflow(ptr, 0x4000, end_ptr)) { + return false; + } + memcpy(ptr, sys->ram[i], 0x4000); + page_hdr->len_h = 0xFF; page_hdr->len_l = 0xFF; + ptr += 0x4000; + } } } else { From 8343dd844f7689469cb1aebe40e61c432b190fba Mon Sep 17 00:00:00 2001 From: leiradel Date: Thu, 8 Dec 2022 13:21:20 +0000 Subject: [PATCH 11/11] Allow state compression --- systems/zx.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/systems/zx.h b/systems/zx.h index 92469d59..078e5103 100644 --- a/systems/zx.h +++ b/systems/zx.h @@ -925,10 +925,6 @@ static bool _zx_overflow(const uint8_t* ptr, intptr_t num_bytes, const uint8_t* } bool zx_quicksave(zx_t* sys, uint8_t* ptr, int num_bytes, bool compress) { - if (compress) { - // FIXME: compressed not supported yet - return false; - } const uint8_t* end_ptr = ptr + num_bytes; if (_zx_overflow(ptr, sizeof(_zx_z80_header), end_ptr)) { return false;