diff --git a/Core/gb.h b/Core/gb.h index 465278a..3e8d915 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -6,6 +6,7 @@ #include #include +#include "model.h" #include "defs.h" #include "save_state.h" @@ -29,14 +30,6 @@ #define GB_STRUCT_VERSION 14 -#define GB_MODEL_FAMILY_MASK 0xF00 -#define GB_MODEL_DMG_FAMILY 0x000 -#define GB_MODEL_MGB_FAMILY 0x100 -#define GB_MODEL_CGB_FAMILY 0x200 -#define GB_MODEL_GBP_BIT 0x20 -#define GB_MODEL_PAL_BIT 0x40 -#define GB_MODEL_NO_SFC_BIT 0x80 - #define GB_REWIND_FRAMES_PER_KEY 255 #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ @@ -96,36 +89,6 @@ typedef struct __attribute__((packed)) { uint8_t alarm_enabled; } GB_huc3_rtc_time_t; -typedef enum { - // GB_MODEL_DMG_0 = 0x000, - // GB_MODEL_DMG_A = 0x001, - GB_MODEL_DMG_B = 0x002, - // GB_MODEL_DMG_C = 0x003, - GB_MODEL_SGB = 0x004, - GB_MODEL_SGB_NTSC = GB_MODEL_SGB, - GB_MODEL_SGB_PAL = GB_MODEL_SGB | GB_MODEL_PAL_BIT, - GB_MODEL_SGB_NTSC_NO_SFC = GB_MODEL_SGB | GB_MODEL_NO_SFC_BIT, - GB_MODEL_SGB_NO_SFC = GB_MODEL_SGB_NTSC_NO_SFC, - GB_MODEL_SGB_PAL_NO_SFC = GB_MODEL_SGB | GB_MODEL_NO_SFC_BIT | GB_MODEL_PAL_BIT, - GB_MODEL_MGB = 0x100, - GB_MODEL_SGB2 = 0x101, - GB_MODEL_SGB2_NO_SFC = GB_MODEL_SGB2 | GB_MODEL_NO_SFC_BIT, - GB_MODEL_CGB_0 = 0x200, - GB_MODEL_CGB_A = 0x201, - GB_MODEL_CGB_B = 0x202, - GB_MODEL_CGB_C = 0x203, - GB_MODEL_CGB_D = 0x204, - GB_MODEL_CGB_E = 0x205, - // GB_MODEL_AGB_0 = 0x206, - GB_MODEL_AGB_A = 0x207, - GB_MODEL_GBP_A = GB_MODEL_AGB_A | GB_MODEL_GBP_BIT, // AGB-A inside a Game Boy Player - GB_MODEL_AGB = GB_MODEL_AGB_A, - GB_MODEL_GBP = GB_MODEL_GBP_A, - //GB_MODEL_AGB_B = 0x208 - //GB_MODEL_AGB_E = 0x209 - //GB_MODEL_GBP_E = GB_MODEL_AGB_E | GB_MODEL_GBP_BIT, // AGB-E inside a Game Boy Player -} GB_model_t; - enum { GB_REGISTER_AF, GB_REGISTER_BC, diff --git a/Core/model.h b/Core/model.h new file mode 100644 index 0000000..81488b9 --- /dev/null +++ b/Core/model.h @@ -0,0 +1,43 @@ +#ifndef model_h +#define model_h + +#define GB_MODEL_FAMILY_MASK 0xF00 +#define GB_MODEL_DMG_FAMILY 0x000 +#define GB_MODEL_MGB_FAMILY 0x100 +#define GB_MODEL_CGB_FAMILY 0x200 +#define GB_MODEL_GBP_BIT 0x20 +#define GB_MODEL_PAL_BIT 0x40 +#define GB_MODEL_NO_SFC_BIT 0x80 + +typedef enum { + // GB_MODEL_DMG_0 = 0x000, + // GB_MODEL_DMG_A = 0x001, + GB_MODEL_DMG_B = 0x002, + // GB_MODEL_DMG_C = 0x003, + GB_MODEL_SGB = 0x004, + GB_MODEL_SGB_NTSC = GB_MODEL_SGB, + GB_MODEL_SGB_PAL = GB_MODEL_SGB | GB_MODEL_PAL_BIT, + GB_MODEL_SGB_NTSC_NO_SFC = GB_MODEL_SGB | GB_MODEL_NO_SFC_BIT, + GB_MODEL_SGB_NO_SFC = GB_MODEL_SGB_NTSC_NO_SFC, + GB_MODEL_SGB_PAL_NO_SFC = GB_MODEL_SGB | GB_MODEL_NO_SFC_BIT | GB_MODEL_PAL_BIT, + GB_MODEL_MGB = 0x100, + GB_MODEL_SGB2 = 0x101, + GB_MODEL_SGB2_NO_SFC = GB_MODEL_SGB2 | GB_MODEL_NO_SFC_BIT, + GB_MODEL_CGB_0 = 0x200, + GB_MODEL_CGB_A = 0x201, + GB_MODEL_CGB_B = 0x202, + GB_MODEL_CGB_C = 0x203, + GB_MODEL_CGB_D = 0x204, + GB_MODEL_CGB_E = 0x205, + // GB_MODEL_AGB_0 = 0x206, + GB_MODEL_AGB_A = 0x207, + GB_MODEL_GBP_A = GB_MODEL_AGB_A | GB_MODEL_GBP_BIT, // AGB-A inside a Game Boy Player + GB_MODEL_AGB = GB_MODEL_AGB_A, + GB_MODEL_GBP = GB_MODEL_GBP_A, + //GB_MODEL_AGB_B = 0x208 + //GB_MODEL_AGB_E = 0x209 + //GB_MODEL_GBP_E = GB_MODEL_AGB_E | GB_MODEL_GBP_BIT, // AGB-E inside a Game Boy Player +} GB_model_t; + + +#endif diff --git a/Core/save_state.c b/Core/save_state.c index a4b316c..ef5d2b5 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -1300,7 +1300,6 @@ static int load_state_internal(GB_gameboy_t *gb, virtual_file_t *file) if (!READ_SECTION(&save, file, apu )) return errno ?: EIO; if (!READ_SECTION(&save, file, rtc )) return errno ?: EIO; if (!READ_SECTION(&save, file, video )) return errno ?: EIO; -#undef READ_SECTION bool attempt_bess = false; @@ -1372,6 +1371,109 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le return load_state_internal(gb, &file); } +static int get_state_model_bess(virtual_file_t *file, GB_model_t *model) +{ + file->seek(file, -sizeof(BESS_footer_t), SEEK_END); + BESS_footer_t footer = {0, }; + file->read(file, &footer, sizeof(footer)); + if (footer.magic != BE32('BESS')) { + return -1; + } + + file->seek(file, LE32(footer.start_offset), SEEK_SET); + while (true) { + BESS_block_t block; + if (file->read(file, &block, sizeof(block)) != sizeof(block)) return errno; + switch (block.magic) { + case BE32('CORE'): { + BESS_CORE_t core = {0,}; + if (LE32(block.size) > sizeof(core) - sizeof(block)) { + if (file->read(file, &core.header + 1, sizeof(core) - sizeof(block)) != sizeof(core) - sizeof(block)) return errno; + file->seek(file, LE32(block.size) - (sizeof(core) - sizeof(block)), SEEK_CUR); + } + else { + if (file->read(file, &core.header + 1, LE32(block.size)) != LE32(block.size)) return errno; + } + + switch (core.full_model) { + case BE32('GDB '): *model = GB_MODEL_DMG_B; return 0; + case BE32('GM '): *model = GB_MODEL_MGB; return 0; + case BE32('SN '): *model = GB_MODEL_SGB_NTSC_NO_SFC; return 0; + case BE32('SP '): *model = GB_MODEL_SGB_PAL; return 0; + case BE32('S2 '): *model = GB_MODEL_SGB2; return 0; + case BE32('CC0 '): *model = GB_MODEL_CGB_0; return 0; + case BE32('CCA '): *model = GB_MODEL_CGB_A; return 0; + case BE32('CCB '): *model = GB_MODEL_CGB_B; return 0; + case BE32('CCC '): *model = GB_MODEL_CGB_C; return 0; + case BE32('CCD '): *model = GB_MODEL_CGB_D; return 0; + case BE32('CCE '): *model = GB_MODEL_CGB_E; return 0; + case BE32('CAA '): *model = GB_MODEL_AGB_A; return 0; + } + return -1; + + default: + file->seek(file, LE32(block.size), SEEK_CUR); + break; + } + } + } + return -1; +} + + +static int get_state_model_internal(virtual_file_t *file, GB_model_t *model) +{ + GB_gameboy_t save; + + bool fix_broken_windows_saves = false; + + if (file->read(file, GB_GET_SECTION(&save, header), GB_SECTION_SIZE(header)) != GB_SECTION_SIZE(header)) return errno; + if (save.magic == 0) { + /* Potentially legacy, broken Windows save state*/ + + file->seek(file, 4, SEEK_SET); + if (file->read(file, GB_GET_SECTION(&save, header), GB_SECTION_SIZE(header)) != GB_SECTION_SIZE(header)) return errno; + fix_broken_windows_saves = true; + } + if (save.magic != state_magic()) { + return get_state_model_bess(file, model); + } + if (!READ_SECTION(&save, file, core_state)) return errno ?: EIO; + *model = save.model; + return 0; +} + +int GB_get_state_model(const char *path, GB_model_t *model) +{ + FILE *f = fopen(path, "rb"); + if (!f) { + return errno; + } + virtual_file_t file = { + .read = file_read, + .seek = file_seek, + .tell = file_tell, + .file = f, + }; + int ret = get_state_model_internal(&file, model); + fclose(f); + return ret; +} + +int GB_get_state_model_from_buffer(const uint8_t *buffer, size_t length, GB_model_t *model) +{ + virtual_file_t file = { + .read = buffer_read, + .seek = buffer_seek, + .tell = buffer_tell, + .buffer = (uint8_t *)buffer, + .position = 0, + .size = length, + }; + + return get_state_model_internal(&file, model); +} + bool GB_is_save_state(const char *path) { diff --git a/Core/save_state.h b/Core/save_state.h index fc5e359..cb98e26 100644 --- a/Core/save_state.h +++ b/Core/save_state.h @@ -33,6 +33,9 @@ void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer); int GB_load_state(GB_gameboy_t *gb, const char *path); int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t length); bool GB_is_save_state(const char *path); +int GB_get_state_model(const char *path, GB_model_t *model); +int GB_get_state_model_from_buffer(const uint8_t *buffer, size_t length, GB_model_t *model); + #ifdef GB_INTERNAL static inline uint32_t state_magic(void) {