BIOS: Refactor loading/hashing of images

Fixes identification of PS2 BIOSes. However, they are not (yet) fastboot
compatible.
This commit is contained in:
Stenzek 2024-07-18 18:08:00 +10:00
parent 19063d4288
commit f0945ca3ca
No known key found for this signature in database
4 changed files with 75 additions and 94 deletions

View file

@ -17,9 +17,12 @@
Log_SetChannel(BIOS); Log_SetChannel(BIOS);
static constexpr BIOS::Hash MakeHashFromString(const char str[]) namespace BIOS {
static const ImageInfo* GetInfoForHash(const std::span<u8> image, const ImageInfo::Hash& hash);
static constexpr ImageInfo::Hash MakeHashFromString(const char str[])
{ {
BIOS::Hash h{}; ImageInfo::Hash h{};
for (int i = 0; str[i] != '\0'; i++) for (int i = 0; str[i] != '\0'; i++)
{ {
u8 nibble = 0; u8 nibble = 0;
@ -31,22 +34,13 @@ static constexpr BIOS::Hash MakeHashFromString(const char str[])
else if (ch >= 'A' && ch <= 'Z') else if (ch >= 'A' && ch <= 'Z')
nibble = 0xA + (str[i] - 'A'); nibble = 0xA + (str[i] - 'A');
h.bytes[i / 2] |= nibble << (((i & 1) ^ 1) * 4); h[i / 2] |= nibble << (((i & 1) ^ 1) * 4);
} }
return h; return h;
} }
std::string BIOS::Hash::ToString() const
{
char str[33];
std::snprintf(str, sizeof(str), "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", bytes[0],
bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], bytes[10],
bytes[11], bytes[12], bytes[13], bytes[14], bytes[15]);
return str;
}
// clang-format off // clang-format off
static constexpr const BIOS::ImageInfo s_image_info_by_hash[] = { static constexpr const ImageInfo s_image_info_by_hash[] = {
{"SCPH-1000, DTL-H1000 (v1.0)", ConsoleRegion::NTSC_J, MakeHashFromString("239665b1a3dade1b5a52c06338011044"), true}, {"SCPH-1000, DTL-H1000 (v1.0)", ConsoleRegion::NTSC_J, MakeHashFromString("239665b1a3dade1b5a52c06338011044"), true},
{"SCPH-1001, 5003, DTL-H1201, H3001 (v2.2 12-04-95 A)", ConsoleRegion::NTSC_U, MakeHashFromString("924e392ed05558ffdb115408c263dccf"), true}, {"SCPH-1001, 5003, DTL-H1201, H3001 (v2.2 12-04-95 A)", ConsoleRegion::NTSC_U, MakeHashFromString("924e392ed05558ffdb115408c263dccf"), true},
{"SCPH-1002, DTL-H1002 (v2.0 05-10-95 E)", ConsoleRegion::PAL, MakeHashFromString("54847e693405ffeb0359c6287434cbef"), true}, {"SCPH-1002, DTL-H1002 (v2.0 05-10-95 E)", ConsoleRegion::PAL, MakeHashFromString("54847e693405ffeb0359c6287434cbef"), true},
@ -126,6 +120,7 @@ static constexpr const BIOS::ImageInfo s_image_info_by_hash[] = {
{"PS2, SCPH-70000 (v5.0 06/14/04 J)", ConsoleRegion::NTSC_J, MakeHashFromString("0eee5d1c779aa50e94edd168b4ebf42e"), true}, {"PS2, SCPH-70000 (v5.0 06/14/04 J)", ConsoleRegion::NTSC_J, MakeHashFromString("0eee5d1c779aa50e94edd168b4ebf42e"), true},
{"PS2, SCPH-70001/SCPH-70011/SCPH-70012 (v5.0 06/14/04 A)", ConsoleRegion::NTSC_U, MakeHashFromString("d333558cc14561c1fdc334c75d5f37b7"), true}, {"PS2, SCPH-70001/SCPH-70011/SCPH-70012 (v5.0 06/14/04 A)", ConsoleRegion::NTSC_U, MakeHashFromString("d333558cc14561c1fdc334c75d5f37b7"), true},
{"PS2, SCPH-70002/SCPH-70003/SCPH-70004/SCPH-70008 (v5.0 06/14/04 E)", ConsoleRegion::PAL, MakeHashFromString("dc752f160044f2ed5fc1f4964db2a095"), true}, {"PS2, SCPH-70002/SCPH-70003/SCPH-70004/SCPH-70008 (v5.0 06/14/04 E)", ConsoleRegion::PAL, MakeHashFromString("dc752f160044f2ed5fc1f4964db2a095"), true},
{"PS2, SCPH-70002 (v5.0 06/14/04 E)", ConsoleRegion::PAL, MakeHashFromString("7ebb4fc5eab6f79a27d76ac9aad392b2"), true},
{"PS2, DTL-H70002 (v5.0 06/14/04 E)", ConsoleRegion::PAL, MakeHashFromString("63ead1d74893bf7f36880af81f68a82d"), true}, {"PS2, DTL-H70002 (v5.0 06/14/04 E)", ConsoleRegion::PAL, MakeHashFromString("63ead1d74893bf7f36880af81f68a82d"), true},
{"PS2, SCPH-70005/SCPH-70006/SCPH-70007 (v5.0 06/14/04 J)", ConsoleRegion::NTSC_J, MakeHashFromString("3e3e030c0f600442fa05b94f87a1e238"), true}, {"PS2, SCPH-70005/SCPH-70006/SCPH-70007 (v5.0 06/14/04 J)", ConsoleRegion::NTSC_J, MakeHashFromString("3e3e030c0f600442fa05b94f87a1e238"), true},
{"PS2, DESR-5500/DESR-5700/DESR-7500/DESR-7700 (v5.0 09/17/04 J)", ConsoleRegion::NTSC_J, MakeHashFromString("1ad977bb539fc9448a08ab276a836bbc"), true}, {"PS2, DESR-5500/DESR-5700/DESR-7500/DESR-7700 (v5.0 09/17/04 J)", ConsoleRegion::NTSC_J, MakeHashFromString("1ad977bb539fc9448a08ab276a836bbc"), true},
@ -159,56 +154,54 @@ static constexpr const BIOS::ImageInfo s_openbios_info = {"OpenBIOS", ConsoleReg
static constexpr const char s_openbios_signature[] = {'O', 'p', 'e', 'n', 'B', 'I', 'O', 'S'}; static constexpr const char s_openbios_signature[] = {'O', 'p', 'e', 'n', 'B', 'I', 'O', 'S'};
static constexpr u32 s_openbios_signature_offset = 0x78; static constexpr u32 s_openbios_signature_offset = 0x78;
BIOS::Hash BIOS::GetImageHash(const BIOS::Image& image) } // namespace BIOS
TinyString BIOS::ImageInfo::GetHashString(const BIOS::ImageInfo::Hash& hash)
{ {
BIOS::Hash hash; return TinyString::from_format(
MD5Digest digest; "{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}", hash[0],
digest.Update(image); hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7], hash[8], hash[9], hash[10], hash[11], hash[12],
digest.Final(hash.bytes); hash[13], hash[14], hash[15]);
return hash;
} }
std::optional<BIOS::Image> BIOS::LoadImageFromFile(const char* filename, Error* error) std::optional<BIOS::Image> BIOS::LoadImageFromFile(const char* filename, Error* error)
{ {
Image ret(BIOS_SIZE); std::optional<BIOS::Image> ret;
auto fp = FileSystem::OpenManagedCFile(filename, "rb", error); auto fp = FileSystem::OpenManagedCFile(filename, "rb", error);
if (!fp) if (!fp)
{ {
Error::AddPrefixFmt(error, "Failed to open BIOS '{}': ", Path::GetFileName(filename)); Error::AddPrefixFmt(error, "Failed to open BIOS '{}': ", Path::GetFileName(filename));
return std::nullopt; return ret;
} }
std::fseek(fp.get(), 0, SEEK_END); const u64 size = static_cast<u64>(FileSystem::FSize64(fp.get()));
const u32 size = static_cast<u32>(std::ftell(fp.get()));
std::fseek(fp.get(), 0, SEEK_SET);
if (size != BIOS_SIZE && size != BIOS_SIZE_PS2 && size != BIOS_SIZE_PS3) if (size != BIOS_SIZE && size != BIOS_SIZE_PS2 && size != BIOS_SIZE_PS3)
{ {
Error::SetStringFmt(error, "BIOS image '{}' size mismatch, expecting either {} or {} bytes but got {} bytes", Error::SetStringFmt(error, "BIOS image '{}' size mismatch, expecting either {} or {} bytes but got {} bytes",
Path::GetFileName(filename), static_cast<unsigned>(BIOS_SIZE), Path::GetFileName(filename), static_cast<unsigned>(BIOS_SIZE),
static_cast<unsigned>(BIOS_SIZE_PS2), size); static_cast<unsigned>(BIOS_SIZE_PS2), size);
return std::nullopt;
}
if (std::fread(ret.data(), 1, ret.size(), fp.get()) != ret.size())
{
Error::SetErrno(error, TinyString::from_format("Failed to read BIOS '{}': ", Path::GetFileName(filename)), errno);
return std::nullopt;
}
DEV_LOG(
fmt::format("Hash for BIOS '{}': {}", FileSystem::GetDisplayNameFromPath(filename), GetImageHash(ret).ToString())
.c_str());
return ret; return ret;
} }
const BIOS::ImageInfo* BIOS::GetInfoForImage(const Image& image) // We want to hash the whole file. That means reading the whole thing in, if it's a larger BIOS (PS2).
{ std::optional<std::vector<u8>> data = FileSystem::ReadBinaryFile(fp.get(), error);
const Hash hash(GetImageHash(image)); if (!data.has_value() || data->size() < BIOS_SIZE)
return GetInfoForImage(image, hash); return ret;
ret = BIOS::Image();
ret->hash = MD5Digest::HashData(data.value());
// But only copy the first 512KB, since that's all that's mapped.
ret->data = std::move(data.value());
ret->data.resize(BIOS_SIZE);
ret->info = GetInfoForHash(ret->data, ret->hash);
DEV_LOG("Hash for BIOS '{}': {}", FileSystem::GetDisplayNameFromPath(filename), ImageInfo::GetHashString(ret->hash));
return ret;
} }
const BIOS::ImageInfo* BIOS::GetInfoForImage(const Image& image, const Hash& hash) const BIOS::ImageInfo* BIOS::GetInfoForHash(const std::span<u8> image, const ImageInfo::Hash& hash)
{ {
// check for openbios // check for openbios
if (image.size() >= (s_openbios_signature_offset + std::size(s_openbios_signature)) && if (image.size() >= (s_openbios_signature_offset + std::size(s_openbios_signature)) &&
@ -223,7 +216,7 @@ const BIOS::ImageInfo* BIOS::GetInfoForImage(const Image& image, const Hash& has
return &ii; return &ii;
} }
WARNING_LOG("Unknown BIOS hash: {}", hash.ToString()); WARNING_LOG("Unknown BIOS hash: {}", ImageInfo::GetHashString(hash));
return nullptr; return nullptr;
} }
@ -331,7 +324,7 @@ DiscRegion BIOS::GetPSExeDiscRegion(const PSEXEHeader& header)
return DiscRegion::Other; return DiscRegion::Other;
} }
std::optional<std::vector<u8>> BIOS::GetBIOSImage(ConsoleRegion region, Error* error) std::optional<BIOS::Image> BIOS::GetBIOSImage(ConsoleRegion region, Error* error)
{ {
std::string bios_name; std::string bios_name;
switch (region) switch (region)
@ -364,20 +357,17 @@ std::optional<std::vector<u8>> BIOS::GetBIOSImage(ConsoleRegion region, Error* e
} }
// verify region // verify region
if (image.has_value()) if (image.has_value() && (!image->info || !IsValidBIOSForRegion(region, image->info->region)))
{
const ImageInfo* ii = GetInfoForImage(image.value());
if (!ii || !IsValidBIOSForRegion(region, ii->region))
{ {
WARNING_LOG("BIOS region {} does not match requested region {}. This may cause issues.", WARNING_LOG("BIOS region {} does not match requested region {}. This may cause issues.",
ii ? Settings::GetConsoleRegionName(ii->region) : "UNKNOWN", Settings::GetConsoleRegionName(region)); image->info ? Settings::GetConsoleRegionName(image->info->region) : "UNKNOWN",
} Settings::GetConsoleRegionName(region));
} }
return image; return image;
} }
std::optional<std::vector<u8>> BIOS::FindBIOSImageInDirectory(ConsoleRegion region, const char* directory, Error* error) std::optional<BIOS::Image> BIOS::FindBIOSImageInDirectory(ConsoleRegion region, const char* directory, Error* error)
{ {
INFO_LOG("Searching for a {} BIOS in '{}'...", Settings::GetConsoleRegionName(region), directory); INFO_LOG("Searching for a {} BIOS in '{}'...", Settings::GetConsoleRegionName(region), directory);
@ -387,7 +377,6 @@ std::optional<std::vector<u8>> BIOS::FindBIOSImageInDirectory(ConsoleRegion regi
std::string fallback_path; std::string fallback_path;
std::optional<Image> fallback_image; std::optional<Image> fallback_image;
const ImageInfo* fallback_info = nullptr;
for (const FILESYSTEM_FIND_DATA& fd : results) for (const FILESYSTEM_FIND_DATA& fd : results)
{ {
@ -402,21 +391,19 @@ std::optional<std::vector<u8>> BIOS::FindBIOSImageInDirectory(ConsoleRegion regi
if (!found_image) if (!found_image)
continue; continue;
const ImageInfo* ii = GetInfoForImage(found_image.value()); if (found_image->info && IsValidBIOSForRegion(region, found_image->info->region))
if (ii && IsValidBIOSForRegion(region, ii->region))
{ {
INFO_LOG("Using BIOS '{}': {}", fd.FileName.c_str(), ii->description); INFO_LOG("Using BIOS '{}': {}", fd.FileName.c_str(), found_image->info->description);
fallback_image = std::move(found_image); fallback_image = std::move(found_image);
return fallback_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
if (!fallback_path.empty() && (fallback_info || !ii)) if (fallback_image.has_value() && (fallback_image->info || !found_image->info))
continue; continue;
fallback_path = std::move(full_path); fallback_path = std::move(full_path);
fallback_image = std::move(found_image); fallback_image = std::move(found_image);
fallback_info = ii;
} }
if (!fallback_image.has_value()) if (!fallback_image.has_value())
@ -436,14 +423,14 @@ std::optional<std::vector<u8>> BIOS::FindBIOSImageInDirectory(ConsoleRegion regi
return fallback_image; return fallback_image;
} }
if (!fallback_info) if (!fallback_image->info)
{ {
WARNING_LOG("Using unknown BIOS '{}'. This may crash.", Path::GetFileName(fallback_path)); WARNING_LOG("Using unknown BIOS '{}'. This may crash.", Path::GetFileName(fallback_path));
} }
else else
{ {
WARNING_LOG("Falling back to possibly-incompatible image '{}': {}", Path::GetFileName(fallback_path), WARNING_LOG("Falling back to possibly-incompatible image '{}': {}", Path::GetFileName(fallback_path),
fallback_info->description); fallback_image->info->description);
} }
return fallback_image; return fallback_image;
@ -467,8 +454,7 @@ std::vector<std::pair<std::string, const BIOS::ImageInfo*>> BIOS::FindBIOSImages
if (!found_image) if (!found_image)
continue; continue;
const ImageInfo* ii = GetInfoForImage(found_image.value()); results.emplace_back(std::move(fd.FileName), found_image->info);
results.emplace_back(std::move(fd.FileName), ii);
} }
return results; return results;

View file

@ -1,11 +1,15 @@
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>. // SPDX-FileCopyrightText: 2019-2024 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 "common/small_string.h"
#include <array>
#include <optional> #include <optional>
#include <span>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
@ -21,24 +25,24 @@ enum : u32
BIOS_SIZE_PS3 = 0x3E66F0 BIOS_SIZE_PS3 = 0x3E66F0
}; };
using Image = std::vector<u8>;
struct Hash
{
u8 bytes[16];
ALWAYS_INLINE bool operator==(const Hash& bh) const { return (std::memcmp(bytes, bh.bytes, sizeof(bytes)) == 0); }
ALWAYS_INLINE bool operator!=(const Hash& bh) const { return (std::memcmp(bytes, bh.bytes, sizeof(bytes)) != 0); }
std::string ToString() const;
};
struct ImageInfo struct ImageInfo
{ {
static constexpr u32 HASH_SIZE = 16;
using Hash = std::array<u8, HASH_SIZE>;
const char* description; const char* description;
ConsoleRegion region; ConsoleRegion region;
Hash hash; Hash hash;
bool patch_compatible; bool patch_compatible;
static TinyString GetHashString(const Hash& hash);
};
struct Image
{
const ImageInfo* info;
ImageInfo::Hash hash;
std::vector<u8> data;
}; };
#pragma pack(push, 1) #pragma pack(push, 1)
@ -63,10 +67,7 @@ static_assert(sizeof(PSEXEHeader) == 0x800);
#pragma pack(pop) #pragma pack(pop)
std::optional<Image> LoadImageFromFile(const char* filename, Error* error); std::optional<Image> LoadImageFromFile(const char* filename, Error* error);
Hash GetImageHash(const Image& image);
const ImageInfo* GetInfoForImage(const Image& image);
const ImageInfo* GetInfoForImage(const Image& image, const Hash& hash);
bool IsValidBIOSForRegion(ConsoleRegion console_region, ConsoleRegion bios_region); bool IsValidBIOSForRegion(ConsoleRegion console_region, ConsoleRegion bios_region);
void PatchBIOS(u8* image, u32 image_size, u32 address, u32 value, u32 mask = UINT32_C(0xFFFFFFFF)); void PatchBIOS(u8* image, u32 image_size, u32 address, u32 value, u32 mask = UINT32_C(0xFFFFFFFF));
@ -78,11 +79,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, Error* error); std::optional<Image> 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, Error* error); std::optional<Image> FindBIOSImageInDirectory(ConsoleRegion region, const char* directory, Error* error);
/// 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

@ -178,7 +178,7 @@ static TickCount s_max_slice_ticks = System::MASTER_CLOCK / 10;
static u32 s_frame_number = 1; static u32 s_frame_number = 1;
static u32 s_internal_frame_number = 1; static u32 s_internal_frame_number = 1;
static const BIOS::ImageInfo* s_bios_image_info = nullptr; static const BIOS::ImageInfo* s_bios_image_info = nullptr;
static BIOS::Hash s_bios_hash = {}; static BIOS::ImageInfo::Hash s_bios_hash = {};
static std::string s_running_game_path; static std::string s_running_game_path;
static std::string s_running_game_serial; static std::string s_running_game_serial;
@ -610,11 +610,6 @@ const BIOS::ImageInfo* System::GetBIOSImageInfo()
return s_bios_image_info; return s_bios_image_info;
} }
const BIOS::Hash& System::GetBIOSHash()
{
return s_bios_hash;
}
float System::GetFPS() float System::GetFPS()
{ {
return s_fps; return s_fps;
@ -2328,11 +2323,12 @@ bool System::DoState(StateWrapper& sw, GPUTexture** host_texture, bool update_di
// Don't bother checking this at all for memory states, since they won't have a different BIOS... // Don't bother checking this at all for memory states, since they won't have a different BIOS...
if (!is_memory_state) if (!is_memory_state)
{ {
BIOS::Hash bios_hash = s_bios_hash; BIOS::ImageInfo::Hash bios_hash = s_bios_hash;
sw.DoBytesEx(bios_hash.bytes, sizeof(bios_hash.bytes), 58, s_bios_hash.bytes); sw.DoBytesEx(bios_hash.data(), BIOS::ImageInfo::HASH_SIZE, 58, s_bios_hash.data());
if (bios_hash != s_bios_hash) if (bios_hash != s_bios_hash)
{ {
WARNING_LOG("BIOS hash mismatch: System: {} | State: {}", s_bios_hash.ToString(), bios_hash.ToString()); WARNING_LOG("BIOS hash mismatch: System: {} | State: {}", BIOS::ImageInfo::GetHashString(s_bios_hash),
BIOS::ImageInfo::GetHashString(bios_hash));
Host::AddIconOSDMessage( Host::AddIconOSDMessage(
"StateBIOSMismatch", ICON_FA_EXCLAMATION_TRIANGLE, "StateBIOSMismatch", ICON_FA_EXCLAMATION_TRIANGLE,
TRANSLATE_STR("System", "This save state was created with a different BIOS. This may cause stability issues."), TRANSLATE_STR("System", "This save state was created with a different BIOS. This may cause stability issues."),
@ -2441,14 +2437,14 @@ bool System::LoadBIOS(Error* error)
if (!bios_image.has_value()) if (!bios_image.has_value())
return false; return false;
s_bios_hash = BIOS::GetImageHash(bios_image.value()); s_bios_image_info = bios_image->info;
s_bios_image_info = BIOS::GetInfoForImage(bios_image.value(), s_bios_hash); s_bios_hash = bios_image->hash;
if (s_bios_image_info) if (s_bios_image_info)
INFO_LOG("Using BIOS: {}", s_bios_image_info->description); INFO_LOG("Using BIOS: {}", s_bios_image_info->description);
else else
WARNING_LOG("Using an unknown BIOS: {}", s_bios_hash.ToString()); WARNING_LOG("Using an unknown BIOS: {}", BIOS::ImageInfo::GetHashString(s_bios_hash));
std::memcpy(Bus::g_bios, bios_image->data(), Bus::BIOS_SIZE); std::memcpy(Bus::g_bios, bios_image->data.data(), Bus::BIOS_SIZE);
return true; return true;
} }

View file

@ -34,7 +34,6 @@ class GrowableMemoryByteStream;
namespace BIOS { namespace BIOS {
struct ImageInfo; struct ImageInfo;
struct Hash;
} // namespace BIOS } // namespace BIOS
namespace GameDatabase { namespace GameDatabase {
@ -217,7 +216,6 @@ bool WasFastBooted();
u64 GetSessionPlayedTime(); u64 GetSessionPlayedTime();
const BIOS::ImageInfo* GetBIOSImageInfo(); const BIOS::ImageInfo* GetBIOSImageInfo();
const BIOS::Hash& GetBIOSHash();
// TODO: Move to PerformanceMetrics // TODO: Move to PerformanceMetrics
static constexpr u32 NUM_FRAME_TIME_SAMPLES = 150; static constexpr u32 NUM_FRAME_TIME_SAMPLES = 150;