Fix various save state compatibility issues between Windows and non-Windows, and a potential crash

This commit is contained in:
Lior Halphon 2020-06-10 22:46:19 +03:00
parent edf7762408
commit b6b56d0766
5 changed files with 70 additions and 31 deletions

View File

@ -602,7 +602,7 @@ struct GB_gameboy_internal_s {
GB_icd_vreset_callback_t icd_vreset_callback; GB_icd_vreset_callback_t icd_vreset_callback;
GB_read_memory_callback_t read_memory_callback; GB_read_memory_callback_t read_memory_callback;
GB_boot_rom_load_callback_t boot_rom_load_callback; GB_boot_rom_load_callback_t boot_rom_load_callback;
GB_print_image_callback_t printer_callback;
/* IR */ /* IR */
uint64_t cycles_since_ir_change; // In 8MHz units uint64_t cycles_since_ir_change; // In 8MHz units
uint64_t cycles_since_input_ir_change; // In 8MHz units uint64_t cycles_since_input_ir_change; // In 8MHz units

View File

@ -31,8 +31,8 @@ static void handle_command(GB_gameboy_t *gb)
image[i] = colors[(palette >> (gb->printer.image[i] * 2)) & 3]; image[i] = colors[(palette >> (gb->printer.image[i] * 2)) & 3];
} }
if (gb->printer.callback) { if (gb->printer_callback) {
gb->printer.callback(gb, image, gb->printer.image_offset / 160, gb->printer_callback(gb, image, gb->printer.image_offset / 160,
gb->printer.command_data[1] >> 4, gb->printer.command_data[1] & 7, gb->printer.command_data[1] >> 4, gb->printer.command_data[1] & 7,
gb->printer.command_data[3] & 0x7F); gb->printer.command_data[3] & 0x7F);
} }
@ -212,5 +212,5 @@ void GB_connect_printer(GB_gameboy_t *gb, GB_print_image_callback_t callback)
memset(&gb->printer, 0, sizeof(gb->printer)); memset(&gb->printer, 0, sizeof(gb->printer));
GB_set_serial_transfer_bit_start_callback(gb, serial_start); GB_set_serial_transfer_bit_start_callback(gb, serial_start);
GB_set_serial_transfer_bit_end_callback(gb, serial_end); GB_set_serial_transfer_bit_end_callback(gb, serial_end);
gb->printer.callback = callback; gb->printer_callback = callback;
} }

View File

@ -48,7 +48,8 @@ typedef struct
uint8_t image[160 * 200]; uint8_t image[160 * 200];
uint16_t image_offset; uint16_t image_offset;
GB_print_image_callback_t callback; /* TODO: Delete me. */
uint64_t padding;
uint8_t compression_run_lenth; uint8_t compression_run_lenth;
bool compression_run_is_compressed; bool compression_run_is_compressed;

View File

@ -40,7 +40,6 @@ int GB_save_state(GB_gameboy_t *gb, const char *path)
if (!dump_section(f, gb->sgb, sizeof(*gb->sgb))) goto error; if (!dump_section(f, gb->sgb, sizeof(*gb->sgb))) 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) {
goto error; goto error;
} }
@ -116,13 +115,21 @@ void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer)
} }
/* Best-effort read function for maximum future compatibility. */ /* Best-effort read function for maximum future compatibility. */
static bool read_section(FILE *f, void *dest, uint32_t size) static bool read_section(FILE *f, void *dest, uint32_t size, bool fix_broken_windows_saves)
{ {
uint32_t saved_size = 0; uint32_t saved_size = 0;
if (fread(&saved_size, 1, sizeof(size), f) != sizeof(size)) { if (fread(&saved_size, 1, sizeof(size), f) != sizeof(size)) {
return false; return false;
} }
if (fix_broken_windows_saves) {
if (saved_size < 4) {
return false;
}
saved_size -= 4;
fseek(f, 4, SEEK_CUR);
}
if (saved_size <= size) { if (saved_size <= size) {
if (fread(dest, 1, saved_size, f) != saved_size) { if (fread(dest, 1, saved_size, f) != saved_size) {
return false; return false;
@ -139,11 +146,21 @@ static bool read_section(FILE *f, void *dest, uint32_t size)
} }
#undef DUMP_SECTION #undef DUMP_SECTION
static bool verify_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t *save) static bool verify_and_update_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t *save)
{ {
if (gb->magic != save->magic) { if (save->ram_size == 0 && (&save->ram_size)[-1] == gb->ram_size) {
GB_log(gb, "The file is not a save state, or is from an incompatible operating system.\n"); /* This is a save state with a bad printer struct from a 32-bit OS */
return false; memcpy(save->extra_oam + 4, save->extra_oam, (uintptr_t)&save->ram_size - (uintptr_t)&save->extra_oam);
}
if (save->ram_size == 0) {
/* Save doesn't have ram size specified, it's a pre 0.12 save state with potentially
incorrect RAM amount if it's a CGB instance */
if (GB_is_cgb(save)) {
save->ram_size = 0x2000 * 8; // Incorrect RAM size
}
else {
save->ram_size = gb->ram_size;
}
} }
if (gb->version != save->version) { if (gb->version != save->version) {
@ -202,7 +219,7 @@ static void sanitize_state(GB_gameboy_t *gb)
} }
} }
#define READ_SECTION(gb, f, section) read_section(f, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section)) #define READ_SECTION(gb, f, section) read_section(f, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section), fix_broken_windows_saves)
int GB_load_state(GB_gameboy_t *gb, const char *path) int GB_load_state(GB_gameboy_t *gb, const char *path)
{ {
@ -219,7 +236,18 @@ int GB_load_state(GB_gameboy_t *gb, const char *path)
return errno; return errno;
} }
bool fix_broken_windows_saves = false;
if (fread(GB_GET_SECTION(&save, header), 1, GB_SECTION_SIZE(header), f) != GB_SECTION_SIZE(header)) goto error; if (fread(GB_GET_SECTION(&save, header), 1, GB_SECTION_SIZE(header), f) != GB_SECTION_SIZE(header)) goto error;
if (save.magic == 0) {
/* Potentially legacy, broken Windows save state */
fseek(f, 4, SEEK_SET);
if (fread(GB_GET_SECTION(&save, header), 1, GB_SECTION_SIZE(header), f) != GB_SECTION_SIZE(header)) goto error;
fix_broken_windows_saves = true;
}
if (gb->magic != save.magic) {
GB_log(gb, "The file is not a save state, or is from an incompatible operating system.\n");
return false;
}
if (!READ_SECTION(&save, f, core_state)) goto error; if (!READ_SECTION(&save, f, core_state)) goto error;
if (!READ_SECTION(&save, f, dma )) goto error; if (!READ_SECTION(&save, f, dma )) goto error;
if (!READ_SECTION(&save, f, mbc )) goto error; if (!READ_SECTION(&save, f, mbc )) goto error;
@ -229,24 +257,13 @@ int GB_load_state(GB_gameboy_t *gb, const char *path)
if (!READ_SECTION(&save, f, rtc )) goto error; if (!READ_SECTION(&save, f, rtc )) goto error;
if (!READ_SECTION(&save, f, video )) goto error; if (!READ_SECTION(&save, f, video )) goto error;
if (save.ram_size == 0) { if (!verify_and_update_state_compatibility(gb, &save)) {
/* Save doesn't have ram size specified, it's a pre 0.12 save state with potentially
incorrect RAM amount if it's a CGB instance */
if (GB_is_cgb(&save)) {
save.ram_size = 0x2000 * 8; // Incorrect RAM size
}
else {
save.ram_size = gb->ram_size;
}
}
if (!verify_state_compatibility(gb, &save)) {
errno = -1; errno = -1;
goto error; goto error;
} }
if (GB_is_hle_sgb(gb)) { if (GB_is_hle_sgb(gb)) {
if (!read_section(f, gb->sgb, sizeof(*gb->sgb))) goto error; if (!read_section(f, gb->sgb, sizeof(*gb->sgb), false)) goto error;
} }
memset(gb->mbc_ram + save.mbc_ram_size, 0xFF, gb->mbc_ram_size - save.mbc_ram_size); memset(gb->mbc_ram + save.mbc_ram_size, 0xFF, gb->mbc_ram_size - save.mbc_ram_size);
@ -297,7 +314,7 @@ static size_t buffer_read(void *dest, size_t length, const uint8_t **buffer, siz
return length; return length;
} }
static bool buffer_read_section(const uint8_t **buffer, size_t *buffer_length, void *dest, uint32_t size) static bool buffer_read_section(const uint8_t **buffer, size_t *buffer_length, void *dest, uint32_t size, bool fix_broken_windows_saves)
{ {
uint32_t saved_size = 0; uint32_t saved_size = 0;
if (buffer_read(&saved_size, sizeof(size), buffer, buffer_length) != sizeof(size)) { if (buffer_read(&saved_size, sizeof(size), buffer, buffer_length) != sizeof(size)) {
@ -306,6 +323,14 @@ static bool buffer_read_section(const uint8_t **buffer, size_t *buffer_length, v
if (saved_size > *buffer_length) return false; if (saved_size > *buffer_length) return false;
if (fix_broken_windows_saves) {
if (saved_size < 4) {
return false;
}
saved_size -= 4;
*buffer += 4;
}
if (saved_size <= size) { if (saved_size <= size) {
if (buffer_read(dest, saved_size, buffer, buffer_length) != saved_size) { if (buffer_read(dest, saved_size, buffer, buffer_length) != saved_size) {
return false; return false;
@ -322,15 +347,27 @@ static bool buffer_read_section(const uint8_t **buffer, size_t *buffer_length, v
return true; return true;
} }
#define READ_SECTION(gb, buffer, length, section) buffer_read_section(&buffer, &length, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section)) #define READ_SECTION(gb, buffer, length, section) buffer_read_section(&buffer, &length, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section), fix_broken_windows_saves)
int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t length) int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t length)
{ {
GB_gameboy_t save; GB_gameboy_t save;
/* Every unread value should be kept the same. */ /* Every unread value should be kept the same. */
memcpy(&save, gb, sizeof(save)); memcpy(&save, gb, sizeof(save));
bool fix_broken_windows_saves = false;
if (buffer_read(GB_GET_SECTION(&save, header), GB_SECTION_SIZE(header), &buffer, &length) != GB_SECTION_SIZE(header)) return -1; if (buffer_read(GB_GET_SECTION(&save, header), GB_SECTION_SIZE(header), &buffer, &length) != GB_SECTION_SIZE(header)) return -1;
if (save.magic == 0) {
/* Potentially legacy, broken Windows save state*/
buffer -= GB_SECTION_SIZE(header) - 4;
length += GB_SECTION_SIZE(header) - 4;
if (buffer_read(GB_GET_SECTION(&save, header), GB_SECTION_SIZE(header), &buffer, &length) != GB_SECTION_SIZE(header)) return -1;
fix_broken_windows_saves = true;
}
if (gb->magic != save.magic) {
GB_log(gb, "The file is not a save state, or is from an incompatible operating system.\n");
return false;
}
if (!READ_SECTION(&save, buffer, length, core_state)) return -1; if (!READ_SECTION(&save, buffer, length, core_state)) return -1;
if (!READ_SECTION(&save, buffer, length, dma )) return -1; if (!READ_SECTION(&save, buffer, length, dma )) return -1;
if (!READ_SECTION(&save, buffer, length, mbc )) return -1; if (!READ_SECTION(&save, buffer, length, mbc )) return -1;
@ -340,12 +377,13 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le
if (!READ_SECTION(&save, buffer, length, rtc )) return -1; if (!READ_SECTION(&save, buffer, length, rtc )) return -1;
if (!READ_SECTION(&save, buffer, length, video )) return -1; if (!READ_SECTION(&save, buffer, length, video )) return -1;
if (!verify_state_compatibility(gb, &save)) {
if (!verify_and_update_state_compatibility(gb, &save)) {
return -1; return -1;
} }
if (GB_is_hle_sgb(gb)) { if (GB_is_hle_sgb(gb)) {
if (!buffer_read_section(&buffer, &length, gb->sgb, sizeof(*gb->sgb))) return -1; if (!buffer_read_section(&buffer, &length, gb->sgb, sizeof(*gb->sgb), false)) return -1;
} }
memset(gb->mbc_ram + save.mbc_ram_size, 0xFF, gb->mbc_ram_size - save.mbc_ram_size); memset(gb->mbc_ram + save.mbc_ram_size, 0xFF, gb->mbc_ram_size - save.mbc_ram_size);

View File

@ -5,7 +5,7 @@
#define GB_PADDING(type, old_usage) type old_usage##__do_not_use #define GB_PADDING(type, old_usage) type old_usage##__do_not_use
#define GB_SECTION(name, ...) __attribute__ ((aligned (8))) struct {} name##_section_start; __VA_ARGS__; struct {} name##_section_end #define GB_SECTION(name, ...) __attribute__ ((aligned (8))) union {uint8_t name##_section_start; struct {__VA_ARGS__};}; uint8_t name##_section_end[0]
#define GB_SECTION_OFFSET(name) (offsetof(GB_gameboy_t, name##_section_start)) #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_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)) #define GB_GET_SECTION(gb, name) ((void*)&((gb)->name##_section_start))