From b6c6b9ed5461a24cb08f0c3482f9bfaa9f86d13e Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sun, 25 Aug 2024 14:36:48 +0300 Subject: [PATCH] Automatic model selection in the Cocoa and SDL frontends, closes #648 --- Cocoa/Document.h | 12 ++++++ Cocoa/Document.m | 79 +++++++++++++++++++----------------- Cocoa/GBApp.m | 2 + Cocoa/MainMenu.xib | 75 ++++++++++++++++++++-------------- SDL/configuration.c | 2 +- SDL/configuration.h | 1 + SDL/gui.c | 25 ++++++++---- SDL/main.c | 99 +++++++++++++++++++++++++++++++-------------- 8 files changed, 189 insertions(+), 106 deletions(-) diff --git a/Cocoa/Document.h b/Cocoa/Document.h index 2546cb9b9..353d09743 100644 --- a/Cocoa/Document.h +++ b/Cocoa/Document.h @@ -6,6 +6,18 @@ #import "GBOSDView.h" #import "GBDebuggerButton.h" +enum model { + MODEL_NONE, + MODEL_DMG, + MODEL_CGB, + MODEL_AGB, + MODEL_SGB, + MODEL_MGB, + MODEL_AUTO, + + MODEL_QUICK_RESET = -1, +}; + @class GBCheatWindowController; @class GBPaletteView; @class GBObjectView; diff --git a/Cocoa/Document.m b/Cocoa/Document.m index aa9e276aa..9a86707c1 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -48,16 +48,6 @@ /* Todo: The general Objective-C coding style conflicts with SameBoy's. This file needs a cleanup. */ /* Todo: Split into category files! This is so messy!!! */ -enum model { - MODEL_NONE, - MODEL_DMG, - MODEL_CGB, - MODEL_AGB, - MODEL_SGB, - MODEL_MGB, - - MODEL_QUICK_RESET = -1, -}; @interface Document () @property GBAudioClient *audioClient; @@ -100,6 +90,7 @@ enum model { bool _logToSideView; bool _shouldClearSideView; enum model _currentModel; + bool _usesAutoModel; bool _rewind; bool _modelsChanging; @@ -263,6 +254,7 @@ static void debuggerReloadCallback(GB_gameboy_t *gb) case MODEL_NONE: case MODEL_QUICK_RESET: + case MODEL_AUTO: case MODEL_CGB: return (GB_model_t)[[NSUserDefaults standardUserDefaults] integerForKey:@"GBCGBModel"]; @@ -677,15 +669,46 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) GB_load_boot_rom(&_gb, [path UTF8String]); } +- (enum model)bestModelForROM +{ + uint8_t *rom = GB_get_direct_access(&_gb, GB_DIRECT_ACCESS_ROM, NULL, NULL); + if (!rom) return MODEL_CGB; + + if (rom[0x143] & 0x80) { // Has CGB features + return MODEL_CGB; + } + if (rom[0x146] == 3) { // Has SGB features + return MODEL_SGB; + } + + if (rom[0x14B] == 1) { // Nintendo-licensed (most likely has boot ROM palettes) + return MODEL_CGB; + } + + if (rom[0x14B] == 0x33 && + rom[0x144] == '0' && + rom[0x145] == '1') { // Ditto + return MODEL_CGB; + } + + return MODEL_DMG; +} + - (IBAction)reset:(id)sender { [self stop]; size_t old_width = GB_get_screen_width(&_gb); - if ([sender tag] != MODEL_NONE) { + if ([sender tag] > MODEL_NONE) { + /* User explictly selected a model, save the preference */ _currentModel = (enum model)[sender tag]; + _usesAutoModel = _currentModel == MODEL_AUTO; + [[NSUserDefaults standardUserDefaults] setInteger:_currentModel forKey:@"GBEmulatedModel"]; } + /* Reload the ROM, SAV and SYM files */ + [self loadROM]; + if ([sender tag] == MODEL_QUICK_RESET) { GB_quick_reset(&_gb); } @@ -699,16 +722,6 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) [self updateMinSize]; - if ([sender tag] > MODEL_NONE) { - /* User explictly selected a model, save the preference */ - [[NSUserDefaults standardUserDefaults] setBool:_currentModel == MODEL_DMG forKey:@"EmulateDMG"]; - [[NSUserDefaults standardUserDefaults] setBool:_currentModel == MODEL_SGB forKey:@"EmulateSGB"]; - [[NSUserDefaults standardUserDefaults] setBool:_currentModel == MODEL_AGB forKey:@"EmulateAGB"]; - [[NSUserDefaults standardUserDefaults] setBool:_currentModel == MODEL_MGB forKey:@"EmulateMGB"]; - } - - /* Reload the ROM, SAV and SYM files */ - [self loadROM]; [self start]; @@ -871,19 +884,10 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency) [self observeStandardDefaultsKey:@"GBVolume" withBlock:^(id newValue) { weakSelf->_volume = [[NSUserDefaults standardUserDefaults] doubleForKey:@"GBVolume"]; }]; - - if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateDMG"]) { - _currentModel = MODEL_DMG; - } - else if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateSGB"]) { - _currentModel = MODEL_SGB; - } - else if ([[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateMGB"]) { - _currentModel = MODEL_MGB; - } - else { - _currentModel = [[NSUserDefaults standardUserDefaults] boolForKey:@"EmulateAGB"]? MODEL_AGB : MODEL_CGB; - } + + + _currentModel = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBEmulatedModel"]; + _usesAutoModel = _currentModel == MODEL_AUTO; [self initCommon]; self.view.gb = &_gb; @@ -1125,7 +1129,7 @@ static bool is_path_writeable(const char *path) return true; } -- (int) loadROM +- (int)loadROM { __block int ret = 0; NSString *fileName = self.romPath; @@ -1177,6 +1181,9 @@ static bool is_path_writeable(const char *path) [GBWarningPopover popoverWithContents:rom_warnings onWindow:self.mainWindow]; } _fileModificationTime = [[NSFileManager defaultManager] attributesOfItemAtPath:fileName error:nil][NSFileModificationDate]; + if (_usesAutoModel) { + _currentModel = [self bestModelForROM]; + } return ret; } @@ -1250,7 +1257,7 @@ static bool is_path_writeable(const char *path) return !GB_debugger_is_stopped(&_gb); } else if ([anItem action] == @selector(reset:) && anItem.tag != MODEL_NONE && anItem.tag != MODEL_QUICK_RESET) { - [(NSMenuItem *)anItem setState:anItem.tag == _currentModel]; + [(NSMenuItem *)anItem setState:(anItem.tag == _currentModel) || (anItem.tag == MODEL_AUTO && _usesAutoModel)]; } else if ([anItem action] == @selector(interrupt:)) { if (![[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]) { diff --git a/Cocoa/GBApp.m b/Cocoa/GBApp.m index 47b742b6f..0e3c0f0e1 100644 --- a/Cocoa/GBApp.m +++ b/Cocoa/GBApp.m @@ -83,6 +83,8 @@ static uint32_t color_to_int(NSColor *color) @"GBJoyConAutoPair": @YES, @"GBJoyConsDefaultsToHorizontal": @YES, + @"GBEmulatedModel": @(MODEL_AUTO), + // Default themes @"GBThemes": @{ @"Desert": @{ diff --git a/Cocoa/MainMenu.xib b/Cocoa/MainMenu.xib index ee78351fe..d1ea5b3d5 100644 --- a/Cocoa/MainMenu.xib +++ b/Cocoa/MainMenu.xib @@ -218,6 +218,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -360,37 +404,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SDL/configuration.c b/SDL/configuration.c index 9847a2d07..b59f47015 100644 --- a/SDL/configuration.c +++ b/SDL/configuration.c @@ -43,7 +43,7 @@ configuration_t configuration = .scaling_mode = GB_SDL_SCALING_INTEGER_FACTOR, .blending_mode = GB_FRAME_BLENDING_MODE_ACCURATE, .rewind_length = 60 * 2, - .model = MODEL_CGB, + .model = MODEL_AUTO, .volume = 100, .rumble_mode = GB_RUMBLE_CARTRIDGE_ONLY, .default_scale = 2, diff --git a/SDL/configuration.h b/SDL/configuration.h index 8a47aebb4..7b5275d31 100644 --- a/SDL/configuration.h +++ b/SDL/configuration.h @@ -83,6 +83,7 @@ typedef struct { MODEL_AGB, MODEL_SGB, MODEL_MGB, + MODEL_AUTO, MODEL_MAX, } model; diff --git a/SDL/gui.c b/SDL/gui.c index 6b0ac63a1..ffdb63d3a 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -933,26 +933,35 @@ static void enter_help_menu(unsigned index) static void cycle_model(unsigned index) { - - configuration.model++; - if (configuration.model == MODEL_MAX) { - configuration.model = 0; + switch (configuration.model) { + case MODEL_DMG: configuration.model = MODEL_MGB; break; + case MODEL_MGB: configuration.model = MODEL_SGB; break; + case MODEL_SGB: configuration.model = MODEL_CGB; break; + case MODEL_CGB: configuration.model = MODEL_AGB; break; + case MODEL_AGB: configuration.model = MODEL_AUTO; break; + case MODEL_AUTO: configuration.model = MODEL_DMG; break; + default: configuration.model = MODEL_AUTO; } pending_command = GB_SDL_RESET_COMMAND; } static void cycle_model_backwards(unsigned index) { - if (configuration.model == 0) { - configuration.model = MODEL_MAX; + switch (configuration.model) { + case MODEL_MGB: configuration.model = MODEL_DMG; break; + case MODEL_SGB: configuration.model = MODEL_MGB; break; + case MODEL_CGB: configuration.model = MODEL_SGB; break; + case MODEL_AGB: configuration.model = MODEL_CGB; break; + case MODEL_AUTO: configuration.model = MODEL_AGB; break; + case MODEL_DMG: configuration.model = MODEL_AUTO; break; + default: configuration.model = MODEL_AUTO; } - configuration.model--; pending_command = GB_SDL_RESET_COMMAND; } static const char *current_model_string(unsigned index) { - return GB_inline_const(const char *[], {"Game Boy", "Game Boy Color", "Game Boy Advance", "Super Game Boy", "Game Boy Pocket"}) + return GB_inline_const(const char *[], {"Game Boy", "Game Boy Color", "Game Boy Advance", "Super Game Boy", "Game Boy Pocket", "Pick Automatically"}) [configuration.model]; } diff --git a/SDL/main.c b/SDL/main.c index 6a395568d..b38d6c571 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -134,24 +134,21 @@ static void log_capture_callback(GB_gameboy_t *gb, const char *string, GB_log_at captured_log[current_len + len_to_add] = 0; } -static void start_capturing_logs(void) +static void *start_capturing_logs(void) { - if (captured_log != NULL) { - free(captured_log); - } + void *previous = captured_log; captured_log = malloc(1); captured_log[0] = 0; GB_set_log_callback(&gb, log_capture_callback); + return previous; } -static const char *end_capturing_logs(bool show_popup, bool should_exit, uint32_t popup_flags, const char *title) +static void end_capturing_logs(bool show_popup, bool should_exit, uint32_t popup_flags, const char *title, void *previous) { - GB_set_log_callback(&gb, console_supported? log_callback : NULL); - if (captured_log[0] == 0) { - free(captured_log); - captured_log = NULL; + if (!previous) { + GB_set_log_callback(&gb, console_supported? log_callback : NULL); } - else { + if (captured_log[0] != 0) { if (show_popup) { SDL_ShowSimpleMessageBox(popup_flags, title, captured_log, window); } @@ -159,7 +156,8 @@ static const char *end_capturing_logs(bool show_popup, bool should_exit, uint32_ exit(1); } } - return captured_log; + free(captured_log); + captured_log = previous; } static void update_palette(void) @@ -620,7 +618,7 @@ static bool handle_pending_command(void) save_extension[2] += command_parameter; replace_extension(filename, strlen(filename), save_path, save_extension); - start_capturing_logs(); + void *previous = start_capturing_logs(); bool success; if (pending_command == GB_SDL_LOAD_STATE_COMMAND) { int result = GB_load_state(&gb, save_path); @@ -639,25 +637,28 @@ static bool handle_pending_command(void) end_capturing_logs(true, false, success? SDL_MESSAGEBOX_INFORMATION : SDL_MESSAGEBOX_ERROR, - success? "Notice" : "Error"); + success? "Notice" : "Error", + previous); if (success) { show_osd_text(pending_command == GB_SDL_LOAD_STATE_COMMAND? "State loaded" : "State saved"); } return false; } - case GB_SDL_LOAD_STATE_FROM_FILE_COMMAND: - start_capturing_logs(); + case GB_SDL_LOAD_STATE_FROM_FILE_COMMAND: { + void *previous = start_capturing_logs(); bool success = GB_load_state(&gb, dropped_state_file) == 0; end_capturing_logs(true, false, success? SDL_MESSAGEBOX_INFORMATION : SDL_MESSAGEBOX_ERROR, - success? "Notice" : "Error"); + success? "Notice" : "Error", + previous); SDL_free(dropped_state_file); if (success) { show_osd_text("State loaded"); } return false; + } case GB_SDL_NO_COMMAND: return false; @@ -697,18 +698,20 @@ static void load_boot_rom(GB_gameboy_t *gb, GB_boot_rom_t type) use_built_in = GB_load_boot_rom(gb, path); } if (use_built_in) { - start_capturing_logs(); + void *previous = start_capturing_logs(); if (GB_load_boot_rom(gb, resource_path(names[type]))) { if (type == GB_BOOT_ROM_CGB_E) { + end_capturing_logs(false, false, 0, NULL, previous); load_boot_rom(gb, GB_BOOT_ROM_CGB); return; } if (type == GB_BOOT_ROM_AGB_0) { + end_capturing_logs(false, false, 0, NULL, previous); load_boot_rom(gb, GB_BOOT_ROM_AGB); return; } } - end_capturing_logs(true, false, SDL_MESSAGEBOX_ERROR, "Error"); + end_capturing_logs(true, false, SDL_MESSAGEBOX_ERROR, "Error", previous); } } @@ -752,13 +755,34 @@ static void debugger_reload_callback(GB_gameboy_t *gb) GB_reset(gb); } -static void run(void) +static GB_model_t model_to_use(void) { - SDL_ShowCursor(SDL_DISABLE); - GB_model_t model; - pending_command = GB_SDL_NO_COMMAND; -restart: - model = (GB_model_t []) + typeof(configuration.model) gui_model = configuration.model; + if (gui_model == MODEL_AUTO) { + uint8_t *rom = GB_get_direct_access(&gb, GB_DIRECT_ACCESS_ROM, NULL, NULL); + if (!rom) { + gui_model = MODEL_CGB; + } + else if (rom[0x143] & 0x80) { // Has CGB features + gui_model = MODEL_CGB; + } + else if (rom[0x146] == 3) { // Has SGB features + gui_model = MODEL_SGB; + } + else if (rom[0x14B] == 1) { // Nintendo-licensed (most likely has boot ROM palettes) + gui_model = MODEL_CGB; + } + else if (rom[0x14B] == 0x33 && + rom[0x144] == '0' && + rom[0x145] == '1') { // Ditto + gui_model = MODEL_CGB; + } + else { + gui_model = MODEL_DMG; + } + } + + return (GB_model_t []) { [MODEL_DMG] = GB_MODEL_DMG_B, [MODEL_CGB] = GB_MODEL_CGB_0 + configuration.cgb_revision, @@ -770,7 +794,16 @@ restart: [SGB_PAL] = GB_MODEL_SGB_PAL, [SGB_2] = GB_MODEL_SGB2, }[configuration.sgb_revision], - }[configuration.model]; + }[gui_model]; +} + +static void run(void) +{ + SDL_ShowCursor(SDL_DISABLE); + GB_model_t model; + pending_command = GB_SDL_NO_COMMAND; +restart:; + model = model_to_use(); if (GB_is_inited(&gb)) { if (doing_hot_swap) { @@ -819,7 +852,7 @@ restart: bool error = false; GB_debugger_clear_symbols(&gb); - start_capturing_logs(); + void *previous = start_capturing_logs(); size_t path_length = strlen(filename); char extension[4] = {0,}; if (path_length > 4) { @@ -840,6 +873,11 @@ restart: else { GB_load_rom(&gb, filename); } + GB_model_t updated_model = model_to_use(); // Could change after loading ROM with auto setting + if (model != updated_model) { + model = updated_model; + GB_switch_model_and_reset(&gb, model); + } /* Configure battery */ char battery_save_path[path_length + 5]; /* At the worst case, size is strlen(path) + 4 bytes for .sav + NULL */ @@ -856,7 +894,7 @@ restart: replace_extension(filename, path_length, cheat_path, ".cht"); GB_load_cheats(&gb, cheat_path); - end_capturing_logs(true, error, SDL_MESSAGEBOX_WARNING, "Warning"); + end_capturing_logs(true, error, SDL_MESSAGEBOX_WARNING, "Warning", previous); static char start_text[64]; static char title[17]; @@ -958,14 +996,15 @@ static void handle_model_option(const char *model_string) GB_model_t model; const char *description; } name_to_model[] = { + {"auto", -1, "Pick automatically"}, {"dmg-b", GB_MODEL_DMG_B, "Game Boy, DMG-CPU B"}, {"dmg", GB_MODEL_DMG_B, "Alias of dmg-b"}, {"sgb-ntsc", GB_MODEL_SGB_NTSC, "Super Game Boy (NTSC)"}, - {"sgb-pal", GB_MODEL_SGB_PAL, "Super Game Boy (PAL"}, + {"sgb-pal", GB_MODEL_SGB_PAL, "Super Game Boy (PAL)"}, {"sgb2", GB_MODEL_SGB2, "Super Game Boy 2"}, {"sgb", GB_MODEL_SGB, "Alias of sgb-ntsc"}, {"mgb", GB_MODEL_MGB, "Game Boy Pocket/Light"}, - {"cgb-0", GB_MODEL_CGB_0, "Game Boy Color, CPU CGB 0"}, + {"cgb-0", GB_MODEL_CGB_0, "Game Boy Color, CPU CGB"}, {"cgb-a", GB_MODEL_CGB_A, "Game Boy Color, CPU CGB A"}, {"cgb-b", GB_MODEL_CGB_B, "Game Boy Color, CPU CGB B"}, {"cgb-c", GB_MODEL_CGB_C, "Game Boy Color, CPU CGB C"}, @@ -1028,6 +1067,7 @@ static void handle_model_option(const char *model_string) break; default: + configuration.model = MODEL_AUTO; break; } } @@ -1093,7 +1133,6 @@ int main(int argc, char **argv) configuration.default_scale %= GB_SDL_DEFAULT_SCALE_MAX + 1; configuration.blending_mode %= GB_FRAME_BLENDING_MODE_ACCURATE + 1; configuration.highpass_mode %= GB_HIGHPASS_MAX; - configuration.model %= MODEL_MAX; configuration.sgb_revision %= SGB_MAX; configuration.dmg_palette %= 5; if (configuration.dmg_palette) {