Modified saved data to be more future-compatible.
This commit is contained in:
parent
36d46567ba
commit
b7e999b242
136
Core/gb.c
136
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;
|
||||
}
|
||||
|
212
Core/gb.h
212
Core/gb.h
@ -5,8 +5,10 @@
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#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;
|
||||
);
|
||||
|
||||
/* Memory */
|
||||
unsigned char *rom;
|
||||
size_t rom_size;
|
||||
unsigned short mbc_rom_bank;
|
||||
/* 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;
|
||||
);
|
||||
|
||||
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_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
|
||||
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_vblank_callback_t vblank_callback;
|
||||
|
||||
GB_sample_t *audio_buffer;
|
||||
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;
|
||||
/* 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;
|
||||
|
||||
/* 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;
|
||||
|
||||
|
12
Core/save_struct.h
Normal file
12
Core/save_struct.h
Normal 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 */
|
Loading…
Reference in New Issue
Block a user