System: Fix triple popup on missing BIOS

This commit is contained in:
Stenzek 2024-05-12 23:58:36 +10:00
parent be920acf38
commit 318fd0b0fd
No known key found for this signature in database
3 changed files with 75 additions and 82 deletions

View file

@ -1,16 +1,20 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com> and contributors. // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com> and contributors.
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "bios.h" #include "bios.h"
#include "cpu_disasm.h"
#include "host.h"
#include "settings.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/error.h"
#include "common/file_system.h" #include "common/file_system.h"
#include "common/log.h" #include "common/log.h"
#include "common/md5_digest.h" #include "common/md5_digest.h"
#include "common/path.h" #include "common/path.h"
#include "cpu_disasm.h"
#include "host.h"
#include "settings.h"
#include <cerrno> #include <cerrno>
Log_SetChannel(BIOS); Log_SetChannel(BIOS);
static constexpr BIOS::Hash MakeHashFromString(const char str[]) static constexpr BIOS::Hash MakeHashFromString(const char str[])
@ -164,13 +168,13 @@ BIOS::Hash BIOS::GetImageHash(const BIOS::Image& image)
return hash; return hash;
} }
std::optional<BIOS::Image> BIOS::LoadImageFromFile(const char* filename) std::optional<BIOS::Image> BIOS::LoadImageFromFile(const char* filename, Error* error)
{ {
Image ret(BIOS_SIZE); Image ret(BIOS_SIZE);
auto fp = FileSystem::OpenManagedCFile(filename, "rb"); auto fp = FileSystem::OpenManagedCFile(filename, "rb", error);
if (!fp) if (!fp)
{ {
Log_ErrorPrintf("Failed to open BIOS image '%s', errno=%d", filename, errno); Error::AddPrefixFmt(error, "Failed to open BIOS '{}': ", Path::GetFileName(filename));
return std::nullopt; return std::nullopt;
} }
@ -180,14 +184,15 @@ std::optional<BIOS::Image> BIOS::LoadImageFromFile(const char* filename)
if (size != BIOS_SIZE && size != BIOS_SIZE_PS2 && size != BIOS_SIZE_PS3) if (size != BIOS_SIZE && size != BIOS_SIZE_PS2 && size != BIOS_SIZE_PS3)
{ {
Log_ErrorPrintf("BIOS image '%s' size mismatch, expecting either %u or %u or %u bytes but got %u bytes", filename, Error::SetStringFmt(error, "BIOS image '{}' size mismatch, expecting either {} or {} bytes but got {} bytes",
BIOS_SIZE, BIOS_SIZE_PS2, BIOS_SIZE_PS3, size); Path::GetFileName(filename), static_cast<unsigned>(BIOS_SIZE),
static_cast<unsigned>(BIOS_SIZE_PS2), size);
return std::nullopt; return std::nullopt;
} }
if (std::fread(ret.data(), 1, ret.size(), fp.get()) != ret.size()) if (std::fread(ret.data(), 1, ret.size(), fp.get()) != ret.size())
{ {
Log_ErrorPrintf("Failed to read BIOS image '%s'", filename); Error::SetErrno(error, TinyString::from_format("Failed to read BIOS '{}': ", Path::GetFileName(filename)), errno);
return std::nullopt; return std::nullopt;
} }
@ -326,7 +331,7 @@ DiscRegion BIOS::GetPSExeDiscRegion(const PSEXEHeader& header)
return DiscRegion::Other; return DiscRegion::Other;
} }
std::optional<std::vector<u8>> BIOS::GetBIOSImage(ConsoleRegion region) std::optional<std::vector<u8>> BIOS::GetBIOSImage(ConsoleRegion region, Error* error)
{ {
std::string bios_name; std::string bios_name;
switch (region) switch (region)
@ -345,31 +350,37 @@ std::optional<std::vector<u8>> BIOS::GetBIOSImage(ConsoleRegion region)
break; break;
} }
std::optional<Image> image;
if (bios_name.empty()) if (bios_name.empty())
{ {
// auto-detect // auto-detect
return FindBIOSImageInDirectory(region, EmuFolders::Bios.c_str()); image = FindBIOSImageInDirectory(region, EmuFolders::Bios.c_str(), error);
} }
else
// try the configured path
std::optional<Image> image = LoadImageFromFile(Path::Combine(EmuFolders::Bios, bios_name).c_str());
if (!image.has_value())
{ {
Host::ReportFormattedErrorAsync("Error", TRANSLATE("HostInterface", "Failed to load configured BIOS file '%s'"), // try the configured path
bios_name.c_str()); image = LoadImageFromFile(Path::Combine(EmuFolders::Bios, bios_name).c_str(), error);
return std::nullopt;
} }
const ImageInfo* ii = GetInfoForImage(image.value()); // verify region
if (!ii || !IsValidBIOSForRegion(region, ii->region)) if (image.has_value())
Log_WarningPrintf("BIOS '%s' does not match region. This may cause issues.", bios_name.c_str()); {
const ImageInfo* ii = GetInfoForImage(image.value());
if (!ii || !IsValidBIOSForRegion(region, ii->region))
{
Log_WarningFmt("BIOS region {} does not match requested region {}. This may cause issues.",
ii ? Settings::GetConsoleRegionName(ii->region) : "UNKNOWN",
Settings::GetConsoleRegionName(region));
}
}
return image; return image;
} }
std::optional<std::vector<u8>> BIOS::FindBIOSImageInDirectory(ConsoleRegion region, const char* directory) std::optional<std::vector<u8>> BIOS::FindBIOSImageInDirectory(ConsoleRegion region, const char* directory, Error* error)
{ {
Log_InfoPrintf("Searching for a %s BIOS in '%s'...", Settings::GetConsoleRegionDisplayName(region), directory); Log_InfoFmt("Searching for a {} BIOS in '{}'...", Settings::GetConsoleRegionName(region), directory);
FileSystem::FindResultsArray results; FileSystem::FindResultsArray results;
FileSystem::FindFiles( FileSystem::FindFiles(
@ -383,20 +394,21 @@ std::optional<std::vector<u8>> BIOS::FindBIOSImageInDirectory(ConsoleRegion regi
{ {
if (fd.Size != BIOS_SIZE && fd.Size != BIOS_SIZE_PS2 && fd.Size != BIOS_SIZE_PS3) if (fd.Size != BIOS_SIZE && fd.Size != BIOS_SIZE_PS2 && fd.Size != BIOS_SIZE_PS3)
{ {
Log_WarningPrintf("Skipping '%s': incorrect size", fd.FileName.c_str()); Log_WarningFmt("Skipping '{}': incorrect size", fd.FileName.c_str());
continue; continue;
} }
std::string full_path(Path::Combine(directory, fd.FileName)); std::string full_path(Path::Combine(directory, fd.FileName));
std::optional<Image> found_image = LoadImageFromFile(full_path.c_str()); std::optional<Image> found_image = LoadImageFromFile(full_path.c_str(), nullptr);
if (!found_image) if (!found_image)
continue; continue;
const ImageInfo* ii = GetInfoForImage(found_image.value()); const ImageInfo* ii = GetInfoForImage(found_image.value());
if (ii && IsValidBIOSForRegion(region, ii->region)) if (ii && IsValidBIOSForRegion(region, ii->region))
{ {
Log_InfoPrintf("Using BIOS '%s': %s", fd.FileName.c_str(), ii->description); Log_InfoFmt("Using BIOS '{}': {}", fd.FileName.c_str(), ii->description);
return found_image; fallback_image = std::move(found_image);
return fallback_image;
} }
// don't let an unknown bios take precedence over a known one // don't let an unknown bios take precedence over a known one
@ -410,53 +422,24 @@ std::optional<std::vector<u8>> BIOS::FindBIOSImageInDirectory(ConsoleRegion regi
if (!fallback_image.has_value()) if (!fallback_image.has_value())
{ {
Host::ReportFormattedErrorAsync("Error", TRANSLATE("HostInterface", "No BIOS image found for %s region"), Error::SetStringFmt(error, TRANSLATE_FS("System", "No BIOS image found for {} region."),
Settings::GetConsoleRegionDisplayName(region)); Settings::GetConsoleRegionName(region));
return std::nullopt; return fallback_image;
} }
if (!fallback_info) if (!fallback_info)
{ {
Log_WarningPrintf("Using unknown BIOS '%s'. This may crash.", fallback_path.c_str()); Log_WarningFmt("Using unknown BIOS '{}'. This may crash.", Path::GetFileName(fallback_path));
} }
else else
{ {
Log_WarningPrintf("Falling back to possibly-incompatible image '%s': %s", fallback_path.c_str(), Log_WarningFmt("Falling back to possibly-incompatible image '{}': {}", Path::GetFileName(fallback_path),
fallback_info->description); fallback_info->description);
} }
return fallback_image; return fallback_image;
} }
std::string BIOS::FindBIOSPathWithHash(const char* directory, const Hash& hash)
{
FileSystem::FindResultsArray files;
FileSystem::FindFiles(directory, "*",
FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_HIDDEN_FILES | FILESYSTEM_FIND_RELATIVE_PATHS, &files);
std::string ret;
for (FILESYSTEM_FIND_DATA& fd : files)
{
if (fd.Size != BIOS_SIZE && fd.Size != BIOS_SIZE_PS2 && fd.Size != BIOS_SIZE_PS3)
continue;
std::string full_path(Path::Combine(directory, fd.FileName));
std::optional<Image> found_image = LoadImageFromFile(full_path.c_str());
if (!found_image)
continue;
const BIOS::Hash found_hash = GetImageHash(found_image.value());
if (found_hash == hash)
{
ret = std::move(full_path);
break;
}
}
return ret;
}
std::vector<std::pair<std::string, const BIOS::ImageInfo*>> BIOS::FindBIOSImagesInDirectory(const char* directory) std::vector<std::pair<std::string, const BIOS::ImageInfo*>> BIOS::FindBIOSImagesInDirectory(const char* directory)
{ {
std::vector<std::pair<std::string, const ImageInfo*>> results; std::vector<std::pair<std::string, const ImageInfo*>> results;
@ -471,7 +454,7 @@ std::vector<std::pair<std::string, const BIOS::ImageInfo*>> BIOS::FindBIOSImages
continue; continue;
std::string full_path(Path::Combine(directory, fd.FileName)); std::string full_path(Path::Combine(directory, fd.FileName));
std::optional<Image> found_image = LoadImageFromFile(full_path.c_str()); std::optional<Image> found_image = LoadImageFromFile(full_path.c_str(), nullptr);
if (!found_image) if (!found_image)
continue; continue;
@ -484,5 +467,5 @@ std::vector<std::pair<std::string, const BIOS::ImageInfo*>> BIOS::FindBIOSImages
bool BIOS::HasAnyBIOSImages() bool BIOS::HasAnyBIOSImages()
{ {
return FindBIOSImageInDirectory(ConsoleRegion::Auto, EmuFolders::Bios.c_str()).has_value(); return FindBIOSImageInDirectory(ConsoleRegion::Auto, EmuFolders::Bios.c_str(), nullptr).has_value();
} }

View file

@ -1,13 +1,17 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>. // SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>.
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once #pragma once
#include "types.h" #include "types.h"
#include <optional> #include <optional>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
class Error;
namespace BIOS { namespace BIOS {
enum : u32 enum : u32
{ {
@ -58,7 +62,7 @@ struct PSEXEHeader
static_assert(sizeof(PSEXEHeader) == 0x800); static_assert(sizeof(PSEXEHeader) == 0x800);
#pragma pack(pop) #pragma pack(pop)
std::optional<Image> LoadImageFromFile(const char* filename); std::optional<Image> LoadImageFromFile(const char* filename, Error* error);
Hash GetImageHash(const Image& image); Hash GetImageHash(const Image& image);
const ImageInfo* GetInfoForImage(const Image& image); const ImageInfo* GetInfoForImage(const Image& image);
@ -74,14 +78,11 @@ bool IsValidPSExeHeader(const PSEXEHeader& header, u32 file_size);
DiscRegion GetPSExeDiscRegion(const PSEXEHeader& header); DiscRegion GetPSExeDiscRegion(const PSEXEHeader& header);
/// Loads the BIOS image for the specified region. /// Loads the BIOS image for the specified region.
std::optional<std::vector<u8>> GetBIOSImage(ConsoleRegion region); std::optional<std::vector<u8>> GetBIOSImage(ConsoleRegion region, Error* error);
/// Searches for a BIOS image for the specified region in the specified directory. If no match is found, the first /// 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. /// BIOS image within 512KB and 4MB will be used.
std::optional<std::vector<u8>> FindBIOSImageInDirectory(ConsoleRegion region, const char* directory); std::optional<std::vector<u8>> FindBIOSImageInDirectory(ConsoleRegion region, const char* directory, Error* error);
/// Returns a BIOS image which matches the specified hash.
std::string FindBIOSPathWithHash(const char* directory, const BIOS::Hash& hash);
/// Returns a list of filenames and descriptions for BIOS images in a directory. /// Returns a list of filenames and descriptions for BIOS images in a directory.
std::vector<std::pair<std::string, const BIOS::ImageInfo*>> FindBIOSImagesInDirectory(const char* directory); std::vector<std::pair<std::string, const BIOS::ImageInfo*>> FindBIOSImagesInDirectory(const char* directory);

View file

@ -102,7 +102,7 @@ static std::string GetExecutableNameForImage(IsoReader& iso, bool strip_subdirec
static bool ReadExecutableFromImage(IsoReader& iso, std::string* out_executable_name, static bool ReadExecutableFromImage(IsoReader& iso, std::string* out_executable_name,
std::vector<u8>* out_executable_data); std::vector<u8>* out_executable_data);
static bool LoadBIOS(const std::string& override_bios_path); static bool LoadBIOS(const std::string& override_bios_path, Error* error);
static void InternalReset(); static void InternalReset();
static void ClearRunningGame(); static void ClearRunningGame();
static void DestroySystem(); static void DestroySystem();
@ -1465,7 +1465,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
} }
// Load BIOS image. // Load BIOS image.
if (!LoadBIOS(parameters.override_bios)) if (!LoadBIOS(parameters.override_bios, error))
{ {
s_state = State::Shutdown; s_state = State::Shutdown;
ClearRunningGame(); ClearRunningGame();
@ -2251,20 +2251,29 @@ bool System::DoState(StateWrapper& sw, GPUTexture** host_texture, bool update_di
return !sw.HasError(); return !sw.HasError();
} }
bool System::LoadBIOS(const std::string& override_bios_path) bool System::LoadBIOS(const std::string& override_bios_path, Error* error)
{ {
std::optional<BIOS::Image> bios_image( std::optional<BIOS::Image> bios_image;
override_bios_path.empty() ? BIOS::GetBIOSImage(s_region) : FileSystem::ReadBinaryFile(override_bios_path.c_str())); if (!override_bios_path.empty())
if (!bios_image.has_value())
{ {
Host::ReportFormattedErrorAsync("Error", TRANSLATE("System", "Failed to load %s BIOS."), bios_image = FileSystem::ReadBinaryFile(override_bios_path.c_str(), error);
Settings::GetConsoleRegionName(s_region)); if (!bios_image.has_value())
return false; {
Error::AddPrefixFmt(error, TRANSLATE_FS("System", "Failed to load {} BIOS."),
Settings::GetConsoleRegionName(s_region));
return false;
}
}
else
{
bios_image = BIOS::GetBIOSImage(s_region, error);
if (!bios_image.has_value())
return false;
} }
if (bios_image->size() != static_cast<u32>(Bus::BIOS_SIZE)) if (bios_image->size() != static_cast<u32>(Bus::BIOS_SIZE))
{ {
Host::ReportFormattedErrorAsync("Error", TRANSLATE("System", "Incorrect BIOS image size")); Error::SetStringView(error, TRANSLATE_SV("System", "Incorrect BIOS image size"));
return false; return false;
} }