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)) { 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) */ /* 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; uint8_t color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped ? 0 : 0xFF;
for (unsigned i = 0; i < WIDTH * LINES; i++) { for (unsigned i = 0; i < WIDTH * LINES; i++) {
gb ->sgb_screen_buffer[i] = color; gb->sgb->screen_buffer[i] = color;
} }
} }
else { else {
@ -386,8 +386,8 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
if (!gb->cgb_mode) { if (!gb->cgb_mode) {
pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3); pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3);
} }
if (gb->sgb_screen_buffer) { if (gb->sgb) {
gb->sgb_screen_buffer[gb->position_in_line + gb->current_line * WIDTH] = pixel; gb->sgb->screen_buffer[gb->position_in_line + gb->current_line * WIDTH] = pixel;
} }
else { else {
gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->background_palettes_rgb[fifo_item->palette * 4 + pixel]; 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 */ /* Todo: Verify access timings */
pixel = ((gb->io_registers[oam_fifo_item->palette? GB_IO_OBP1 : GB_IO_OBP0] >> (pixel << 1)) & 3); pixel = ((gb->io_registers[oam_fifo_item->palette? GB_IO_OBP1 : GB_IO_OBP0] >> (pixel << 1)) & 3);
} }
if (gb->sgb_screen_buffer) { if (gb->sgb) {
gb->sgb_screen_buffer[gb->position_in_line + gb->current_line * WIDTH] =pixel; gb->sgb->screen_buffer[gb->position_in_line + gb->current_line * WIDTH] =pixel;
} }
else { else {
gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->sprite_palettes_rgb[oam_fifo_item->palette * 4 + pixel]; 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) { if (gb->breakpoints) {
free(gb->breakpoints); free(gb->breakpoints);
} }
if (gb->sgb_screen_buffer) { if (gb->sgb) {
free(gb->sgb_screen_buffer); free(gb->sgb);
} }
#ifndef DISABLE_DEBUGGER #ifndef DISABLE_DEBUGGER
GB_debugger_clear_symbols(gb); GB_debugger_clear_symbols(gb);
@ -643,17 +643,18 @@ void GB_reset(GB_gameboy_t *gb)
gb->accessed_oam_row = -1; gb->accessed_oam_row = -1;
gb->sgb_player_count = 1;
if (GB_is_sgb(gb)) { if (GB_is_sgb(gb)) {
if (!gb->sgb_screen_buffer) { if (!gb->sgb) {
gb->sgb_screen_buffer = malloc(160 * 144); gb->sgb = malloc(sizeof(*gb->sgb));
} }
gb->sgb->player_count = 1;
} }
else { else {
if (gb->sgb_screen_buffer) { if (gb->sgb) {
free(gb->sgb_screen_buffer); free(gb->sgb);
gb->sgb_screen_buffer = NULL; gb->sgb = NULL;
} }
} }

View File

@ -52,7 +52,6 @@ typedef union {
typedef enum { typedef enum {
// GB_MODEL_DMG_0 = 0x000, // GB_MODEL_DMG_0 = 0x000,
// GB_MODEL_DMG_A = 0x001, // GB_MODEL_DMG_A = 0x001,
GB_MODEL_DMG_B = 0x002, GB_MODEL_DMG_B = 0x002,
@ -466,21 +465,6 @@ struct GB_gameboy_internal_s {
uint8_t mode_for_interrupt; uint8_t mode_for_interrupt;
bool lyc_interrupt_line; 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 */ /* 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 */ /* This data is reserved on reset and must come last in the struct */
@ -577,6 +561,9 @@ struct GB_gameboy_internal_s {
unsigned pos; unsigned pos;
} *rewind_sequences; // lasts about 4 seconds } *rewind_sequences; // lasts about 4 seconds
size_t rewind_pos; size_t rewind_pos;
/* SGB - saved and allocated optionally */
GB_sgb_t *sgb;
/* Misc */ /* Misc */
bool turbo; 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; previous_state = gb->io_registers[GB_IO_JOYP] & 0xF;
key_selection = (gb->io_registers[GB_IO_JOYP] >> 4) & 3; key_selection = (gb->io_registers[GB_IO_JOYP] >> 4) & 3;
gb->io_registers[GB_IO_JOYP] &= 0xF0; gb->io_registers[GB_IO_JOYP] &= 0xF0;
uint8_t current_player = gb->sgb? gb->sgb->current_player : 0;
switch (key_selection) { switch (key_selection) {
case 3: case 3:
if (gb->sgb_player_count > 1) { if (gb->sgb && gb->sgb->player_count > 1) {
gb->io_registers[GB_IO_JOYP] |= 0xF - gb->sgb_current_player; gb->io_registers[GB_IO_JOYP] |= 0xF - current_player;
} }
else { else {
/* Nothing is wired, all up */ /* Nothing is wired, all up */
@ -25,7 +26,7 @@ void GB_update_joyp(GB_gameboy_t *gb)
case 2: case 2:
/* Direction keys */ /* Direction keys */
for (uint8_t i = 0; i < 4; i++) { 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. */ /* 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)) { if (!(gb->io_registers[GB_IO_JOYP] & 1)) {
@ -39,13 +40,13 @@ void GB_update_joyp(GB_gameboy_t *gb)
case 1: case 1:
/* Other keys */ /* Other keys */
for (uint8_t i = 0; i < 4; i++) { 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; break;
case 0: case 0:
for (uint8_t i = 0; i < 4; i++) { 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; 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, rtc )) goto error;
if (!DUMP_SECTION(gb, f, video )) 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) { if (fwrite(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) {
goto error; 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(apu ) + sizeof(uint32_t)
+ GB_SECTION_SIZE(rtc ) + sizeof(uint32_t) + GB_SECTION_SIZE(rtc ) + sizeof(uint32_t)
+ GB_SECTION_SIZE(video ) + 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->mbc_ram_size
+ gb->ram_size + gb->ram_size
+ gb->vram_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, rtc );
DUMP_SECTION(gb, buffer, video ); 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->mbc_ram, gb->mbc_ram_size, &buffer);
buffer_write(gb->ram, gb->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) static bool verify_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t *save)
{ {
if (gb->magic != save->magic) { 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; return false;
} }
if (gb->version != save->version) { 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; return false;
} }
if (gb->mbc_ram_size < save->mbc_ram_size) { 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; return false;
} }
if (gb->ram_size != save->ram_size) { 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; return false;
} }
if (gb->vram_size != save->vram_size) { 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; return false;
} }
@ -190,6 +204,10 @@ int GB_load_state(GB_gameboy_t *gb, const char *path)
goto error; 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); 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) { if (fread(gb->mbc_ram, 1, save.mbc_ram_size, f) != save.mbc_ram_size) {
fclose(f); 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; 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); 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) { if (buffer_read(gb->mbc_ram, save.mbc_ram_size, &buffer, &length) != save.mbc_ram_size) {
return -1; return -1;

View File

@ -1,4 +1,4 @@
#include "sgb.h" #include "gb.h"
enum { enum {
MLT_REQ = 0x11, 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. */ 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; uint8_t checksum = 0;
for (unsigned i = 2; i < 0x10; i++) { 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_log(gb, "Failed checksum for SGB header command, disabling SGB features\n");
gb->sgb_disable_commands = true; gb->sgb->disable_commands = true;
return; return;
} }
if (gb->sgb_command[0] == 0xf9) { if (gb->sgb->command[0] == 0xf9) {
if (gb->sgb_command[0xc] != 3) { // SGB Flag if (gb->sgb->command[0xc] != 3) { // SGB Flag
GB_log(gb, "SGB flag is not 0x03, disabling SGB features\n"); 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) { else if (gb->sgb->command[0] == 0xfb) {
if (gb->sgb_command[0x3] != 0x33) { // Old licensee code if (gb->sgb->command[0x3] != 0x33) { // Old licensee code
GB_log(gb, "Old licensee code is not 0x33, disabling SGB features\n"); GB_log(gb, "Old licensee code is not 0x33, disabling SGB features\n");
gb->sgb_disable_commands = true; gb->sgb->disable_commands = true;
} }
} }
return; return;
} }
switch (gb->sgb_command[0] >> 3) { switch (gb->sgb->command[0] >> 3) {
case MLT_REQ: case MLT_REQ:
gb->sgb_player_count = (uint8_t[]){1, 2, 1, 4}[gb->sgb_command[1] & 3]; 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->current_player = gb->sgb->player_count - 1;
break; break;
default: default:
GB_log(gb, "Unimplemented SGB command %x: ", gb->sgb_command[0] >> 3); GB_log(gb, "Unimplemented SGB command %x: ", gb->sgb->command[0] >> 3);
for (unsigned i = 0; i < gb->sgb_command_write_index / 8; i++) { for (unsigned i = 0; i < gb->sgb->command_write_index / 8; i++) {
GB_log(gb, "%02x ", gb->sgb_command[i]); GB_log(gb, "%02x ", gb->sgb->command[i]);
} }
GB_log(gb, "\n"); 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) void GB_sgb_write(GB_gameboy_t *gb, uint8_t value)
{ {
if (!GB_is_sgb(gb)) return; if (!GB_is_sgb(gb)) return;
if (gb->sgb_disable_commands) return; if (gb->sgb->disable_commands) return;
if (gb->sgb_command_write_index >= sizeof(gb->sgb_command) * 8) 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; uint16_t command_size = (gb->sgb->command[0] & 7 ?: 1) * SGB_PACKET_SIZE * 8;
if ((gb->sgb_command[0] & 0xF1) == 0xF1) { if ((gb->sgb->command[0] & 0xF1) == 0xF1) {
command_size = SGB_PACKET_SIZE * 8; command_size = SGB_PACKET_SIZE * 8;
} }
switch ((value >> 4) & 3) { switch ((value >> 4) & 3) {
case 3: case 3:
gb->sgb_ready_for_pulse = true; gb->sgb->ready_for_pulse = true;
break; break;
case 2: // Zero case 2: // Zero
if (!gb->sgb_ready_for_pulse || !gb->sgb_ready_for_write) return; if (!gb->sgb->ready_for_pulse || !gb->sgb->ready_for_write) return;
if (gb->sgb_ready_for_stop) { if (gb->sgb->ready_for_stop) {
if (gb->sgb_command_write_index == command_size) { if (gb->sgb->command_write_index == command_size) {
command_ready(gb); command_ready(gb);
gb->sgb_command_write_index = 0; gb->sgb->command_write_index = 0;
memset(gb->sgb_command, 0, sizeof(gb->sgb_command)); memset(gb->sgb->command, 0, sizeof(gb->sgb->command));
} }
gb->sgb_ready_for_pulse = false; gb->sgb->ready_for_pulse = false;
gb->sgb_ready_for_write = false; gb->sgb->ready_for_write = false;
gb->sgb_ready_for_stop = false; gb->sgb->ready_for_stop = false;
} }
else { else {
gb->sgb_command_write_index++; gb->sgb->command_write_index++;
gb->sgb_ready_for_pulse = false; gb->sgb->ready_for_pulse = false;
if (((gb->sgb_command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) { if (((gb->sgb->command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) {
gb->sgb_ready_for_stop = true; gb->sgb->ready_for_stop = true;
} }
} }
break; break;
case 1: // One case 1: // One
if (!gb->sgb_ready_for_pulse || !gb->sgb_ready_for_write) return; if (!gb->sgb->ready_for_pulse || !gb->sgb->ready_for_write) return;
if (gb->sgb_ready_for_stop) { if (gb->sgb->ready_for_stop) {
GB_log(gb, "Corrupt SGB command.\n"); GB_log(gb, "Corrupt SGB command.\n");
gb->sgb_ready_for_pulse = false; gb->sgb->ready_for_pulse = false;
gb->sgb_ready_for_write = false; gb->sgb->ready_for_write = false;
gb->sgb_command_write_index = 0; gb->sgb->command_write_index = 0;
memset(gb->sgb_command, 0, sizeof(gb->sgb_command)); memset(gb->sgb->command, 0, sizeof(gb->sgb->command));
} }
else { else {
gb->sgb_command[gb->sgb_command_write_index / 8] |= 1 << (gb->sgb_command_write_index & 7); gb->sgb->command[gb->sgb->command_write_index / 8] |= 1 << (gb->sgb->command_write_index & 7);
gb->sgb_command_write_index++; gb->sgb->command_write_index++;
gb->sgb_ready_for_pulse = false; gb->sgb->ready_for_pulse = false;
if (((gb->sgb_command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) { if (((gb->sgb->command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) {
gb->sgb_ready_for_stop = true; gb->sgb->ready_for_stop = true;
} }
} }
break; break;
case 0: case 0:
if (!gb->sgb_ready_for_pulse) return; if (!gb->sgb->ready_for_pulse) return;
gb->sgb_ready_for_pulse = false; gb->sgb->ready_for_pulse = false;
gb->sgb_ready_for_write = true; gb->sgb->ready_for_write = true;
gb->sgb_ready_for_pulse = false; gb->sgb->ready_for_pulse = false;
if (gb->sgb_player_count > 1 && (value & 0x30) != (gb->io_registers[GB_IO_JOYP] & 0x30)) { 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_current_player &= gb->sgb_player_count - 1; gb->sgb->current_player &= gb->sgb->player_count - 1;
} }
break; break;
@ -145,7 +145,7 @@ void GB_sgb_render(GB_gameboy_t *gb)
} }
uint32_t *output = &gb->screen[48 + 39 * 256]; 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 y = 0; y < 144; y++) {
for (unsigned x = 0; x < 160; x++) { for (unsigned x = 0; x < 160; x++) {
*(output++) = colors[*(input++) & 3]; *(output++) = colors[*(input++) & 3];

View File

@ -1,10 +1,27 @@
#ifndef sgb_h #ifndef sgb_h
#define sgb_h #define sgb_h
#include "gb.h" #include "gb_struct_def.h"
#include <stdint.h>
#include <stdbool.h>
#ifdef GB_INTERNAL #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_write(GB_gameboy_t *gb, uint8_t value);
void GB_sgb_render(GB_gameboy_t *gb); void GB_sgb_render(GB_gameboy_t *gb);
#endif #endif
#endif #endif