SGB save states

This commit is contained in:
Lior Halphon 2018-11-16 01:53:01 +02:00
parent 634a54c046
commit 2f2b792edf
7 changed files with 122 additions and 94 deletions

View File

@ -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];

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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];

View File

@ -1,10 +1,27 @@
#ifndef sgb_h
#define sgb_h
#include "gb.h"
#include "gb_struct_def.h"
#include <stdint.h>
#include <stdbool.h>
#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