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;
|
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
214
Core/gb.h
@ -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
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