System: Add option to install BIOS when missing

pull/3782/head
Stenzek 2 months ago
parent f1029b2df2
commit f11b43227a
No known key found for this signature in database

@ -348,7 +348,7 @@ DiscRegion BIOS::GetPSExeDiscRegion(const PSEXEHeader& header)
return DiscRegion::Other;
}
std::optional<BIOS::Image> BIOS::GetBIOSImage(ConsoleRegion region, Error* error)
std::optional<BIOS::Image> BIOS::GetBIOSImage(ConsoleRegion region, bool* using_auto_select, Error* error)
{
std::string bios_name;
switch (region)
@ -369,7 +369,10 @@ std::optional<BIOS::Image> BIOS::GetBIOSImage(ConsoleRegion region, Error* error
std::optional<Image> image;
if (bios_name.empty())
const bool auto_select = bios_name.empty();
if (using_auto_select)
*using_auto_select = auto_select;
if (auto_select)
{
// auto-detect
image = FindBIOSImageInDirectory(region, EmuFolders::Bios.c_str(), error);
@ -436,18 +439,13 @@ std::optional<BIOS::Image> BIOS::FindBIOSImageInDirectory(ConsoleRegion region,
if (Error::IsValid(error))
Error::AddSuffix(error, "\n\n");
#ifndef __ANDROID__
Error::AddSuffixFmt(
error,
TRANSLATE_FS("System", "No BIOS image found for {} region.\n\nDuckStation requires a PS1 or PS2 BIOS in order to "
"run.\n\nFor legal reasons, you *must* obtain a BIOS from an actual PS1 unit that you own "
"(borrowing doesn't count).\n\nOnce dumped, this BIOS image should be placed in the bios "
"folder within the data directory (Tools Menu -> Open Data Directory)."),
TRANSLATE_FS(
"System",
"No BIOS image found for {} region.\n\nDuckStation requires a PS1 or PS2 BIOS in order to run.\n\nFor legal "
"reasons, you must obtain a BIOS from an actual PS1/PS2 unit that you own (borrowing doesn't count)."),
Settings::GetConsoleRegionName(region));
#else
Error::AddSuffixFmt(error, TRANSLATE_FS("System", "No BIOS image found for {} region."),
Settings::GetConsoleRegionName(region));
#endif
return image;
}

@ -96,7 +96,7 @@ bool IsValidPSExeHeader(const PSEXEHeader& header, size_t file_size);
DiscRegion GetPSExeDiscRegion(const PSEXEHeader& header);
/// Loads the BIOS image for the specified region.
std::optional<Image> GetBIOSImage(ConsoleRegion region, Error* error);
std::optional<Image> GetBIOSImage(ConsoleRegion region, bool* using_auto_select, Error* error);
/// Searches for a BIOS image for the specified region in the specified directory. If no match is found, the first
/// BIOS image within 512KB and 4MB will be used.

@ -705,14 +705,29 @@ void FullscreenUI::DoStartPath(std::string path, std::string state, std::optiona
// This can "fail" if HC mode is enabled and the user cancels, or other startup cancel paths.
Error error;
if (!System::BootSystem(std::move(params), &error))
if (const System::BootResult result = System::BootSystem(std::move(params), &error);
result != System::BootResult::Success)
{
VideoThread::RunOnThread([error_desc = error.TakeDescription()]() {
VideoThread::RunOnThread([error_desc = error.TakeDescription(), result]() {
if (!IsInitialized())
return;
OpenInfoMessageDialog(ICON_EMOJI_NO_ENTRY_SIGN, TRANSLATE_STR("System", "Error"),
fmt::format(TRANSLATE_FS("System", "Failed to boot system: {}"), error_desc));
std::string title = TRANSLATE_STR("System", "Error");
std::string message = fmt::format("{}\n\n{}", TRANSLATE_SV("System", "Failed to boot system:"), error_desc);
if (result == System::BootResult::MissingBIOS)
{
fmt::format_to(std::back_inserter(message), "\n\n{}",
TRANSLATE_SV("System", "Do you want to install a BIOS file now?"));
OpenConfirmMessageDialog(ICON_EMOJI_NO_ENTRY_SIGN, std::move(title), std::move(message), [](bool result) {
if (result)
StartInstallBIOS();
});
}
else
{
OpenInfoMessageDialog(ICON_EMOJI_NO_ENTRY_SIGN, std::move(title), std::move(message));
}
ClearSaveStateEntryList();
UpdateRunIdleState();
});

@ -115,6 +115,7 @@ void SwitchToSettings(SettingsPage page = SettingsPage::Interface);
bool SwitchToGameSettings(SettingsPage page = SettingsPage::Summary);
void SwitchToGameSettings(const GameList::Entry* entry, SettingsPage page = SettingsPage::Summary);
bool SwitchToGameSettingsForPath(const std::string& path, SettingsPage page = SettingsPage::Summary);
void StartInstallBIOS();
void DrawSettingsWindow();
SettingsPage GetCurrentSettingsPage();
bool IsInputBindingDialogOpen();

@ -3183,10 +3183,7 @@ void FullscreenUI::DrawBIOSSettingsPage()
if (MenuButton(FSUI_ICONVSTR(ICON_FA_FILE_IMPORT, "Install BIOS"),
FSUI_VSTR("Copies a BIOS image to the configured global BIOS directory.")))
{
OpenFileSelector(FSUI_ICONVSTR(ICON_FA_FILE_IMPORT, "Select BIOS Image"), {}, {}, [](std::string path) {
if (!path.empty())
InstallBIOS(std::move(path), false);
});
StartInstallBIOS();
}
DrawFolderSetting(bsi, FSUI_ICONVSTR(ICON_FA_FOLDER, "BIOS Directory"), "BIOS", "SearchDirectory", EmuFolders::Bios);
@ -3198,6 +3195,14 @@ void FullscreenUI::DrawBIOSSettingsPage()
EndMenuButtons();
}
void FullscreenUI::StartInstallBIOS()
{
OpenFileSelector(FSUI_ICONVSTR(ICON_FA_FILE_IMPORT, "Select BIOS Image"), {}, {}, [](std::string path) {
if (!path.empty())
InstallBIOS(std::move(path), false);
});
}
void FullscreenUI::InstallBIOS(std::string source_path, bool replace)
{
Error error;

@ -158,8 +158,8 @@ static void LogUnsafeSettingsToConsole(const SmallStringBase& messages);
static bool Initialize(std::unique_ptr<CDImage> disc, DiscRegion disc_region, bool force_software_renderer,
std::optional<bool> start_fullscreen, Error* error);
static bool LoadBIOS(Error* error);
static bool SetBootMode(BootMode new_boot_mode, DiscRegion disc_region, Error* error);
static bool LoadBIOS(bool* using_auto_select, Error* error);
static bool SetBootMode(BootMode new_boot_mode, DiscRegion disc_region, bool* missing_bios, Error* error);
static void InternalReset();
static void ClearRunningGame();
static void DestroySystem();
@ -1447,7 +1447,7 @@ void System::ResetSystem()
const BootMode new_boot_mode = (s_state.boot_mode == BootMode::BootEXE || s_state.boot_mode == BootMode::BootPSF) ?
s_state.boot_mode :
(g_settings.bios_patch_fast_boot ? BootMode::FastBoot : BootMode::FullBoot);
if (Error error; !SetBootMode(new_boot_mode, CDROM::GetDiscRegion(), &error))
if (Error error; !SetBootMode(new_boot_mode, CDROM::GetDiscRegion(), nullptr, &error))
ERROR_LOG("Failed to reload BIOS on boot mode change, the system may be unstable: {}", error.GetDescription());
// Have to turn on turbo if fast forwarding boot.
@ -1544,7 +1544,7 @@ bool System::SaveResumeState(Error* error)
return SaveState(std::move(path), error, false, true);
}
bool System::BootSystem(SystemBootParameters parameters, Error* error)
System::BootResult System::BootSystem(SystemBootParameters parameters, Error* error)
{
Timer boot_timer;
@ -1584,7 +1584,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
{
gpu_dump = GPUDump::Player::Open(parameters.path, error);
if (!gpu_dump)
return false;
return BootResult::Failure;
boot_mode = BootMode::ReplayGPUDump;
}
@ -1603,7 +1603,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
if (!disc)
{
Error::AddPrefixFmt(error, "Failed to open CD image '{}':\n", Path::GetFileName(parameters.path));
return false;
return BootResult::Failure;
}
disc_region = GameList::GetCustomRegionForPath(parameters.path).value_or(GetRegionForImage(disc.get()));
@ -1619,7 +1619,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
{
Error::AddPrefixFmt(error, "Failed to switch to subimage {} in '{}':\n", parameters.media_playlist_index,
Path::GetFileName(parameters.path));
return false;
return BootResult::Failure;
}
// Can't early cancel without destroying past this point.
@ -1648,7 +1648,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
Path::GetFileName(parameters.override_exe));
Host::OnSystemStopping();
DestroySystem();
return false;
return BootResult::Failure;
}
INFO_LOG("Overriding boot executable: '{}'", parameters.override_exe);
@ -1676,7 +1676,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
// Technically a failure, but user-initiated. Returning false here would try to display a non-existent error.
Host::OnSystemStopping();
DestroySystem();
return true;
return BootResult::Success;
}
}
@ -1687,23 +1687,30 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
boot_mode = BootMode::FastBoot;
}
// Load BIOS image, component setup, check for subchannel in games that need it.
// Load BIOS image.
const std::optional<bool> start_fullscreen = parameters.override_fullscreen.has_value() ?
std::optional<bool>(parameters.override_fullscreen.value()) :
(ShouldStartFullscreen() ? std::optional<bool>(true) : std::nullopt);
if (!SetBootMode(boot_mode, disc_region, error) ||
!Initialize(std::move(disc), disc_region, parameters.force_software_renderer, start_fullscreen, error))
if (bool missing_bios = false; !SetBootMode(boot_mode, disc_region, &missing_bios, error))
{
Host::OnSystemStopping();
DestroySystem();
return false;
return missing_bios ? BootResult::MissingBIOS : BootResult::Failure;
}
// Component setup.
if (!Initialize(std::move(disc), disc_region, parameters.force_software_renderer, start_fullscreen, error))
{
Host::OnSystemStopping();
DestroySystem();
return BootResult::Failure;
}
// Check for required subchannel data.
// Annoyingly we can't do this before initializing, because subchannel data can be loaded from outside the image.
if (!parameters.ignore_missing_subchannel && !CheckForRequiredSubQ(error))
{
bool result = false;
BootResult result = BootResult::Failure;
if (Core::GetBoolSettingValue("CDROM", "AllowBootingWithoutSBIFile", false))
{
Host::ConfirmMessageAsync(
@ -1723,7 +1730,8 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
}
});
result = true;
// Prevent this boot from going through, but don't treat it as a failure. The user can choose to continue or not.
result = BootResult::Success;
}
Host::OnSystemStopping();
@ -1753,7 +1761,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
Path::GetFileName(parameters.save_state));
Host::OnSystemStopping();
DestroySystem();
return false;
return BootResult::Failure;
}
InputManager::UpdateHostMouseMode();
@ -1784,7 +1792,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
INFO_LOG("System booted in {:.2f}ms", boot_timer.GetTimeMilliseconds());
PerformanceCounters::Reset();
ResetThrottler();
return true;
return BootResult::Success;
}
bool System::Initialize(std::unique_ptr<CDImage> disc, DiscRegion disc_region, bool force_software_renderer,
@ -2666,9 +2674,9 @@ void System::DoMemoryState(StateWrapper& sw, MemorySaveState& mss, bool update_d
GPU::UpdateDisplay(false);
}
bool System::LoadBIOS(Error* error)
bool System::LoadBIOS(bool* using_auto_select, Error* error)
{
std::optional<BIOS::Image> bios_image = BIOS::GetBIOSImage(s_state.region, error);
std::optional<BIOS::Image> bios_image = BIOS::GetBIOSImage(s_state.region, using_auto_select, error);
if (!bios_image.has_value())
return false;
@ -2720,7 +2728,7 @@ void System::InternalReset()
s_state.internal_frame_number = 0;
}
bool System::SetBootMode(BootMode new_boot_mode, DiscRegion disc_region, Error* error)
bool System::SetBootMode(BootMode new_boot_mode, DiscRegion disc_region, bool* missing_bios, Error* error)
{
// Can we actually fast boot? If starting, s_bios_image_info won't be valid.
const bool can_fast_boot =
@ -2735,7 +2743,7 @@ bool System::SetBootMode(BootMode new_boot_mode, DiscRegion disc_region, Error*
return true;
// Need to reload the BIOS to wipe out the patching.
if (new_boot_mode != BootMode::ReplayGPUDump && !LoadBIOS(error))
if (new_boot_mode != BootMode::ReplayGPUDump && !LoadBIOS(missing_bios, error))
return false;
// Handle the case of BIOSes not being able to full boot.

@ -117,6 +117,13 @@ enum class BootMode : u8
ReplayGPUDump,
};
enum class BootResult : u8
{
Success,
Failure,
MissingBIOS,
};
enum class Taint : u8
{
CPUOverclock,
@ -276,7 +283,7 @@ void ReloadInputBindings();
/// Reloads only controller settings.
void UpdateControllerSettings();
bool BootSystem(SystemBootParameters parameters, Error* error);
BootResult BootSystem(SystemBootParameters parameters, Error* error);
void PauseSystem(bool paused);
void ResetSystem();

@ -3,6 +3,7 @@
#include "qthost.h"
#include "autoupdaterdialog.h"
#include "biossettingswidget.h"
#include "displaywidget.h"
#include "logwindow.h"
#include "mainwindow.h"
@ -1089,10 +1090,29 @@ void CoreThread::bootSystem(std::shared_ptr<SystemBootParameters> params)
return;
Error error;
if (!System::BootSystem(std::move(*params), &error))
{
emit errorReported(tr("Error"),
tr("Failed to boot system: %1").arg(QString::fromStdString(error.GetDescription())));
if (const System::BootResult result = System::BootSystem(std::move(*params), &error);
result != System::BootResult::Success)
{
std::string message =
fmt::format("{}\n\n{}", TRANSLATE_SV("System", "Failed to boot system:"), error.GetDescription());
Host::RunOnUIThread([message = QString::fromStdString(message), result]() {
const QString title = QCoreApplication::translate("System", "Error");
if (result == System::BootResult::MissingBIOS)
{
QMessageBox* const mbox = QtUtils::NewMessageBox(
g_main_window, QMessageBox::Critical, title,
QStringLiteral("%1\n\n%2")
.arg(message)
.arg(QCoreApplication::translate("System", "Do you want to install a BIOS file now?")),
QMessageBox::Yes | QMessageBox::No);
connect(mbox, &QMessageBox::accepted, g_main_window, []() { BIOSSettingsWidget::installBIOS(g_main_window); });
mbox->open();
}
else
{
QtUtils::AsyncMessageBox(g_main_window, QMessageBox::Critical, title, message);
}
});
}
}

Loading…
Cancel
Save