From 296e81a8a35110ca56bdd9a5e9ebf1bbc927e4b6 Mon Sep 17 00:00:00 2001 From: Maximilian Mader Date: Wed, 15 Apr 2026 16:35:54 +0200 Subject: [PATCH] HW/GBPlayer: Try to load CGB boot ROM for (C)GB games If the Game Boy Player boots a Game Boy or Game Boy Color game we try to load a boot ROM now. The mGBA core configuration has also been updated to reflect that the Game Boy Player is based on a Game Boy Advance SoC and not a Game Boy Color. This means that (C)GB games can detect that they are running on a Game Boy Advance now. --- .../features/settings/model/StringSetting.kt | 1 + .../settings/ui/SettingsFragmentPresenter.kt | 10 ++++ .../app/src/main/res/values/strings.xml | 1 + Source/Core/Common/CommonPaths.h | 1 + Source/Core/Common/FileUtil.cpp | 1 + Source/Core/Common/FileUtil.h | 1 + Source/Core/Core/Config/MainSettings.cpp | 1 + Source/Core/Core/Config/MainSettings.h | 1 + Source/Core/Core/HW/GBACore.cpp | 60 +++++++++++++++++-- Source/Core/Core/HW/GBACore.h | 1 + .../Core/DolphinQt/Settings/GameCubePane.cpp | 21 +++++++ Source/Core/DolphinQt/Settings/GameCubePane.h | 3 + Source/Core/UICommon/UICommon.cpp | 1 + 13 files changed, 99 insertions(+), 4 deletions(-) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt index 512a33ddbb..a38bb3db16 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt @@ -70,6 +70,7 @@ enum class StringSetting( ), MAIN_WFS_PATH(Settings.FILE_DOLPHIN, Settings.SECTION_INI_GENERAL, "WFSPath", ""), MAIN_GBA_BIOS_PATH(Settings.FILE_DOLPHIN, Settings.SECTION_INI_GBA, "BIOS", ""), + MAIN_GBA_CGB_BOOT_ROM_PATH(Settings.FILE_DOLPHIN, Settings.SECTION_INI_GBA, "CGBBootROM", ""), MAIN_GB_PLAYER_ROM(Settings.FILE_DOLPHIN, Settings.SECTION_INI_GBA, "GBPlayerRom", ""), MAIN_GBA_SAVES_PATH(Settings.FILE_DOLPHIN, Settings.SECTION_INI_GBA, "SavesPath", ""), MAIN_TRIFORCE_IP_REDIRECTIONS( diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt index 32a7a64025..f7cd4e5f1a 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt @@ -714,6 +714,16 @@ class SettingsFragmentPresenter( "/GBA/gba_bios.bin" ) ) + sl.add( + FilePicker( + context, + StringSetting.MAIN_GBA_CGB_BOOT_ROM_PATH, + R.string.gba_cgb_boot_rom_path, + 0, + fragmentView.activityResultLaunchers.requestBinFile, + "/GBA/cgb_agb_boot.bin" + ) + ) sl.add( FilePicker( context, diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml index 04c48bcba4..3cb4d92feb 100644 --- a/Source/Android/app/src/main/res/values/strings.xml +++ b/Source/Android/app/src/main/res/values/strings.xml @@ -87,6 +87,7 @@ GameCube Serial Port 1 Device GBA Settings BIOS + Game Boy Color Boot ROM Game Boy Player ROM Saves Wii diff --git a/Source/Core/Common/CommonPaths.h b/Source/Core/Common/CommonPaths.h index 474890c4d8..e09115718c 100644 --- a/Source/Core/Common/CommonPaths.h +++ b/Source/Core/Common/CommonPaths.h @@ -143,6 +143,7 @@ #define GC_MEMCARD_NETPLAY "NetPlayTemp" #define GBA_BIOS "gba_bios.bin" +#define GBA_CGB_BOOT_ROM "cgb_agb_boot.bin" #define GBA_SAVE_NETPLAY "NetPlayTemp" #define WII_STATE "state.dat" diff --git a/Source/Core/Common/FileUtil.cpp b/Source/Core/Common/FileUtil.cpp index 9f17a58675..8037139c0a 100644 --- a/Source/Core/Common/FileUtil.cpp +++ b/Source/Core/Common/FileUtil.cpp @@ -908,6 +908,7 @@ static void RebuildUserDirectories(unsigned int dir_index) s_user_paths[D_GBAUSER_IDX] = s_user_paths[D_USER_IDX] + GBA_USER_DIR DIR_SEP; s_user_paths[D_GBASAVES_IDX] = s_user_paths[D_GBAUSER_IDX] + GBASAVES_DIR DIR_SEP; s_user_paths[F_GBABIOS_IDX] = s_user_paths[D_GBAUSER_IDX] + GBA_BIOS; + s_user_paths[F_GBACGBBOOTROM_IDX] = s_user_paths[D_GBAUSER_IDX] + GBA_CGB_BOOT_ROM; s_user_paths[D_ASM_ROOT_IDX] = s_user_paths[D_USER_IDX] + ASSEMBLY_DIR DIR_SEP; diff --git a/Source/Core/Common/FileUtil.h b/Source/Core/Common/FileUtil.h index d5c8285305..3b7a7ce501 100644 --- a/Source/Core/Common/FileUtil.h +++ b/Source/Core/Common/FileUtil.h @@ -98,6 +98,7 @@ enum F_FREELOOKCONFIG_IDX, F_GBABIOS_IDX, F_RETROACHIEVEMENTSCONFIG_IDX, + F_GBACGBBOOTROM_IDX, NUM_PATH_INDICES }; diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index 24e2c19571..be60aad932 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -398,6 +398,7 @@ void SetIsoPaths(std::span paths) #ifdef HAS_LIBMGBA const Info MAIN_GBA_BIOS_PATH{{System::Main, "GBA", "BIOS"}, ""}; +const Info MAIN_GBA_CGB_BOOT_ROM_PATH{{System::Main, "GBA", "CGBBootROM"}, ""}; const std::array, 5> MAIN_GBA_ROM_PATHS{ Info{{System::Main, "GBA", "Rom1"}, ""}, Info{{System::Main, "GBA", "Rom2"}, ""}, diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h index 22c87ba110..1ed01650ff 100644 --- a/Source/Core/Core/Config/MainSettings.h +++ b/Source/Core/Core/Config/MainSettings.h @@ -224,6 +224,7 @@ void SetIsoPaths(std::span paths); #ifdef HAS_LIBMGBA extern const Info MAIN_GBA_BIOS_PATH; +extern const Info MAIN_GBA_CGB_BOOT_ROM_PATH; extern const std::array, 5> MAIN_GBA_ROM_PATHS; extern const Info MAIN_GBA_SAVES_PATH; extern const Info MAIN_GBA_SAVES_IN_ROM_PATH; diff --git a/Source/Core/Core/HW/GBACore.cpp b/Source/Core/Core/HW/GBACore.cpp index b67b8a45c0..3d2d3f21ea 100644 --- a/Source/Core/Core/HW/GBACore.cpp +++ b/Source/Core/Core/HW/GBACore.cpp @@ -218,12 +218,21 @@ bool Core::Start(u64 gc_ticks) mCoreConfigSetIntValue(&m_core->config, "useBios", 0); mCoreConfigSetIntValue(&m_core->config, "skipBios", 0); - // If we eventually load GBC BIOS, then these should potentially all be "CGB". + // The official GBC/GBA bootstrap ROM will detect a non-"GBC compatible" ROM, load palettes as + // desired, and switch into GB mode. If forced into GBC/GBA mode without a bootstrap ROM, mGBA + // will not perform this step. Instead, we set gb.colors to inform it to load the palette and set + // it directly to DMG mode in advance. However, if a GBC or GBA bootstrap ROM is loaded, we want + // to stay in GBC/GBA mode as appropriate so the bootstrap ROM can take care of it itself and we + // show the right boot splash. + // Note that even when using a GBC bootstrap ROM and not the one from + // an actual GBA, the system will appear as GBA to games because mGBA fixes the B register when it + // unloads the bootstrap ROM, so features such as Oracle's "Advance Shop" will be available. + // See `GBUnmapBIOS` in mGBA’s `src/gb/gb.c`. mCoreConfigSetValue(&m_core->config, "gb.model", "DMG"); mCoreConfigSetValue(&m_core->config, "sgb.model", "DMG"); - mCoreConfigSetValue(&m_core->config, "cgb.model", "CGB"); - mCoreConfigSetValue(&m_core->config, "cgb.hybridModel", "CGB"); - mCoreConfigSetValue(&m_core->config, "cgb.sgbModel", "CGB"); + mCoreConfigSetValue(&m_core->config, "cgb.model", "AGB"); + mCoreConfigSetValue(&m_core->config, "cgb.hybridModel", "AGB"); + mCoreConfigSetValue(&m_core->config, "cgb.sgbModel", "AGB"); mCoreConfigSetIntValue(&m_core->config, "gb.colors", GB_COLORS_CGB); @@ -231,6 +240,10 @@ bool Core::Start(u64 gc_ticks) { LoadBIOS(File::GetUserPath(F_GBABIOS_IDX).c_str()); } + else if (m_core->platform(m_core) == mPLATFORM_GB) + { + LoadCGBBootROM(File::GetUserPath(F_GBACGBBOOTROM_IDX).c_str()); + } if (rom) { @@ -371,6 +384,45 @@ bool Core::LoadBIOS(const char* bios_path) return true; } +bool Core::LoadCGBBootROM(const char* boot_rom_path) +{ + VFile* vf = OpenReadOnlyFile(boot_rom_path); + if (!vf) + { + ERROR_LOG_FMT(CORE, "GBA{0} failed to open the Game Boy Color boot ROM in {1}", + m_device_number + 1, boot_rom_path); + return false; + } + + if (!GBIsCompatibleBIOS(vf, GB_MODEL_AGB)) + { + ERROR_LOG_FMT(CORE, "The provided Game Boy Color boot ROM in {1} is not compatible with GBA{0}", + m_device_number + 1, boot_rom_path); + vf->close(vf); + return false; + } + + // When a compatible boot ROM is provided, nothing should break if mGBA is set to run as GBA + // for non-CGB games + mCoreConfigSetValue(&m_core->config, "gb.model", "AGB"); + mCoreConfigSetValue(&m_core->config, "sgb.model", "AGB"); + + if (!m_core->loadBIOS(m_core, vf, 0)) + { + ERROR_LOG_FMT(CORE, "GBA{0} failed to load the GBC Boot ROM in {1}", m_device_number + 1, + boot_rom_path); + vf->close(vf); + + // Reset the pre-CGB model config to be safe + mCoreConfigSetValue(&m_core->config, "gb.model", "DMG"); + mCoreConfigSetValue(&m_core->config, "sgb.model", "DMG"); + + return false; + } + + return true; +} + bool Core::LoadSave(const char* save_path) { VFile* vf = VFileOpen(save_path, O_CREAT | O_RDWR); diff --git a/Source/Core/Core/HW/GBACore.h b/Source/Core/Core/HW/GBACore.h index 6b1934f71c..8d75860900 100644 --- a/Source/Core/Core/HW/GBACore.h +++ b/Source/Core/Core/HW/GBACore.h @@ -123,6 +123,7 @@ private: void HandleEvent(SyncEvent event); bool LoadBIOS(const char* bios_path); + bool LoadCGBBootROM(const char* boot_rom_path); bool LoadSave(const char* save_path); void SetSIODriver(); diff --git a/Source/Core/DolphinQt/Settings/GameCubePane.cpp b/Source/Core/DolphinQt/Settings/GameCubePane.cpp index e6d0db14b5..a22d42b6e9 100644 --- a/Source/Core/DolphinQt/Settings/GameCubePane.cpp +++ b/Source/Core/DolphinQt/Settings/GameCubePane.cpp @@ -211,6 +211,15 @@ void GameCubePane::CreateWidgets() gba_layout->addWidget(m_gba_browse_bios, gba_row, 2); gba_row++; + m_gba_cgb_boot_rom_edit = + new ConfigUserPath(F_GBACGBBOOTROM_IDX, Config::MAIN_GBA_CGB_BOOT_ROM_PATH); + m_gba_browse_cgb_boot_rom = new NonDefaultQPushButton(QStringLiteral("...")); + + gba_layout->addWidget(new QLabel(tr("Game Boy Color Boot ROM:")), gba_row, 0); + gba_layout->addWidget(m_gba_cgb_boot_rom_edit, gba_row, 1); + gba_layout->addWidget(m_gba_browse_cgb_boot_rom, gba_row, 2); + gba_row++; + for (size_t i = 0; i < m_gba_rom_edits.size(); ++i) { m_gba_rom_edits[i] = new ConfigText(Config::MAIN_GBA_ROM_PATHS[i]); @@ -279,6 +288,8 @@ void GameCubePane::ConnectWidgets() #ifdef HAS_LIBMGBA // GBA Settings connect(m_gba_browse_bios, &QPushButton::clicked, this, &GameCubePane::BrowseGBABios); + connect(m_gba_browse_cgb_boot_rom, &QPushButton::clicked, this, + &GameCubePane::BrowseGBACGBBootRom); #if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) connect(m_gba_save_rom_path, &QCheckBox::checkStateChanged, this, &GameCubePane::SaveRomPathChanged); @@ -696,6 +707,16 @@ void GameCubePane::BrowseGBABios() m_gba_bios_edit->SetTextAndUpdate(file); } +void GameCubePane::BrowseGBACGBBootRom() +{ + QString file = QDir::toNativeSeparators(DolphinFileDialog::getOpenFileName( + this, tr("Select GBC boot ROM"), + QString::fromStdString(Config::Get(Config::MAIN_GBA_CGB_BOOT_ROM_PATH)), + tr("All Files (*)"))); + if (!file.isEmpty()) + m_gba_cgb_boot_rom_edit->SetTextAndUpdate(file); +} + void GameCubePane::BrowseGBARom(size_t index) { QString file = QString::fromStdString(GetOpenGBARom({})); diff --git a/Source/Core/DolphinQt/Settings/GameCubePane.h b/Source/Core/DolphinQt/Settings/GameCubePane.h index 947ee7342c..7fc019a843 100644 --- a/Source/Core/DolphinQt/Settings/GameCubePane.h +++ b/Source/Core/DolphinQt/Settings/GameCubePane.h @@ -57,6 +57,7 @@ private: #ifdef HAS_LIBMGBA void BrowseGBABios(); + void BrowseGBACGBBootRom(); void BrowseGBARom(size_t index); void SaveRomPathChanged(); void BrowseGBASaves(); @@ -84,6 +85,8 @@ private: ConfigBool* m_gba_save_rom_path; QPushButton* m_gba_browse_bios; ConfigUserPath* m_gba_bios_edit; + QPushButton* m_gba_browse_cgb_boot_rom; + ConfigUserPath* m_gba_cgb_boot_rom_edit; std::array m_gba_browse_roms; std::array m_gba_rom_edits; QPushButton* m_gba_browse_saves; diff --git a/Source/Core/UICommon/UICommon.cpp b/Source/Core/UICommon/UICommon.cpp index 51e0798463..81fd84c145 100644 --- a/Source/Core/UICommon/UICommon.cpp +++ b/Source/Core/UICommon/UICommon.cpp @@ -112,6 +112,7 @@ static void InitCustomPaths() File::CreateFullPath(File::GetUserPath(D_WIISDCARDSYNCFOLDER_IDX)); #ifdef HAS_LIBMGBA File::SetUserPath(F_GBABIOS_IDX, Config::Get(Config::MAIN_GBA_BIOS_PATH)); + File::SetUserPath(F_GBACGBBOOTROM_IDX, Config::Get(Config::MAIN_GBA_CGB_BOOT_ROM_PATH)); File::SetUserPath(D_GBASAVES_IDX, Config::Get(Config::MAIN_GBA_SAVES_PATH)); File::CreateFullPath(File::GetUserPath(D_GBASAVES_IDX)); #endif