API to detect MBC RAM activity, more frequent battery saves in Cocoa and SDL

This commit is contained in:
Lior Halphon
2026-03-02 00:46:17 +02:00
parent ddce507f4a
commit 202073bb5c
5 changed files with 68 additions and 1 deletions
+17 -1
View File
@@ -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];
+11
View File
@@ -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[] = {
+5
View File
@@ -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);
+13
View File
@@ -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)
+22
View File
@@ -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);
}