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.
This commit is contained in:
Maximilian Mader 2026-04-15 16:35:54 +02:00
parent 914f5c5621
commit 296e81a8a3
No known key found for this signature in database
GPG Key ID: F71D56A3151C4FB3
13 changed files with 99 additions and 4 deletions

View File

@ -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(

View File

@ -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,

View File

@ -87,6 +87,7 @@
<string name="serial_port_1_device">GameCube Serial Port 1 Device</string>
<string name="gba_settings">GBA Settings</string>
<string name="gba_bios_path">BIOS</string>
<string name="gba_cgb_boot_rom_path">Game Boy Color Boot ROM</string>
<string name="gb_player_rom">Game Boy Player ROM</string>
<string name="gba_saves_path">Saves</string>
<string name="wii_submenu">Wii</string>

View File

@ -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"

View File

@ -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;

View File

@ -98,6 +98,7 @@ enum
F_FREELOOKCONFIG_IDX,
F_GBABIOS_IDX,
F_RETROACHIEVEMENTSCONFIG_IDX,
F_GBACGBBOOTROM_IDX,
NUM_PATH_INDICES
};

View File

@ -398,6 +398,7 @@ void SetIsoPaths(std::span<const std::string> paths)
#ifdef HAS_LIBMGBA
const Info<std::string> MAIN_GBA_BIOS_PATH{{System::Main, "GBA", "BIOS"}, ""};
const Info<std::string> MAIN_GBA_CGB_BOOT_ROM_PATH{{System::Main, "GBA", "CGBBootROM"}, ""};
const std::array<Info<std::string>, 5> MAIN_GBA_ROM_PATHS{
Info<std::string>{{System::Main, "GBA", "Rom1"}, ""},
Info<std::string>{{System::Main, "GBA", "Rom2"}, ""},

View File

@ -224,6 +224,7 @@ void SetIsoPaths(std::span<const std::string> paths);
#ifdef HAS_LIBMGBA
extern const Info<std::string> MAIN_GBA_BIOS_PATH;
extern const Info<std::string> MAIN_GBA_CGB_BOOT_ROM_PATH;
extern const std::array<Info<std::string>, 5> MAIN_GBA_ROM_PATHS;
extern const Info<std::string> MAIN_GBA_SAVES_PATH;
extern const Info<bool> MAIN_GBA_SAVES_IN_ROM_PATH;

View File

@ -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 mGBAs `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);

View File

@ -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();

View File

@ -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({}));

View File

@ -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<QPushButton*, 5> m_gba_browse_roms;
std::array<ConfigText*, 5> m_gba_rom_edits;
QPushButton* m_gba_browse_saves;

View File

@ -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