Modified saved data to be more future-compatible.

This commit is contained in:
Lior Halphon 2016-06-11 14:52:09 +03:00
parent 36d46567ba
commit b7e999b242
3 changed files with 215 additions and 147 deletions

136
Core/gb.c
View File

@ -208,113 +208,134 @@ int gb_load_rom(GB_gameboy_t *gb, const char *path)
return 0; 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. */ /* Todo: we need a sane and protable save state format. */
int gb_save_state(GB_gameboy_t *gb, const char *path) 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"); FILE *f = fopen(path, "w");
if (!f) { if (!f) {
return errno; return errno;
} }
if (fwrite(&save, 1, offsetof(GB_gameboy_t, first_unsaved_data), f) != offsetof(GB_gameboy_t, first_unsaved_data)) { if (fwrite(GB_GET_SECTION(gb, header), 1, GB_SECTION_SIZE(header), f) != GB_SECTION_SIZE(header)) goto error;
fclose(f); if (!DUMP_SECTION(gb, f, core_state)) goto error;
return EIO; 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) { if (fwrite(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) {
fclose(f); goto error;
return EIO;
} }
if (fwrite(gb->ram, 1, gb->ram_size, f) != gb->ram_size) { if (fwrite(gb->ram, 1, gb->ram_size, f) != gb->ram_size) {
fclose(f); goto error;
return EIO;
} }
if (fwrite(gb->vram, 1, gb->vram_size, f) != gb->vram_size) { if (fwrite(gb->vram, 1, gb->vram_size, f) != gb->vram_size) {
fclose(f); goto error;
return EIO;
} }
errno = 0; errno = 0;
error:
fclose(f); fclose(f);
return errno; 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) int gb_load_state(GB_gameboy_t *gb, const char *path)
{ {
GB_gameboy_t save; GB_gameboy_t save;
/* Every unread value should be kept the same. */
memcpy(&save, gb, sizeof(save));
FILE *f = fopen(path, "r"); FILE *f = fopen(path, "r");
if (!f) { if (!f) {
return errno; return errno;
} }
if (fread(&save, 1, offsetof(GB_gameboy_t, first_unsaved_data), f) != offsetof(GB_gameboy_t, first_unsaved_data)) { if (fread(GB_GET_SECTION(&save, header), 1, GB_SECTION_SIZE(header), f) != GB_SECTION_SIZE(header)) goto error;
fclose(f); if (!READ_SECTION(&save, f, core_state)) goto error;
return EIO; if (!READ_SECTION(&save, f, hdma )) goto error;
} if (!READ_SECTION(&save, f, mbc )) goto error;
if (!READ_SECTION(&save, f, hram )) goto error;
save.cartridge_type = gb->cartridge_type; if (!READ_SECTION(&save, f, timing )) goto error;
save.rom = gb->rom; if (!READ_SECTION(&save, f, apu )) goto error;
save.rom_size = gb->rom_size; if (!READ_SECTION(&save, f, rtc )) goto error;
save.mbc_ram = gb->mbc_ram; if (!READ_SECTION(&save, f, video )) goto error;
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 (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, "File is not a save state, or is from an incompatible operating system.\n");
fclose(f); errno = -1;
return -1; goto error;
} }
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, "Save state is for a different version of SameBoy.\n");
fclose(f); errno = -1;
return -1; goto error;
} }
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, "Save state has non-matching MBC RAM size.\n");
fclose(f); errno = -1;
return -1; goto error;
} }
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, "Save state has non-matching RAM size. Try changing emulated model.\n");
fclose(f); errno = -1;
return -1; goto error;
} }
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, "Save state has non-matching VRAM size. Try changing emulated model.\n");
fclose(f); errno = -1;
return -1; goto error;
} }
if (fread(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) { 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; return EIO;
} }
memcpy(gb, &save, offsetof(GB_gameboy_t, first_unsaved_data)); memcpy(gb, &save, sizeof(save));
errno = 0; errno = 0;
error:
fclose(f); fclose(f);
return errno; return errno;
} }

132
Core/gb.h
View File

@ -5,8 +5,10 @@
#include <stdio.h> #include <stdio.h>
#include <time.h> #include <time.h>
#include "apu.h" #include "apu.h"
#include "save_struct.h"
#define GB_STRUCT_VERSION 7
#define GB_STRUCT_VERSION 8
enum { enum {
GB_REGISTER_AF, GB_REGISTER_AF,
@ -168,14 +170,23 @@ typedef struct {
bool has_rumble; bool has_rumble;
} GB_cartridge_t; } GB_cartridge_t;
typedef struct GB_gameboy_s{ /* 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. */
typedef struct GB_gameboy_s {
GB_SECTION(header,
uintptr_t magic; // States are currently platform dependent uintptr_t magic; // States are currently platform dependent
int version; // and version dependent int version; // and version dependent
);
GB_SECTION(core_state,
/* Registers */ /* Registers */
unsigned short pc; unsigned short pc;
unsigned short registers[GB_REGISTERS_16_BIT]; unsigned short registers[GB_REGISTERS_16_BIT];
bool ime; bool ime;
unsigned char interrupt_enable; unsigned char interrupt_enable;
unsigned char cgb_ram_bank;
/* CPU and General Hardware Flags*/ /* CPU and General Hardware Flags*/
bool cgb_mode; bool cgb_mode;
@ -183,77 +194,51 @@ typedef struct GB_gameboy_s{
bool cgb_double_speed; bool cgb_double_speed;
bool halted; bool halted;
bool stopped; bool stopped;
bool bios_finished;
);
/* HDMA */ /* HDMA */
GB_SECTION(hdma,
bool hdma_on; bool hdma_on;
bool hdma_on_hblank; bool hdma_on_hblank;
unsigned char hdma_steps_left; unsigned char hdma_steps_left;
unsigned short hdma_cycles; unsigned short hdma_cycles;
unsigned short hdma_current_src, hdma_current_dest; unsigned short hdma_current_src, hdma_current_dest;
);
/* Memory */ /* MBC */
unsigned char *rom; GB_SECTION(mbc,
size_t rom_size;
unsigned short mbc_rom_bank; unsigned short mbc_rom_bank;
const GB_cartridge_t *cartridge_type;
unsigned char *mbc_ram;
unsigned char mbc_ram_bank; unsigned char mbc_ram_bank;
size_t mbc_ram_size; size_t mbc_ram_size;
bool mbc_ram_enable; bool mbc_ram_enable;
bool mbc_ram_banking; bool mbc_ram_banking;
);
unsigned char *ram;
unsigned long ram_size; // Different between CGB and DMG
unsigned char cgb_ram_bank;
/* HRAM and HW Registers */
GB_SECTION(hram,
unsigned char hram[0xFFFF - 0xFF80]; unsigned char hram[0xFFFF - 0xFF80];
unsigned char io_registers[0x80]; 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;
/* Timing */ /* Timing */
GB_SECTION(timing,
signed long last_vblank; signed long last_vblank;
unsigned long display_cycles; unsigned long display_cycles;
unsigned long div_cycles; unsigned long div_cycles;
unsigned long tima_cycles; unsigned long tima_cycles;
unsigned long dma_cycles; unsigned long dma_cycles;
double apu_cycles; double apu_cycles;
);
/* APU */ /* APU */
GB_SECTION(apu,
GB_apu_t apu; GB_apu_t apu;
GB_sample_t *audio_buffer; );
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;
bool keys[8];
/* RTC */ /* RTC */
GB_SECTION(rtc,
union { union {
struct { struct {
unsigned char rtc_seconds; unsigned char rtc_seconds;
@ -265,23 +250,72 @@ typedef struct GB_gameboy_s{
unsigned char rtc_data[5]; unsigned char rtc_data[5];
}; };
time_t last_rtc_second; time_t last_rtc_second;
);
/* Unsaved User */ /* Video Display */
struct {} first_unsaved_data; GB_SECTION(video,
bool turbo; unsigned long vram_size; // Different between CGB and DMG
bool debug_stopped; 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;
bool audio_stream_started; // detects first copy request to minimize lag
volatile bool audio_copy_in_progress;
/* Callbacks */
void *user_data;
GB_log_callback_t log_callback; GB_log_callback_t log_callback;
GB_input_callback_t input_callback; GB_input_callback_t input_callback;
GB_rgb_encode_callback_t rgb_encode_callback; GB_rgb_encode_callback_t rgb_encode_callback;
void *user_data; GB_vblank_callback_t vblank_callback;
/* Debugger */
int debug_call_depth; int debug_call_depth;
bool debug_fin_command, debug_next_command; bool debug_fin_command, debug_next_command;
unsigned short n_breakpoints; unsigned short n_breakpoints;
unsigned short *breakpoints; unsigned short *breakpoints;
bool stack_leak_detection; bool stack_leak_detection;
unsigned short sp_for_call_depth[0x200]; /* Should be much more than enough */ unsigned short sp_for_call_depth[0x200]; /* Should be much more than enough */
unsigned short addr_for_call_depth[0x200]; 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; } GB_gameboy_t;

12
Core/save_struct.h Normal file
View File

@ -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 */