SameBoy | Accurate GB/GBC emulator |
| download: https://git.y1.nz/archives/sameboy.tar.gz | |
| README | Files | Log | Refs | LICENSE |
commit 202073bb5cfd9911328cd2e3bc60389987ac34a3 parent ddce507f4a3da96ed2b3371fc25713c151ff6615 Author: Lior Halphon <LIJI32@gmail.com> Date: Mon, 2 Mar 2026 00:46:17 +0200 API to detect MBC RAM activity, more frequent battery saves in Cocoa and SDL Diffstat:
| M | Cocoa/Document.m | 18 | +++++++++++++++++- |
| M | Core/gb.c | 11 | +++++++++++ |
| M | Core/gb.h | 5 | +++++ |
| M | Core/memory.c | 13 | +++++++++++++ |
| M | SDL/main.c | 22 | ++++++++++++++++++++++ |
5 files changed, 68 insertions(+), 1 deletion(-)
diff --git a/Cocoa/Document.m b/Cocoa/Document.m @@ -67,6 +67,9 @@ NSTimer *_consoleOutputTimer; NSTimer *_hexTimer; + NSTimer *_batterySaveTimer; + bool _dirtyBattery; + bool _fullScreen; bool _inSyncInput; NSString *_debuggerCommandWhilePaused; @@ -503,6 +506,9 @@ static void debuggerReloadCallback(GB_gameboy_t *gb) _hexTimer = [NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(reloadMemoryView) userInfo:nil repeats:true]; [[NSRunLoop mainRunLoop] addTimer:_hexTimer forMode:NSDefaultRunLoopMode]; + _batterySaveTimer = [NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(batteryTimerExpired) userInfo:nil repeats:true]; + [[NSRunLoop mainRunLoop] addTimer:_batterySaveTimer forMode:NSDefaultRunLoopMode]; + /* Clear pending alarms, don't play alarms while playing */ if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GBNotificationsUsed"]) { NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter]; @@ -580,6 +586,7 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) - (void)postRun { [_hexTimer invalidate]; + [_batterySaveTimer invalidate]; [_audioLock lock]; _audioBufferPosition = _audioBufferNeeded = 0; [_audioLock signal]; @@ -808,6 +815,16 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) } } +- (void)batteryTimerExpired +{ + if (_dirtyBattery && !GB_get_battery_dirty(&_gb)) { + GB_save_battery(&_gb, self.savPath.UTF8String); + } + + _dirtyBattery = GB_get_battery_dirty(&_gb); + GB_clear_battery_dirty(&_gb); +} + - (NSFont *)debuggerFontOfSize:(unsigned)size { if (!size) { @@ -835,7 +852,6 @@ again:; goto again; } - - (void)updateFonts { _hexController.font = [self debuggerFontOfSize:12]; diff --git a/Core/gb.c b/Core/gb.c @@ -2032,6 +2032,17 @@ void GB_get_rom_title(GB_gameboy_t *gb, char *title) } } +bool GB_get_battery_dirty(GB_gameboy_t *gb) +{ + if (GB_save_battery_size(gb) == 0) return false; + return gb->battery_dirty; +} + +void GB_clear_battery_dirty(GB_gameboy_t *gb) +{ + gb->battery_dirty = false; +} + uint32_t GB_get_rom_crc32(GB_gameboy_t *gb) { static const uint32_t table[] = { diff --git a/Core/gb.h b/Core/gb.h @@ -841,6 +841,7 @@ struct GB_gameboy_internal_s { GB_rumble_mode_t rumble_mode; uint32_t rumble_on_cycles; uint32_t rumble_off_cycles; + bool battery_dirty; /* Temporary state */ bool wx_just_changed; @@ -1009,6 +1010,10 @@ uint32_t GB_get_clock_rate(GB_gameboy_t *gb); uint32_t GB_get_unmultiplied_clock_rate(GB_gameboy_t *gb); void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier); +/* Checks if the ROM has modified battery-backed data since the last call to GB_clear_battery_dirty */ +bool GB_get_battery_dirty(GB_gameboy_t *gb); +void GB_clear_battery_dirty(GB_gameboy_t *gb); + /* Handy ROM info APIs */ // `title` must be at least 17 bytes in size void GB_get_rom_title(GB_gameboy_t *gb, char *title); diff --git a/Core/memory.c b/Core/memory.c @@ -865,6 +865,7 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) break; case 0x6000: case 0x7000: memcpy(&gb->rtc_latched, &gb->rtc_real, sizeof(gb->rtc_real)); + gb->battery_dirty = true; break; } break; @@ -976,9 +977,11 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) break; case 0x10: memcpy(&gb->rtc_latched, &gb->rtc_real, sizeof(gb->rtc_real)); + gb->battery_dirty = true; break; case 0x11: { memcpy(&gb->rtc_real, &gb->rtc_latched, sizeof(gb->rtc_real)); + gb->battery_dirty = true; break; } case 0x14: @@ -1045,13 +1048,16 @@ static bool huc3_write(GB_gameboy_t *gb, uint8_t value) else if (gb->huc3.access_index >= 0x58 && gb->huc3.access_index <= 0x5A) { gb->huc3.alarm_minutes &= ~(0xF << ((gb->huc3.access_index - 0x58) * 4)); gb->huc3.alarm_minutes |= ((value & 0xF) << ((gb->huc3.access_index - 0x58) * 4)); + gb->battery_dirty = true; } else if (gb->huc3.access_index >= 0x5B && gb->huc3.access_index <= 0x5E) { gb->huc3.alarm_days &= ~(0xF << ((gb->huc3.access_index - 0x5B) * 4)); gb->huc3.alarm_days |= ((value & 0xF) << ((gb->huc3.access_index - 0x5B) * 4)); + gb->battery_dirty = true; } else if (gb->huc3.access_index == 0x5F) { gb->huc3.alarm_enabled = value & 1; + gb->battery_dirty = true; } else { // GB_log(gb, "Attempting to write %x to unsupported HuC-3 register: %03x\n", value & 0xF, gb->huc3.access_index); @@ -1157,6 +1163,7 @@ static void write_mbc7_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) // WRITE if (gb->mbc7.eeprom_write_enabled) { ((uint16_t *)gb->mbc_ram)[gb->mbc7.eeprom_command & 0x7F] = 0; + gb->battery_dirty = true; } gb->mbc7.argument_bits_left = 16; // We still need to process this command, don't erase eeprom_command @@ -1168,6 +1175,7 @@ static void write_mbc7_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) // ERASE if (gb->mbc7.eeprom_write_enabled) { ((uint16_t *)gb->mbc_ram)[gb->mbc7.eeprom_command & 0x7F] = 0xFFFF; + gb->battery_dirty = true; gb->mbc7.read_bits = 0x3FFF; // Emulate some time to settle } gb->mbc7.eeprom_command = 0; @@ -1177,6 +1185,7 @@ static void write_mbc7_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) if (gb->mbc7.eeprom_write_enabled) { memset(gb->mbc_ram, 0xFF, gb->mbc_ram_size); ((uint16_t *)gb->mbc_ram)[gb->mbc7.eeprom_command & 0x7F] = 0xFFFF; + gb->battery_dirty = true; gb->mbc7.read_bits = 0xFF; // Emulate some time to settle } gb->mbc7.eeprom_command = 0; @@ -1185,6 +1194,7 @@ static void write_mbc7_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) // WRAL (WRite ALl) if (gb->mbc7.eeprom_write_enabled) { memset(gb->mbc_ram, 0, gb->mbc_ram_size); + gb->battery_dirty = true; } gb->mbc7.argument_bits_left = 16; // We still need to process this command, don't erase eeprom_command @@ -1201,11 +1211,13 @@ static void write_mbc7_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) if (gb->mbc7.eeprom_command & 0x100) { // WRITE ((uint16_t *)gb->mbc_ram)[gb->mbc7.eeprom_command & 0x7F] |= bit; + gb->battery_dirty = true; } else { // WRAL for (unsigned i = 0; i < 0x7F; i++) { ((uint16_t *)gb->mbc_ram)[i] |= bit; + gb->battery_dirty = true; } } } @@ -1291,6 +1303,7 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) } gb->mbc_ram[((addr & 0x1FFF) + effective_bank * 0x2000) & (gb->mbc_ram_size - 1)] = value; + gb->battery_dirty = true; } static void write_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) diff --git a/SDL/main.c b/SDL/main.c @@ -37,6 +37,8 @@ static typeof(free) *free_function = NULL; static char *battery_save_path_ptr = NULL; static SDL_GLContext gl_context = NULL; static bool console_supported = false; +static bool battery_dirty = false; +static unsigned battery_timer = 0; bool uses_gl(void) { @@ -433,6 +435,9 @@ static void handle_events(GB_gameboy_t *gb) break; case HOTKEY_PAUSE: paused = !paused; + if (paused) { + GB_save_battery(gb, battery_save_path_ptr); + } break; case HOTKEY_MUTE: GB_audio_set_paused(GB_audio_is_playing()); @@ -582,6 +587,9 @@ static void handle_events(GB_gameboy_t *gb) case SDL_SCANCODE_P: if (event.key.keysym.mod & MODIFIER) { paused = !paused; + if (paused) { + GB_save_battery(gb, battery_save_path_ptr); + } } break; case SDL_SCANCODE_M: @@ -758,6 +766,20 @@ static void vblank(GB_gameboy_t *gb, GB_vblank_type_t type) } } do_rewind = rewind_down; + + battery_timer++; + if (battery_timer == 15) { + battery_timer = 0; + + if (battery_dirty && !GB_get_battery_dirty(gb)) { + GB_save_battery(gb, battery_save_path_ptr); + GB_log(gb, "Saved\n"); + } + + battery_dirty = GB_get_battery_dirty(gb); + GB_clear_battery_dirty(gb); + } + handle_events(gb); }
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.