diff --git a/Core/gb.c b/Core/gb.c index 89375461..beb34c07 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -208,113 +208,134 @@ int gb_load_rom(GB_gameboy_t *gb, const char *path) return 0; } +static bool dump_section(FILE *f, const void *src, uint32_t size) +{ + if (fwrite(&size, 1, sizeof(size), f) != sizeof(size)) { + return false; + } + + if (fwrite(src, 1, size, f) != size) { + return false; + } + + return true; +} + +#define DUMP_SECTION(gb, f, section) dump_section(f, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section)) + /* Todo: we need a sane and protable save state format. */ int gb_save_state(GB_gameboy_t *gb, const char *path) { - GB_gameboy_t save; - memcpy(&save, gb, offsetof(GB_gameboy_t, first_unsaved_data)); - save.cartridge_type = NULL; // Kept from load_rom - save.rom = NULL; // Kept from load_rom - save.rom_size = 0; // Kept from load_rom - save.mbc_ram = NULL; - save.ram = NULL; - save.vram = NULL; - save.screen = NULL; // Kept from user - save.audio_buffer = NULL; // Kept from user - save.buffer_size = 0; // Kept from user - save.sample_rate = 0; // Kept from user - save.audio_position = 0; // Kept from previous state - save.vblank_callback = NULL; - save.user_data = NULL; - memset(save.keys, 0, sizeof(save.keys)); // Kept from user - FILE *f = fopen(path, "w"); if (!f) { return errno; } - if (fwrite(&save, 1, offsetof(GB_gameboy_t, first_unsaved_data), f) != offsetof(GB_gameboy_t, first_unsaved_data)) { - fclose(f); - return EIO; - } + if (fwrite(GB_GET_SECTION(gb, header), 1, GB_SECTION_SIZE(header), f) != GB_SECTION_SIZE(header)) goto error; + if (!DUMP_SECTION(gb, f, core_state)) goto error; + if (!DUMP_SECTION(gb, f, hdma )) goto error; + if (!DUMP_SECTION(gb, f, mbc )) goto error; + if (!DUMP_SECTION(gb, f, hram )) goto error; + if (!DUMP_SECTION(gb, f, timing )) goto error; + if (!DUMP_SECTION(gb, f, apu )) goto error; + if (!DUMP_SECTION(gb, f, rtc )) goto error; + if (!DUMP_SECTION(gb, f, video )) goto error; + if (fwrite(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) { - fclose(f); - return EIO; + goto error; } if (fwrite(gb->ram, 1, gb->ram_size, f) != gb->ram_size) { - fclose(f); - return EIO; + goto error; } if (fwrite(gb->vram, 1, gb->vram_size, f) != gb->vram_size) { - fclose(f); - return EIO; + goto error; } errno = 0; + +error: fclose(f); return errno; } +/* Best-effort read function for maximum future compatibility. */ +static bool read_section(FILE *f, void *dest, uint32_t size) +{ + uint32_t saved_size = 0; + if (fread(&saved_size, 1, sizeof(size), f) != sizeof(size)) { + return false; + } + + if (saved_size <= size) { + if (fread(dest, 1, saved_size, f) != saved_size) { + return false; + } + } + else { + if (fread(dest, 1, size, f) != size) { + return false; + } + fseek(f, saved_size - size, SEEK_CUR); + } + + return true; +} + +#define READ_SECTION(gb, f, section) read_section(f, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section)) + int gb_load_state(GB_gameboy_t *gb, const char *path) { GB_gameboy_t save; + /* Every unread value should be kept the same. */ + memcpy(&save, gb, sizeof(save)); + FILE *f = fopen(path, "r"); if (!f) { return errno; } - if (fread(&save, 1, offsetof(GB_gameboy_t, first_unsaved_data), f) != offsetof(GB_gameboy_t, first_unsaved_data)) { - fclose(f); - return EIO; - } - - save.cartridge_type = gb->cartridge_type; - save.rom = gb->rom; - save.rom_size = gb->rom_size; - save.mbc_ram = gb->mbc_ram; - save.ram = gb->ram; - save.vram = gb->vram; - save.screen = gb->screen; - save.audio_buffer = gb->audio_buffer; - save.buffer_size = gb->buffer_size; - save.sample_rate = gb->sample_rate; - save.audio_position = gb->audio_position; - save.vblank_callback = gb->vblank_callback; - save.user_data = gb->user_data; - memcpy(save.keys, gb->keys, sizeof(save.keys)); + if (fread(GB_GET_SECTION(&save, header), 1, GB_SECTION_SIZE(header), f) != GB_SECTION_SIZE(header)) goto error; + if (!READ_SECTION(&save, f, core_state)) goto error; + if (!READ_SECTION(&save, f, hdma )) goto error; + if (!READ_SECTION(&save, f, mbc )) goto error; + if (!READ_SECTION(&save, f, hram )) goto error; + if (!READ_SECTION(&save, f, timing )) goto error; + if (!READ_SECTION(&save, f, apu )) goto error; + if (!READ_SECTION(&save, f, rtc )) goto error; + if (!READ_SECTION(&save, f, video )) goto error; if (gb->magic != save.magic) { gb_log(gb, "File is not a save state, or is from an incompatible operating system.\n"); - fclose(f); - return -1; + errno = -1; + goto error; } if (gb->version != save.version) { gb_log(gb, "Save state is for a different version of SameBoy.\n"); - fclose(f); - return -1; + errno = -1; + goto error; } if (gb->mbc_ram_size != save.mbc_ram_size) { gb_log(gb, "Save state has non-matching MBC RAM size.\n"); - fclose(f); - return -1; + errno = -1; + goto error; } if (gb->ram_size != save.ram_size) { gb_log(gb, "Save state has non-matching RAM size. Try changing emulated model.\n"); - fclose(f); - return -1; + errno = -1; + goto error; } if (gb->vram_size != save.vram_size) { gb_log(gb, "Save state has non-matching VRAM size. Try changing emulated model.\n"); - fclose(f); - return -1; + errno = -1; + goto error; } if (fread(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) { @@ -332,8 +353,9 @@ int gb_load_state(GB_gameboy_t *gb, const char *path) return EIO; } - memcpy(gb, &save, offsetof(GB_gameboy_t, first_unsaved_data)); + memcpy(gb, &save, sizeof(save)); errno = 0; +error: fclose(f); return errno; } diff --git a/Core/gb.h b/Core/gb.h index a1c9f154..e8d99d40 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -5,8 +5,10 @@ #include #include #include "apu.h" +#include "save_struct.h" -#define GB_STRUCT_VERSION 7 + +#define GB_STRUCT_VERSION 8 enum { GB_REGISTER_AF, @@ -168,120 +170,152 @@ typedef struct { bool has_rumble; } GB_cartridge_t; -typedef struct GB_gameboy_s{ - uintptr_t magic; // States are currently platform dependent - int version; // and version dependent - /* Registers */ - unsigned short pc; - unsigned short registers[GB_REGISTERS_16_BIT]; - bool ime; - unsigned char interrupt_enable; +/* When state saving, each section is dumped independently of other sections. + This allows adding data to the end of the section without worrying about future compatibility. + Some other changes might be "safe" as well. */ - /* CPU and General Hardware Flags*/ - bool cgb_mode; - bool is_cgb; - bool cgb_double_speed; - bool halted; - bool stopped; +typedef struct GB_gameboy_s { + GB_SECTION(header, + uintptr_t magic; // States are currently platform dependent + int version; // and version dependent + ); + + GB_SECTION(core_state, + /* Registers */ + unsigned short pc; + unsigned short registers[GB_REGISTERS_16_BIT]; + bool ime; + unsigned char interrupt_enable; + unsigned char cgb_ram_bank; + + /* CPU and General Hardware Flags*/ + bool cgb_mode; + bool is_cgb; + bool cgb_double_speed; + bool halted; + bool stopped; + bool bios_finished; + ); /* HDMA */ - bool hdma_on; - bool hdma_on_hblank; - unsigned char hdma_steps_left; - unsigned short hdma_cycles; - unsigned short hdma_current_src, hdma_current_dest; + GB_SECTION(hdma, + bool hdma_on; + bool hdma_on_hblank; + unsigned char hdma_steps_left; + unsigned short hdma_cycles; + unsigned short hdma_current_src, hdma_current_dest; + ); + + /* MBC */ + GB_SECTION(mbc, + unsigned short mbc_rom_bank; + unsigned char mbc_ram_bank; + size_t mbc_ram_size; + bool mbc_ram_enable; + bool mbc_ram_banking; + ); - /* Memory */ - unsigned char *rom; - size_t rom_size; - unsigned short mbc_rom_bank; - const GB_cartridge_t *cartridge_type; - unsigned char *mbc_ram; - unsigned char mbc_ram_bank; - size_t mbc_ram_size; - bool mbc_ram_enable; - bool mbc_ram_banking; - - unsigned char *ram; - unsigned long ram_size; // Different between CGB and DMG - unsigned char cgb_ram_bank; - - unsigned char hram[0xFFFF - 0xFF80]; - unsigned char io_registers[0x80]; - - /* Video Display */ - unsigned char *vram; - unsigned long vram_size; // Different between CGB and DMG - unsigned char cgb_vram_bank; - unsigned char oam[0xA0]; - unsigned char background_palletes_data[0x40]; - unsigned char sprite_palletes_data[0x40]; - uint32_t background_palletes_rgb[0x20]; - uint32_t sprite_palletes_rgb[0x20]; - bool ly144_bug_oam; - bool ly144_bug_hblank; - signed short previous_lcdc_x; - signed short line_x_bias; - bool effective_window_enabled; - unsigned char effective_window_y; - bool stat_interrupt_line; - - unsigned char bios[0x900]; - bool bios_finished; + /* HRAM and HW Registers */ + GB_SECTION(hram, + unsigned char hram[0xFFFF - 0xFF80]; + unsigned char io_registers[0x80]; + ); /* Timing */ - signed long last_vblank; - unsigned long display_cycles; - unsigned long div_cycles; - unsigned long tima_cycles; - unsigned long dma_cycles; - double apu_cycles; + GB_SECTION(timing, + signed long last_vblank; + unsigned long display_cycles; + unsigned long div_cycles; + unsigned long tima_cycles; + unsigned long dma_cycles; + double apu_cycles; + ); /* APU */ - GB_apu_t apu; + GB_SECTION(apu, + GB_apu_t apu; + ); + + /* RTC */ + GB_SECTION(rtc, + union { + struct { + unsigned char rtc_seconds; + unsigned char rtc_minutes; + unsigned char rtc_hours; + unsigned char rtc_days; + unsigned char rtc_high; + }; + unsigned char rtc_data[5]; + }; + time_t last_rtc_second; + ); + + /* Video Display */ + GB_SECTION(video, + unsigned long vram_size; // Different between CGB and DMG + unsigned char cgb_vram_bank; + unsigned char oam[0xA0]; + unsigned char background_palletes_data[0x40]; + unsigned char sprite_palletes_data[0x40]; + uint32_t background_palletes_rgb[0x20]; + uint32_t sprite_palletes_rgb[0x20]; + bool ly144_bug_oam; + bool ly144_bug_hblank; + signed short previous_lcdc_x; + unsigned char padding; + bool effective_window_enabled; + unsigned char effective_window_y; + bool stat_interrupt_line; + signed char line_x_bias; + ); + + /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ + + /* ROM */ + unsigned char *rom; + size_t rom_size; + const GB_cartridge_t *cartridge_type; + + /* Various RAMs */ + unsigned char *ram; + unsigned char *vram; + unsigned char *mbc_ram; + + /* I/O */ + uint32_t *screen; GB_sample_t *audio_buffer; + bool keys[8]; + + /* Audio Specific */ unsigned int buffer_size; unsigned int sample_rate; unsigned int audio_position; - volatile bool audio_copy_in_progress; bool audio_stream_started; // detects first copy request to minimize lag - - /* I/O */ - uint32_t *screen; - GB_vblank_callback_t vblank_callback; + volatile bool audio_copy_in_progress; - bool keys[8]; - - /* RTC */ - union { - struct { - unsigned char rtc_seconds; - unsigned char rtc_minutes; - unsigned char rtc_hours; - unsigned char rtc_days; - unsigned char rtc_high; - }; - unsigned char rtc_data[5]; - }; - time_t last_rtc_second; - - /* Unsaved User */ - struct {} first_unsaved_data; - bool turbo; - bool debug_stopped; + /* Callbacks */ + void *user_data; GB_log_callback_t log_callback; GB_input_callback_t input_callback; GB_rgb_encode_callback_t rgb_encode_callback; - void *user_data; + GB_vblank_callback_t vblank_callback; + + /* Debugger */ int debug_call_depth; bool debug_fin_command, debug_next_command; unsigned short n_breakpoints; unsigned short *breakpoints; - bool stack_leak_detection; unsigned short sp_for_call_depth[0x200]; /* Should be much more than enough */ unsigned short addr_for_call_depth[0x200]; + bool debug_stopped; + + /* Misc */ + bool turbo; + unsigned long ram_size; // Different between CGB and DMG + unsigned char bios[0x900]; } GB_gameboy_t; diff --git a/Core/save_struct.h b/Core/save_struct.h new file mode 100644 index 00000000..d88922c4 --- /dev/null +++ b/Core/save_struct.h @@ -0,0 +1,12 @@ +/* Macros to make the GB_gameboy_t struct more future compatible when state saving */ +#ifndef save_struct_h +#define save_struct_h + +#define GB_PADDING(type, old_usage) type old_usage##__do_not_use + +#define GB_SECTION(name, ...) __attribute__ ((aligned (sizeof(void*)))) struct {} name##_section_start; __VA_ARGS__; struct {} name##_section_end +#define GB_SECTION_OFFSET(name) (offsetof(GB_gameboy_t, name##_section_start)) +#define GB_SECTION_SIZE(name) (offsetof(GB_gameboy_t, name##_section_end) - offsetof(GB_gameboy_t, name##_section_start)) +#define GB_GET_SECTION(gb, name) ((void*)&((gb)->name##_section_start)) + +#endif /* save_struct_h */