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