Skip to content
212 changes: 208 additions & 4 deletions systems/zx.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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.)
Expand Down Expand Up @@ -810,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;
Expand All @@ -829,10 +836,204 @@ typedef struct {
uint8_t page_nr;
} _zx_z80_page_header;

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) {
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) * 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;
}

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) {
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->flags0 |= (compress && sys->type == ZX_TYPE_48K) ? 0x20 : 0x00;
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 (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;
}
}
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) - 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;
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_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)) {
return false;
}
_zx_z80_page_header* page_hdr = (_zx_z80_page_header*) ptr;
ptr += sizeof(*page_hdr);
page_hdr->page_nr = i + 3;
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 {
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)) {
Expand Down Expand Up @@ -896,8 +1097,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
Expand Down Expand Up @@ -978,6 +1181,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 {
Expand Down