BIOS: Automatically detect images, improve selection UI

This commit is contained in:
Connor McLaughlin
2020-09-22 23:08:07 +10:00
parent 3385346b7f
commit 7d01bedf07
23 changed files with 512 additions and 179 deletions

View File

@ -4,6 +4,7 @@
#include "common/log.h"
#include "common/md5_digest.h"
#include "cpu_disasm.h"
#include <array>
#include <cerrno>
Log_SetChannel(BIOS);
@ -36,16 +37,28 @@ std::string Hash::ToString() const
return str;
}
static constexpr Hash SCPH_1000_HASH = MakeHashFromString("239665b1a3dade1b5a52c06338011044");
static constexpr Hash SCPH_1001_HASH = MakeHashFromString("924e392ed05558ffdb115408c263dccf");
static constexpr Hash SCPH_1002_HASH = MakeHashFromString("54847e693405ffeb0359c6287434cbef");
static constexpr Hash SCPH_3000_HASH = MakeHashFromString("849515939161e62f6b866f6853006780");
static constexpr Hash SCPH_5500_HASH = MakeHashFromString("8dd7d5296a650fac7319bce665a6a53c");
static constexpr Hash SCPH_5501_HASH = MakeHashFromString("490f666e1afb15b7362b406ed1cea246");
static constexpr Hash SCPH_5502_HASH = MakeHashFromString("32736f17079d0b2b7024407c39bd3050");
static constexpr Hash SCPH_7001_HASH = MakeHashFromString("1e68c231d0896b7eadcad1d7d8e76129");
static constexpr Hash SCPH_7002_HASH = MakeHashFromString("b9d9a0286c33dc6b7237bb13cd46fdee");
static constexpr Hash SCPH_POPS660_HASH = MakeHashFromString("c53ca5908936d412331790f4426c6c33");
static constexpr std::array<ImageInfo, 12> s_image_infos = {{
{"SCPH-1000, DTL-H1000 (v1.0 J)", ConsoleRegion::NTSC_J, MakeHashFromString("239665b1a3dade1b5a52c06338011044")},
{"SCPH-1001, 5003, DTL-H1201, H3001 (v2.2 12-04-95 A)", ConsoleRegion::NTSC_U,
MakeHashFromString("924e392ed05558ffdb115408c263dccf")},
{"SCPH-1002, DTL-H1002 (v2.0 05-10-95 E)", ConsoleRegion::PAL,
MakeHashFromString("54847e693405ffeb0359c6287434cbef")},
{"SCPH-3000, DTL-H1000H (v1.1 01-22-95)", ConsoleRegion::NTSC_J,
MakeHashFromString("849515939161e62f6b866f6853006780")},
{"SCPH-5000, DTL-H1200, H3000 (v2.2 12-04-95 J)", ConsoleRegion::NTSC_J,
MakeHashFromString("8dd7d5296a650fac7319bce665a6a53c")},
{"SCPH-5501, 5503, 7003 (v3.0 11-18-96 A)", ConsoleRegion::NTSC_U,
MakeHashFromString("490f666e1afb15b7362b406ed1cea246")},
{"SCPH-5502, 5552 (v3.0 01-06-97 E)", ConsoleRegion::PAL, MakeHashFromString("32736f17079d0b2b7024407c39bd3050")},
{"SCPH-7000, 7500, 9000 (v4.0 08-18-97 J)", ConsoleRegion::NTSC_J,
MakeHashFromString("8e4c14f567745eff2f0408c8129f72a6")},
{"SCPH-7000W (v4.1 11-14-97 A)", ConsoleRegion::NTSC_J, MakeHashFromString("b84be139db3ee6cbd075630aa20a6553")},
{"SCPH-7001, 7501, 7503, 9001, 9003, 9903 (v4.1 12-16-97 A)", ConsoleRegion::NTSC_U,
MakeHashFromString("1e68c231d0896b7eadcad1d7d8e76129")},
{"SCPH-7002, 7502, 9002 (v4.1 12-16-97 E)", ConsoleRegion::PAL,
MakeHashFromString("b9d9a0286c33dc6b7237bb13cd46fdee")},
{"POPS-660 (PSP)", ConsoleRegion::Auto, MakeHashFromString("c53ca5908936d412331790f4426c6c33")},
}};
Hash GetHash(const Image& image)
{
@ -56,14 +69,13 @@ Hash GetHash(const Image& image)
return hash;
}
std::optional<Image> LoadImageFromFile(std::string_view filename)
std::optional<Image> LoadImageFromFile(const char* filename)
{
Image ret(BIOS_SIZE);
std::string filename_str(filename);
auto fp = FileSystem::OpenManagedCFile(filename_str.c_str(), "rb");
auto fp = FileSystem::OpenManagedCFile(filename, "rb");
if (!fp)
{
Log_ErrorPrintf("Failed to open BIOS image '%s', errno=%d", filename_str.c_str(), errno);
Log_ErrorPrintf("Failed to open BIOS image '%s', errno=%d", filename, errno);
return std::nullopt;
}
@ -73,21 +85,20 @@ std::optional<Image> LoadImageFromFile(std::string_view filename)
if (size != BIOS_SIZE)
{
Log_ErrorPrintf("BIOS image '%s' mismatch, expecting %u bytes, got %u bytes", filename_str.c_str(), BIOS_SIZE,
size);
Log_ErrorPrintf("BIOS image '%s' mismatch, expecting %u bytes, got %u bytes", filename, BIOS_SIZE, size);
return std::nullopt;
}
if (std::fread(ret.data(), 1, ret.size(), fp.get()) != ret.size())
{
Log_ErrorPrintf("Failed to read BIOS image '%s'", filename_str.c_str());
Log_ErrorPrintf("Failed to read BIOS image '%s'", filename);
return std::nullopt;
}
return ret;
}
std::optional<Hash> GetHashForFile(const std::string_view filename)
std::optional<Hash> GetHashForFile(const char* filename)
{
auto image = LoadImageFromFile(filename);
if (!image)
@ -96,23 +107,24 @@ std::optional<Hash> GetHashForFile(const std::string_view filename)
return GetHash(*image);
}
const ImageInfo* GetImageInfoForHash(const Hash& hash)
{
for (const ImageInfo& ii : s_image_infos)
{
if (ii.hash == hash)
return &ii;
}
return nullptr;
}
bool IsValidHashForRegion(ConsoleRegion region, const Hash& hash)
{
switch (region)
{
case ConsoleRegion::NTSC_J:
return (hash == SCPH_1000_HASH || hash == SCPH_3000_HASH || hash == SCPH_5500_HASH || hash == SCPH_POPS660_HASH);
const ImageInfo* ii = GetImageInfoForHash(hash);
if (!ii)
return false;
case ConsoleRegion::NTSC_U:
return (hash == SCPH_1001_HASH || hash == SCPH_5501_HASH || hash == SCPH_7001_HASH || hash == SCPH_POPS660_HASH);
case ConsoleRegion::PAL:
return (hash == SCPH_1002_HASH || hash == SCPH_5502_HASH || hash == SCPH_7002_HASH || hash == SCPH_POPS660_HASH);
case ConsoleRegion::Auto:
default:
return false;
}
return (ii->region == ConsoleRegion::Auto || ii->region == region);
}
void PatchBIOS(Image& bios, u32 address, u32 value, u32 mask /*= UINT32_C(0xFFFFFFFF)*/)
@ -135,9 +147,8 @@ void PatchBIOS(Image& bios, u32 address, u32 value, u32 mask /*= UINT32_C(0xFFFF
bool PatchBIOSEnableTTY(Image& image, const Hash& hash)
{
if (hash != SCPH_1000_HASH && hash != SCPH_1001_HASH && hash != SCPH_1002_HASH && hash != SCPH_3000_HASH &&
hash != SCPH_5500_HASH && hash != SCPH_5501_HASH && hash != SCPH_5502_HASH && hash != SCPH_7001_HASH &&
hash != SCPH_7002_HASH && hash != SCPH_POPS660_HASH)
const ImageInfo* ii = GetImageInfoForHash(hash);
if (!ii)
{
Log_WarningPrintf("Incompatible version for TTY patch: %s", hash.ToString().c_str());
return false;
@ -151,9 +162,8 @@ bool PatchBIOSEnableTTY(Image& image, const Hash& hash)
bool PatchBIOSFastBoot(Image& image, const Hash& hash)
{
if (hash != SCPH_1000_HASH && hash != SCPH_1001_HASH && hash != SCPH_1002_HASH && hash != SCPH_3000_HASH &&
hash != SCPH_5500_HASH && hash != SCPH_5501_HASH && hash != SCPH_5502_HASH && hash != SCPH_7001_HASH &&
hash != SCPH_7002_HASH && hash != SCPH_POPS660_HASH)
const ImageInfo* ii = GetImageInfoForHash(hash);
if (!ii)
{
Log_WarningPrintf("Incompatible version for fast-boot patch: %s", hash.ToString().c_str());
return false;

View File

@ -24,6 +24,13 @@ struct Hash
std::string ToString() const;
};
struct ImageInfo
{
const char* description;
ConsoleRegion region;
Hash hash;
};
#pragma pack(push, 1)
struct PSEXEHeader
{
@ -46,9 +53,10 @@ static_assert(sizeof(PSEXEHeader) == 0x800);
#pragma pack(pop)
Hash GetHash(const Image& image);
std::optional<Image> LoadImageFromFile(std::string_view filename);
std::optional<Hash> GetHashForFile(std::string_view filename);
std::optional<Image> LoadImageFromFile(const char* filename);
std::optional<Hash> GetHashForFile(const char* filename);
const ImageInfo* GetImageInfoForHash(const Hash& hash);
bool IsValidHashForRegion(ConsoleRegion region, const Hash& hash);
void PatchBIOS(Image& image, u32 address, u32 value, u32 mask = UINT32_C(0xFFFFFFFF));

View File

@ -201,84 +201,132 @@ void HostInterface::AddFormattedOSDMessage(float duration, const char* format, .
std::optional<std::vector<u8>> HostInterface::GetBIOSImage(ConsoleRegion region)
{
// Try the other default filenames in the directory of the configured BIOS.
#define TRY_FILENAME(filename) \
do \
{ \
String try_filename = filename; \
std::optional<BIOS::Image> found_image = BIOS::LoadImageFromFile(try_filename.GetCharArray()); \
if (found_image) \
{ \
BIOS::Hash found_hash = BIOS::GetHash(*found_image); \
Log_DevPrintf("Hash for BIOS '%s': %s", try_filename.GetCharArray(), found_hash.ToString().c_str()); \
if (BIOS::IsValidHashForRegion(region, found_hash)) \
{ \
Log_InfoPrintf("Using BIOS from '%s' for region '%s'", try_filename.GetCharArray(), \
Settings::GetConsoleRegionName(region)); \
return found_image; \
} \
} \
} while (0)
// Try the configured image.
TRY_FILENAME(g_settings.bios_path.c_str());
// Try searching in the same folder for other region's images.
const std::string* bios_path;
switch (region)
{
case ConsoleRegion::NTSC_J:
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph3000.bin", false, false));
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "ps-11j.bin", false, false));
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph1000.bin", false, false));
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "ps-10j.bin", false, false));
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph5500.bin", false, false));
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "ps-30j.bin", false, false));
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph7000.bin", false, false));
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph7500.bin", false, false));
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph9000.bin", false, false));
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "ps-40j.bin", false, false));
break;
case ConsoleRegion::NTSC_U:
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph1001.bin", false, false));
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "ps-22a.bin", false, false));
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph5501.bin", false, false));
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph5503.bin", false, false));
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph7003.bin", false, false));
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "ps-30a.bin", false, false));
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph7001.bin", false, false));
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph7501.bin", false, false));
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph7503.bin", false, false));
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph9001.bin", false, false));
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph9003.bin", false, false));
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph9903.bin", false, false));
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "ps-41a.bin", false, false));
bios_path = &g_settings.bios_path_ntsc_j;
break;
case ConsoleRegion::PAL:
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph1002.bin", false, false));
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "ps-21e.bin", false, false));
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph5502.bin", false, false));
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph5552.bin", false, false));
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "ps-30e.bin", false, false));
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph7002.bin", false, false));
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph7502.bin", false, false));
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "scph9002.bin", false, false));
TRY_FILENAME(FileSystem::BuildPathRelativeToFile(g_settings.bios_path.c_str(), "ps-41e.bin", false, false));
bios_path = &g_settings.bios_path_pal;
break;
case ConsoleRegion::NTSC_U:
default:
bios_path = &g_settings.bios_path_ntsc_u;
break;
}
#undef RELATIVE_PATH
#undef TRY_FILENAME
if (bios_path->empty())
{
// auto-detect
return FindBIOSImageInDirectory(region, GetUserDirectoryRelativePath("bios").c_str());
}
// Fall back to the default image.
Log_WarningPrintf("No suitable BIOS image for region %s could be located, using configured image '%s'. This may "
"result in instability.",
Settings::GetConsoleRegionName(region), g_settings.bios_path.c_str());
return BIOS::LoadImageFromFile(g_settings.bios_path);
// try the configured path
std::optional<BIOS::Image> image = BIOS::LoadImageFromFile(
GetUserDirectoryRelativePath("bios" FS_OSPATH_SEPARATOR_STR "%s", bios_path->c_str()).c_str());
if (!image.has_value())
{
g_host_interface->ReportFormattedError(
g_host_interface->TranslateString("HostInterface", "Failed to load configured BIOS file '%s'"),
bios_path->c_str());
return std::nullopt;
}
BIOS::Hash found_hash = BIOS::GetHash(*image);
Log_DevPrintf("Hash for BIOS '%s': %s", bios_path->c_str(), found_hash.ToString().c_str());
if (!BIOS::IsValidHashForRegion(region, found_hash))
Log_WarningPrintf("Hash for BIOS '%s' does not match region. This may cause issues.", bios_path->c_str());
return image;
}
std::optional<std::vector<u8>> HostInterface::FindBIOSImageInDirectory(ConsoleRegion region, const char* directory)
{
Log_InfoPrintf("Searching for a %s BIOS in '%s'...", Settings::GetConsoleRegionDisplayName(region), directory);
FileSystem::FindResultsArray results;
FileSystem::FindFiles(
directory, "*", FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_HIDDEN_FILES | FILESYSTEM_FIND_RELATIVE_PATHS, &results);
std::string fallback_path;
std::optional<BIOS::Image> fallback_image;
for (const FILESYSTEM_FIND_DATA& fd : results)
{
if (fd.Size != BIOS::BIOS_SIZE)
{
Log_WarningPrintf("Skipping '%s': incorrect size", fd.FileName.c_str());
continue;
}
std::string full_path(
StringUtil::StdStringFromFormat("%s" FS_OSPATH_SEPARATOR_STR "%s", directory, fd.FileName.c_str()));
std::optional<BIOS::Image> found_image = BIOS::LoadImageFromFile(full_path.c_str());
if (!found_image)
continue;
BIOS::Hash found_hash = BIOS::GetHash(*found_image);
Log_DevPrintf("Hash for BIOS '%s': %s", fd.FileName.c_str(), found_hash.ToString().c_str());
if (BIOS::IsValidHashForRegion(region, found_hash))
{
Log_InfoPrintf("Using BIOS '%s'", fd.FileName.c_str());
return found_image;
}
fallback_path = std::move(full_path);
fallback_image = std::move(found_image);
}
if (!fallback_image.has_value())
{
g_host_interface->ReportFormattedError(
g_host_interface->TranslateString("HostInterface", "No BIOS image found for %s region"),
Settings::GetConsoleRegionDisplayName(region));
return std::nullopt;
}
Log_WarningPrintf("Falling back to possibly-incompatible image '%s'", fallback_path.c_str());
return fallback_image;
}
std::vector<std::pair<std::string, const BIOS::ImageInfo*>>
HostInterface::FindBIOSImagesInDirectory(const char* directory)
{
std::vector<std::pair<std::string, const BIOS::ImageInfo*>> results;
FileSystem::FindResultsArray files;
FileSystem::FindFiles(directory, "*",
FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_HIDDEN_FILES | FILESYSTEM_FIND_RELATIVE_PATHS, &files);
for (FILESYSTEM_FIND_DATA& fd : files)
{
if (fd.Size != BIOS::BIOS_SIZE)
continue;
std::string full_path(
StringUtil::StdStringFromFormat("%s" FS_OSPATH_SEPARATOR_STR "%s", directory, fd.FileName.c_str()));
std::optional<BIOS::Image> found_image = BIOS::LoadImageFromFile(full_path.c_str());
if (!found_image)
continue;
BIOS::Hash found_hash = BIOS::GetHash(*found_image);
const BIOS::ImageInfo* ii = BIOS::GetImageInfoForHash(found_hash);
results.emplace_back(std::move(fd.FileName), ii);
}
return results;
}
std::vector<std::pair<std::string, const BIOS::ImageInfo*>> HostInterface::FindBIOSImagesInUserDirectory()
{
return FindBIOSImagesInDirectory(GetUserDirectoryRelativePath("bios").c_str());
}
bool HostInterface::LoadState(const char* filename)
@ -410,7 +458,9 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si)
si.SetBoolValue("Audio", "Sync", true);
si.SetBoolValue("Audio", "DumpOnBoot", false);
si.SetStringValue("BIOS", "Path", "bios" FS_OSPATH_SEPARATOR_STR "scph1001.bin");
si.SetStringValue("BIOS", "PathNTSCU", "");
si.SetStringValue("BIOS", "PathNTSCJ", "");
si.SetStringValue("BIOS", "PathPAL", "");
si.SetBoolValue("BIOS", "PatchTTYEnable", false);
si.SetBoolValue("BIOS", "PatchFastBoot", false);

View File

@ -22,6 +22,11 @@ class GameList;
struct SystemBootParameters;
namespace BIOS
{
struct ImageInfo;
}
class HostInterface
{
public:
@ -116,6 +121,14 @@ public:
/// Loads the BIOS image for the specified region.
std::optional<std::vector<u8>> GetBIOSImage(ConsoleRegion region);
/// Searches for a BIOS image for the specified region in the specified directory. If no match is found, the first
/// 512KB BIOS image will be used.
std::optional<std::vector<u8>> FindBIOSImageInDirectory(ConsoleRegion region, const char* 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*>> FindBIOSImagesInUserDirectory();
virtual void OnRunningGameChanged();
virtual void OnSystemPerformanceCountersUpdated();

View File

@ -157,7 +157,9 @@ void Settings::Load(SettingsInterface& si)
gpu_fifo_size = static_cast<u32>(si.GetIntValue("Hacks", "GPUFIFOSize", DEFAULT_GPU_FIFO_SIZE));
gpu_max_run_ahead = si.GetIntValue("Hacks", "GPUMaxRunAhead", DEFAULT_GPU_MAX_RUN_AHEAD);
bios_path = si.GetStringValue("BIOS", "Path", "bios" FS_OSPATH_SEPARATOR_STR "scph1001.bin");
bios_path_ntsc_u = si.GetStringValue("BIOS", "PathNTSCU", "");
bios_path_ntsc_j = si.GetStringValue("BIOS", "PathNTSCJ", "");
bios_path_pal = si.GetStringValue("BIOS", "PathPAL", "");
bios_patch_tty_enable = si.GetBoolValue("BIOS", "PatchTTYEnable", false);
bios_patch_fast_boot = si.GetBoolValue("BIOS", "PatchFastBoot", false);
@ -271,7 +273,9 @@ void Settings::Save(SettingsInterface& si) const
si.SetIntValue("Hacks", "GPUFIFOSize", gpu_fifo_size);
si.SetIntValue("Hacks", "GPUMaxRunAhead", gpu_max_run_ahead);
si.SetStringValue("BIOS", "Path", bios_path.c_str());
si.SetStringValue("BIOS", "PathNTSCJ", bios_path_ntsc_j.c_str());
si.SetStringValue("BIOS", "PathNTSCU", bios_path_ntsc_u.c_str());
si.SetStringValue("BIOS", "PathPAL", bios_path_pal.c_str());
si.SetBoolValue("BIOS", "PatchTTYEnable", bios_patch_tty_enable);
si.SetBoolValue("BIOS", "PatchFastBoot", bios_patch_fast_boot);

View File

@ -147,7 +147,9 @@ struct Settings
// TODO: Controllers, memory cards, etc.
std::string bios_path;
std::string bios_path_ntsc_j;
std::string bios_path_ntsc_u;
std::string bios_path_pal;
bool bios_patch_tty_enable = false;
bool bios_patch_fast_boot = false;