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

214
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,120 +170,152 @@ 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.
uintptr_t magic; // States are currently platform dependent This allows adding data to the end of the section without worrying about future compatibility.
int version; // and version dependent Some other changes might be "safe" as well. */
/* Registers */
unsigned short pc;
unsigned short registers[GB_REGISTERS_16_BIT];
bool ime;
unsigned char interrupt_enable;
/* CPU and General Hardware Flags*/ typedef struct GB_gameboy_s {
bool cgb_mode; GB_SECTION(header,
bool is_cgb; uintptr_t magic; // States are currently platform dependent
bool cgb_double_speed; int version; // and version dependent
bool halted; );
bool stopped;
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 */ /* HDMA */
bool hdma_on; GB_SECTION(hdma,
bool hdma_on_hblank; bool hdma_on;
unsigned char hdma_steps_left; bool hdma_on_hblank;
unsigned short hdma_cycles; unsigned char hdma_steps_left;
unsigned short hdma_current_src, hdma_current_dest; 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; /* HRAM and HW Registers */
unsigned char *mbc_ram; GB_SECTION(hram,
unsigned char mbc_ram_bank; unsigned char hram[0xFFFF - 0xFF80];
size_t mbc_ram_size; unsigned char io_registers[0x80];
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;
/* Timing */ /* Timing */
signed long last_vblank; GB_SECTION(timing,
unsigned long display_cycles; signed long last_vblank;
unsigned long div_cycles; unsigned long display_cycles;
unsigned long tima_cycles; unsigned long div_cycles;
unsigned long dma_cycles; unsigned long tima_cycles;
double apu_cycles; unsigned long dma_cycles;
double apu_cycles;
);
/* APU */ /* 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; GB_sample_t *audio_buffer;
bool keys[8];
/* Audio Specific */
unsigned int buffer_size; unsigned int buffer_size;
unsigned int sample_rate; unsigned int sample_rate;
unsigned int audio_position; unsigned int audio_position;
volatile bool audio_copy_in_progress;
bool audio_stream_started; // detects first copy request to minimize lag bool audio_stream_started; // detects first copy request to minimize lag
volatile bool audio_copy_in_progress;
/* I/O */
uint32_t *screen;
GB_vblank_callback_t vblank_callback;
bool keys[8]; /* Callbacks */
void *user_data;
/* 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;
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 */