diff --git a/Core/gb.c b/Core/gb.c index 1e940cf..e91420c 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -251,6 +251,48 @@ typedef union { } vba64; } GB_rtc_save_t; +int GB_save_battery_size(GB_gameboy_t *gb) +{ + if (!gb->cartridge_type->has_battery) return 0; // Nothing to save. + if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */ + + GB_rtc_save_t rtc_save_size; + return gb->mbc_ram_size + (gb->cartridge_type->has_rtc ? sizeof(rtc_save_size.vba64) : 0); +} + +int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size) +{ + if (!gb->cartridge_type->has_battery) return 0; // Nothing to save. + if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */ + + if (size < GB_save_battery_size(gb)) return EIO; + + memcpy(buffer, gb->mbc_ram, gb->mbc_ram_size); + + if (gb->cartridge_type->has_rtc) { + GB_rtc_save_t rtc_save = {{{{0,}},},}; + rtc_save.vba64.rtc_real.seconds = gb->rtc_real.seconds; + rtc_save.vba64.rtc_real.minutes = gb->rtc_real.minutes; + rtc_save.vba64.rtc_real.hours = gb->rtc_real.hours; + rtc_save.vba64.rtc_real.days = gb->rtc_real.days; + rtc_save.vba64.rtc_real.high = gb->rtc_real.high; + rtc_save.vba64.rtc_latched.seconds = gb->rtc_latched.seconds; + rtc_save.vba64.rtc_latched.minutes = gb->rtc_latched.minutes; + rtc_save.vba64.rtc_latched.hours = gb->rtc_latched.hours; + rtc_save.vba64.rtc_latched.days = gb->rtc_latched.days; + rtc_save.vba64.rtc_latched.high = gb->rtc_latched.high; +#ifdef GB_BIG_ENDIAN + rtc_save.vba64.last_rtc_second = __builtin_bswap64(gb->last_rtc_second); +#else + rtc_save.vba64.last_rtc_second = gb->last_rtc_second; +#endif + memcpy(buffer + gb->mbc_ram_size, &rtc_save.vba64, sizeof(rtc_save.vba64)); + } + + errno = 0; + return errno; +} + int GB_save_battery(GB_gameboy_t *gb, const char *path) { if (!gb->cartridge_type->has_battery) return 0; // Nothing to save. @@ -294,6 +336,79 @@ int GB_save_battery(GB_gameboy_t *gb, const char *path) return errno; } +void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size) +{ + memcpy(gb->mbc_ram, buffer, MIN(gb->mbc_ram_size, size)); + if (size <= gb->mbc_ram_size) { + goto reset_rtc; + } + + GB_rtc_save_t rtc_save; + memcpy(&rtc_save, buffer + gb->mbc_ram_size, MIN(sizeof(rtc_save), size)); + switch (size - gb->mbc_ram_size) { + case sizeof(rtc_save.sameboy_legacy): + memcpy(&gb->rtc_real, &rtc_save.sameboy_legacy.rtc_real, sizeof(gb->rtc_real)); + memcpy(&gb->rtc_latched, &rtc_save.sameboy_legacy.rtc_real, sizeof(gb->rtc_real)); + gb->last_rtc_second = rtc_save.sameboy_legacy.last_rtc_second; + break; + + case sizeof(rtc_save.vba32): + gb->rtc_real.seconds = rtc_save.vba32.rtc_real.seconds; + gb->rtc_real.minutes = rtc_save.vba32.rtc_real.minutes; + gb->rtc_real.hours = rtc_save.vba32.rtc_real.hours; + gb->rtc_real.days = rtc_save.vba32.rtc_real.days; + gb->rtc_real.high = rtc_save.vba32.rtc_real.high; + gb->rtc_latched.seconds = rtc_save.vba32.rtc_latched.seconds; + gb->rtc_latched.minutes = rtc_save.vba32.rtc_latched.minutes; + gb->rtc_latched.hours = rtc_save.vba32.rtc_latched.hours; + gb->rtc_latched.days = rtc_save.vba32.rtc_latched.days; + gb->rtc_latched.high = rtc_save.vba32.rtc_latched.high; +#ifdef GB_BIG_ENDIAN + gb->last_rtc_second = __builtin_bswap32(rtc_save.vba32.last_rtc_second); +#else + gb->last_rtc_second = rtc_save.vba32.last_rtc_second; +#endif + break; + + case sizeof(rtc_save.vba64): + gb->rtc_real.seconds = rtc_save.vba64.rtc_real.seconds; + gb->rtc_real.minutes = rtc_save.vba64.rtc_real.minutes; + gb->rtc_real.hours = rtc_save.vba64.rtc_real.hours; + gb->rtc_real.days = rtc_save.vba64.rtc_real.days; + gb->rtc_real.high = rtc_save.vba64.rtc_real.high; + gb->rtc_latched.seconds = rtc_save.vba64.rtc_latched.seconds; + gb->rtc_latched.minutes = rtc_save.vba64.rtc_latched.minutes; + gb->rtc_latched.hours = rtc_save.vba64.rtc_latched.hours; + gb->rtc_latched.days = rtc_save.vba64.rtc_latched.days; + gb->rtc_latched.high = rtc_save.vba64.rtc_latched.high; +#ifdef GB_BIG_ENDIAN + gb->last_rtc_second = __builtin_bswap64(rtc_save.vba64.last_rtc_second); +#else + gb->last_rtc_second = rtc_save.vba64.last_rtc_second; +#endif + break; + + default: + goto reset_rtc; + } + if (gb->last_rtc_second > time(NULL)) { + /* We must reset RTC here, or it will not advance. */ + goto reset_rtc; + } + + if (gb->last_rtc_second < 852076800) { /* 1/1/97. There weren't any RTC games that time, + so if the value we read is lower it means it wasn't + really RTC data. */ + goto reset_rtc; + } + goto exit; +reset_rtc: + gb->last_rtc_second = time(NULL); + gb->rtc_real.high |= 0x80; /* This gives the game a hint that the clock should be reset. */ +exit: + return; +} + /* Loading will silently stop if the format is incomplete */ void GB_load_battery(GB_gameboy_t *gb, const char *path) { diff --git a/Core/gb.h b/Core/gb.h index e12fcfa..59a11c2 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -1,5 +1,6 @@ #ifndef GB_h #define GB_h +#define typeof __typeof__ #include #include #include @@ -542,6 +543,7 @@ struct GB_gameboy_internal_s { GB_icd_pixel_callback_t icd_pixel_callback; GB_icd_vreset_callback_t icd_hreset_callback; GB_icd_vreset_callback_t icd_vreset_callback; + GB_read_memory_callback_t read_memory_callback; /* IR */ long cycles_since_ir_change; // In 8MHz units @@ -668,8 +670,12 @@ int GB_load_boot_rom(GB_gameboy_t *gb, const char *path); void GB_load_boot_rom_from_buffer(GB_gameboy_t *gb, const unsigned char *buffer, size_t size); int GB_load_rom(GB_gameboy_t *gb, const char *path); void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size); - + +int GB_save_battery_size(GB_gameboy_t *gb); +int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size); int GB_save_battery(GB_gameboy_t *gb, const char *path); + +void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size); void GB_load_battery(GB_gameboy_t *gb, const char *path); void GB_set_turbo_mode(GB_gameboy_t *gb, bool on, bool no_frame_skip); diff --git a/Core/memory.c b/Core/memory.c index 76d8821..40ea00f 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -429,6 +429,11 @@ uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr) if (is_addr_in_dma_use(gb, addr)) { addr = gb->dma_current_src; } + if (gb->read_memory_callback) { + uint8_t data = read_map[addr >> 12](gb, addr); + data = gb->read_memory_callback(gb, addr, data); + return data; + } return read_map[addr >> 12](gb, addr); } diff --git a/Core/memory.h b/Core/memory.h index 03d636d..f0d0390 100644 --- a/Core/memory.h +++ b/Core/memory.h @@ -3,6 +3,9 @@ #include "gb_struct_def.h" #include +typedef uint8_t (*GB_read_memory_callback_t)(GB_gameboy_t *gb, uint16_t addr, uint8_t data); +void GB_set_read_memory_callback(GB_gameboy_t *gb, GB_read_memory_callback_t callback); + uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr); void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value); #ifdef GB_INTERNAL diff --git a/Core/sgb.c b/Core/sgb.c index 7d648f7..c36bc3c 100644 --- a/Core/sgb.c +++ b/Core/sgb.c @@ -3,6 +3,10 @@ #include #include +#ifndef M_PI + #define M_PI 3.14159265358979323846 +#endif + #define INTRO_ANIMATION_LENGTH 200 enum { diff --git a/Core/timing.c b/Core/timing.c index 138819f..283558c 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -3,7 +3,7 @@ #ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0500 #endif -#include +#include #else #include #endif