From 2f2b792edf2c103cd73d4a324acaedf61fd04db0 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 16 Nov 2018 01:53:01 +0200 Subject: [PATCH] SGB save states --- Core/display.c | 12 +++--- Core/gb.c | 17 ++++---- Core/gb.h | 19 ++------- Core/joypad.c | 11 ++--- Core/save_state.c | 32 +++++++++++--- Core/sgb.c | 106 +++++++++++++++++++++++----------------------- Core/sgb.h | 19 ++++++++- 7 files changed, 122 insertions(+), 94 deletions(-) diff --git a/Core/display.c b/Core/display.c index 9c6e9cb..f5ba685 100644 --- a/Core/display.c +++ b/Core/display.c @@ -132,10 +132,10 @@ static void display_vblank(GB_gameboy_t *gb) if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || gb->stopped) || gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON)) { /* LCD is off, set screen to white or black (if LCD is on in stop mode) */ - if (gb->sgb_screen_buffer) { + if (gb->sgb) { uint8_t color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped ? 0 : 0xFF; for (unsigned i = 0; i < WIDTH * LINES; i++) { - gb ->sgb_screen_buffer[i] = color; + gb->sgb->screen_buffer[i] = color; } } else { @@ -386,8 +386,8 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) if (!gb->cgb_mode) { pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3); } - if (gb->sgb_screen_buffer) { - gb->sgb_screen_buffer[gb->position_in_line + gb->current_line * WIDTH] = pixel; + if (gb->sgb) { + gb->sgb->screen_buffer[gb->position_in_line + gb->current_line * WIDTH] = pixel; } else { gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->background_palettes_rgb[fifo_item->palette * 4 + pixel]; @@ -400,8 +400,8 @@ static void render_pixel_if_possible(GB_gameboy_t *gb) /* Todo: Verify access timings */ pixel = ((gb->io_registers[oam_fifo_item->palette? GB_IO_OBP1 : GB_IO_OBP0] >> (pixel << 1)) & 3); } - if (gb->sgb_screen_buffer) { - gb->sgb_screen_buffer[gb->position_in_line + gb->current_line * WIDTH] =pixel; + if (gb->sgb) { + gb->sgb->screen_buffer[gb->position_in_line + gb->current_line * WIDTH] =pixel; } else { gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->sprite_palettes_rgb[oam_fifo_item->palette * 4 + pixel]; diff --git a/Core/gb.c b/Core/gb.c index 29d6df0..b38715c 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -139,8 +139,8 @@ void GB_free(GB_gameboy_t *gb) if (gb->breakpoints) { free(gb->breakpoints); } - if (gb->sgb_screen_buffer) { - free(gb->sgb_screen_buffer); + if (gb->sgb) { + free(gb->sgb); } #ifndef DISABLE_DEBUGGER GB_debugger_clear_symbols(gb); @@ -643,17 +643,18 @@ void GB_reset(GB_gameboy_t *gb) gb->accessed_oam_row = -1; - gb->sgb_player_count = 1; if (GB_is_sgb(gb)) { - if (!gb->sgb_screen_buffer) { - gb->sgb_screen_buffer = malloc(160 * 144); + if (!gb->sgb) { + gb->sgb = malloc(sizeof(*gb->sgb)); } + gb->sgb->player_count = 1; + } else { - if (gb->sgb_screen_buffer) { - free(gb->sgb_screen_buffer); - gb->sgb_screen_buffer = NULL; + if (gb->sgb) { + free(gb->sgb); + gb->sgb = NULL; } } diff --git a/Core/gb.h b/Core/gb.h index 83f880c..e9f1387 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -52,7 +52,6 @@ typedef union { typedef enum { - // GB_MODEL_DMG_0 = 0x000, // GB_MODEL_DMG_A = 0x001, GB_MODEL_DMG_B = 0x002, @@ -466,21 +465,6 @@ struct GB_gameboy_internal_s { uint8_t mode_for_interrupt; bool lyc_interrupt_line; ); - - /* Super Game Boy state, only dumped/loaded for relevant models */ - GB_SECTION(sgb, - uint8_t sgb_command[16 * 7]; - uint16_t sgb_command_write_index; - bool sgb_ready_for_pulse; - bool sgb_ready_for_write; - bool sgb_ready_for_stop; - bool sgb_disable_commands; - - /* Screen buffer */ - uint8_t *sgb_screen_buffer; - /* Multiplayer Input */ - uint8_t sgb_player_count, sgb_current_player; - ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ /* This data is reserved on reset and must come last in the struct */ @@ -577,6 +561,9 @@ struct GB_gameboy_internal_s { unsigned pos; } *rewind_sequences; // lasts about 4 seconds size_t rewind_pos; + + /* SGB - saved and allocated optionally */ + GB_sgb_t *sgb; /* Misc */ bool turbo; diff --git a/Core/joypad.c b/Core/joypad.c index b4e0f76..053ef2f 100644 --- a/Core/joypad.c +++ b/Core/joypad.c @@ -11,10 +11,11 @@ void GB_update_joyp(GB_gameboy_t *gb) previous_state = gb->io_registers[GB_IO_JOYP] & 0xF; key_selection = (gb->io_registers[GB_IO_JOYP] >> 4) & 3; gb->io_registers[GB_IO_JOYP] &= 0xF0; + uint8_t current_player = gb->sgb? gb->sgb->current_player : 0; switch (key_selection) { case 3: - if (gb->sgb_player_count > 1) { - gb->io_registers[GB_IO_JOYP] |= 0xF - gb->sgb_current_player; + if (gb->sgb && gb->sgb->player_count > 1) { + gb->io_registers[GB_IO_JOYP] |= 0xF - current_player; } else { /* Nothing is wired, all up */ @@ -25,7 +26,7 @@ void GB_update_joyp(GB_gameboy_t *gb) case 2: /* Direction keys */ for (uint8_t i = 0; i < 4; i++) { - gb->io_registers[GB_IO_JOYP] |= (!gb->keys[gb->sgb_current_player][i]) << i; + gb->io_registers[GB_IO_JOYP] |= (!gb->keys[current_player][i]) << i; } /* Forbid pressing two opposing keys, this breaks a lot of games; even if it's somewhat possible. */ if (!(gb->io_registers[GB_IO_JOYP] & 1)) { @@ -39,13 +40,13 @@ void GB_update_joyp(GB_gameboy_t *gb) case 1: /* Other keys */ for (uint8_t i = 0; i < 4; i++) { - gb->io_registers[GB_IO_JOYP] |= (!gb->keys[gb->sgb_current_player][i + 4]) << i; + gb->io_registers[GB_IO_JOYP] |= (!gb->keys[current_player][i + 4]) << i; } break; case 0: for (uint8_t i = 0; i < 4; i++) { - gb->io_registers[GB_IO_JOYP] |= (!(gb->keys[gb->sgb_current_player][i] || gb->keys[gb->sgb_current_player][i + 4])) << i; + gb->io_registers[GB_IO_JOYP] |= (!(gb->keys[current_player][i] || gb->keys[current_player][i + 4])) << i; } break; diff --git a/Core/save_state.c b/Core/save_state.c index 2278853..26c9b52 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -36,6 +36,10 @@ int GB_save_state(GB_gameboy_t *gb, const char *path) if (!DUMP_SECTION(gb, f, rtc )) goto error; if (!DUMP_SECTION(gb, f, video )) goto error; + if (GB_is_sgb(gb)) { + if (!dump_section(f, gb->sgb, sizeof(*gb->sgb))) goto error; + } + if (fwrite(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) { goto error; @@ -69,6 +73,7 @@ size_t GB_get_save_state_size(GB_gameboy_t *gb) + GB_SECTION_SIZE(apu ) + sizeof(uint32_t) + GB_SECTION_SIZE(rtc ) + sizeof(uint32_t) + GB_SECTION_SIZE(video ) + sizeof(uint32_t) + + (GB_is_sgb(gb)? sizeof(*gb->sgb) + sizeof(uint32_t) : 0) + gb->mbc_ram_size + gb->ram_size + gb->vram_size; @@ -100,6 +105,10 @@ void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer) DUMP_SECTION(gb, buffer, rtc ); DUMP_SECTION(gb, buffer, video ); + if (GB_is_sgb(gb)) { + buffer_dump_section(&buffer, gb->sgb, sizeof(*gb->sgb)); + } + buffer_write(gb->mbc_ram, gb->mbc_ram_size, &buffer); buffer_write(gb->ram, gb->ram_size, &buffer); @@ -133,27 +142,32 @@ static bool read_section(FILE *f, void *dest, uint32_t size) static bool verify_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t *save) { if (gb->magic != save->magic) { - GB_log(gb, "File is not a save state, or is from an incompatible operating system.\n"); + GB_log(gb, "The file is not a save state, or is from an incompatible operating system.\n"); return false; } if (gb->version != save->version) { - GB_log(gb, "Save state is for a different version of SameBoy.\n"); + GB_log(gb, "The save state is for a different version of SameBoy.\n"); return false; } if (gb->mbc_ram_size < save->mbc_ram_size) { - GB_log(gb, "Save state has non-matching MBC RAM size.\n"); + GB_log(gb, "The save state has non-matching MBC RAM size.\n"); return false; } if (gb->ram_size != save->ram_size) { - GB_log(gb, "Save state has non-matching RAM size. Try changing emulated model.\n"); + GB_log(gb, "The save state has non-matching RAM size. Try changing the emulated model.\n"); return false; } if (gb->vram_size != save->vram_size) { - GB_log(gb, "Save state has non-matching VRAM size. Try changing emulated model.\n"); + GB_log(gb, "The save state has non-matching VRAM size. Try changing the emulated model.\n"); + return false; + } + + if (GB_is_sgb(gb) != GB_is_sgb(save)) { + GB_log(gb, "The save state is %sfor a Super Game Boy. Try changing the emulated model.\n", GB_is_sgb(save)? "" : "not "); return false; } @@ -190,6 +204,10 @@ int GB_load_state(GB_gameboy_t *gb, const char *path) goto error; } + if (GB_is_sgb(gb)) { + if (!read_section(f, gb->sgb, sizeof(*gb->sgb))) goto error; + } + memset(gb->mbc_ram + save.mbc_ram_size, 0xFF, gb->mbc_ram_size - save.mbc_ram_size); if (fread(gb->mbc_ram, 1, save.mbc_ram_size, f) != save.mbc_ram_size) { fclose(f); @@ -289,6 +307,10 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le return -1; } + if (GB_is_sgb(gb)) { + if (!buffer_read_section(&buffer, &length, gb->sgb, sizeof(*gb->sgb))) return -1; + } + memset(gb->mbc_ram + save.mbc_ram_size, 0xFF, gb->mbc_ram_size - save.mbc_ram_size); if (buffer_read(gb->mbc_ram, save.mbc_ram_size, &buffer, &length) != save.mbc_ram_size) { return -1; diff --git a/Core/sgb.c b/Core/sgb.c index e3f71ad..f23abfa 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -1,4 +1,4 @@ -#include "sgb.h" +#include "gb.h" enum { MLT_REQ = 0x11, @@ -15,41 +15,41 @@ static void command_ready(GB_gameboy_t *gb) 0xE content bytes. The last command, FB, is padded with zeros, so information past the header is not sent. */ - if ((gb->sgb_command[0] & 0xF1) == 0xF1) { + if ((gb->sgb->command[0] & 0xF1) == 0xF1) { uint8_t checksum = 0; for (unsigned i = 2; i < 0x10; i++) { - checksum += gb->sgb_command[i]; + checksum += gb->sgb->command[i]; } - if (checksum != gb->sgb_command[1]) { + if (checksum != gb->sgb->command[1]) { GB_log(gb, "Failed checksum for SGB header command, disabling SGB features\n"); - gb->sgb_disable_commands = true; + gb->sgb->disable_commands = true; return; } - if (gb->sgb_command[0] == 0xf9) { - if (gb->sgb_command[0xc] != 3) { // SGB Flag + if (gb->sgb->command[0] == 0xf9) { + if (gb->sgb->command[0xc] != 3) { // SGB Flag GB_log(gb, "SGB flag is not 0x03, disabling SGB features\n"); - gb->sgb_disable_commands = true; + gb->sgb->disable_commands = true; } } - else if (gb->sgb_command[0] == 0xfb) { - if (gb->sgb_command[0x3] != 0x33) { // Old licensee code + else if (gb->sgb->command[0] == 0xfb) { + if (gb->sgb->command[0x3] != 0x33) { // Old licensee code GB_log(gb, "Old licensee code is not 0x33, disabling SGB features\n"); - gb->sgb_disable_commands = true; + gb->sgb->disable_commands = true; } } return; } - switch (gb->sgb_command[0] >> 3) { + switch (gb->sgb->command[0] >> 3) { case MLT_REQ: - gb->sgb_player_count = (uint8_t[]){1, 2, 1, 4}[gb->sgb_command[1] & 3]; - gb->sgb_current_player = gb->sgb_player_count - 1; + gb->sgb->player_count = (uint8_t[]){1, 2, 1, 4}[gb->sgb->command[1] & 3]; + gb->sgb->current_player = gb->sgb->player_count - 1; break; default: - GB_log(gb, "Unimplemented SGB command %x: ", gb->sgb_command[0] >> 3); - for (unsigned i = 0; i < gb->sgb_command_write_index / 8; i++) { - GB_log(gb, "%02x ", gb->sgb_command[i]); + GB_log(gb, "Unimplemented SGB command %x: ", gb->sgb->command[0] >> 3); + for (unsigned i = 0; i < gb->sgb->command_write_index / 8; i++) { + GB_log(gb, "%02x ", gb->sgb->command[i]); } GB_log(gb, "\n"); ; @@ -60,66 +60,66 @@ static void command_ready(GB_gameboy_t *gb) void GB_sgb_write(GB_gameboy_t *gb, uint8_t value) { if (!GB_is_sgb(gb)) return; - if (gb->sgb_disable_commands) return; - if (gb->sgb_command_write_index >= sizeof(gb->sgb_command) * 8) return; + if (gb->sgb->disable_commands) return; + if (gb->sgb->command_write_index >= sizeof(gb->sgb->command) * 8) return; - uint16_t command_size = (gb->sgb_command[0] & 7 ?: 1) * SGB_PACKET_SIZE * 8; - if ((gb->sgb_command[0] & 0xF1) == 0xF1) { + uint16_t command_size = (gb->sgb->command[0] & 7 ?: 1) * SGB_PACKET_SIZE * 8; + if ((gb->sgb->command[0] & 0xF1) == 0xF1) { command_size = SGB_PACKET_SIZE * 8; } switch ((value >> 4) & 3) { case 3: - gb->sgb_ready_for_pulse = true; + gb->sgb->ready_for_pulse = true; break; case 2: // Zero - if (!gb->sgb_ready_for_pulse || !gb->sgb_ready_for_write) return; - if (gb->sgb_ready_for_stop) { - if (gb->sgb_command_write_index == command_size) { + if (!gb->sgb->ready_for_pulse || !gb->sgb->ready_for_write) return; + if (gb->sgb->ready_for_stop) { + if (gb->sgb->command_write_index == command_size) { command_ready(gb); - gb->sgb_command_write_index = 0; - memset(gb->sgb_command, 0, sizeof(gb->sgb_command)); + gb->sgb->command_write_index = 0; + memset(gb->sgb->command, 0, sizeof(gb->sgb->command)); } - gb->sgb_ready_for_pulse = false; - gb->sgb_ready_for_write = false; - gb->sgb_ready_for_stop = false; + gb->sgb->ready_for_pulse = false; + gb->sgb->ready_for_write = false; + gb->sgb->ready_for_stop = false; } else { - gb->sgb_command_write_index++; - gb->sgb_ready_for_pulse = false; - if (((gb->sgb_command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) { - gb->sgb_ready_for_stop = true; + gb->sgb->command_write_index++; + gb->sgb->ready_for_pulse = false; + if (((gb->sgb->command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) { + gb->sgb->ready_for_stop = true; } } break; case 1: // One - if (!gb->sgb_ready_for_pulse || !gb->sgb_ready_for_write) return; - if (gb->sgb_ready_for_stop) { + if (!gb->sgb->ready_for_pulse || !gb->sgb->ready_for_write) return; + if (gb->sgb->ready_for_stop) { GB_log(gb, "Corrupt SGB command.\n"); - gb->sgb_ready_for_pulse = false; - gb->sgb_ready_for_write = false; - gb->sgb_command_write_index = 0; - memset(gb->sgb_command, 0, sizeof(gb->sgb_command)); + gb->sgb->ready_for_pulse = false; + gb->sgb->ready_for_write = false; + gb->sgb->command_write_index = 0; + memset(gb->sgb->command, 0, sizeof(gb->sgb->command)); } else { - gb->sgb_command[gb->sgb_command_write_index / 8] |= 1 << (gb->sgb_command_write_index & 7); - gb->sgb_command_write_index++; - gb->sgb_ready_for_pulse = false; - if (((gb->sgb_command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) { - gb->sgb_ready_for_stop = true; + gb->sgb->command[gb->sgb->command_write_index / 8] |= 1 << (gb->sgb->command_write_index & 7); + gb->sgb->command_write_index++; + gb->sgb->ready_for_pulse = false; + if (((gb->sgb->command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) { + gb->sgb->ready_for_stop = true; } } break; case 0: - if (!gb->sgb_ready_for_pulse) return; - gb->sgb_ready_for_pulse = false; - gb->sgb_ready_for_write = true; - gb->sgb_ready_for_pulse = false; - if (gb->sgb_player_count > 1 && (value & 0x30) != (gb->io_registers[GB_IO_JOYP] & 0x30)) { - gb->sgb_current_player++; - gb->sgb_current_player &= gb->sgb_player_count - 1; + if (!gb->sgb->ready_for_pulse) return; + gb->sgb->ready_for_pulse = false; + gb->sgb->ready_for_write = true; + gb->sgb->ready_for_pulse = false; + if (gb->sgb->player_count > 1 && (value & 0x30) != (gb->io_registers[GB_IO_JOYP] & 0x30)) { + gb->sgb->current_player++; + gb->sgb->current_player &= gb->sgb->player_count - 1; } break; @@ -145,7 +145,7 @@ void GB_sgb_render(GB_gameboy_t *gb) } uint32_t *output = &gb->screen[48 + 39 * 256]; - uint8_t *input = gb->sgb_screen_buffer; + uint8_t *input = gb->sgb->screen_buffer; for (unsigned y = 0; y < 144; y++) { for (unsigned x = 0; x < 160; x++) { *(output++) = colors[*(input++) & 3]; diff --git a/Core/sgb.h b/Core/sgb.h index c4541fa..5074737 100644 --- a/Core/sgb.h +++ b/Core/sgb.h @@ -1,10 +1,27 @@ #ifndef sgb_h #define sgb_h -#include "gb.h" +#include "gb_struct_def.h" +#include +#include #ifdef GB_INTERNAL +typedef struct { + uint8_t command[16 * 7]; + uint16_t command_write_index; + bool ready_for_pulse; + bool ready_for_write; + bool ready_for_stop; + bool disable_commands; + + /* Screen buffer */ + uint8_t screen_buffer[160 * 144]; + /* Multiplayer Input */ + uint8_t player_count, current_player; +} GB_sgb_t; + void GB_sgb_write(GB_gameboy_t *gb, uint8_t value); void GB_sgb_render(GB_gameboy_t *gb); + #endif #endif