Achievements: Move game hash calculation to system

Saves reading the executable a second time.
dev
Stenzek 6 days ago
parent 6194f08a99
commit 59247c84b9
No known key found for this signature in database

@ -5,7 +5,6 @@
#include "achievements.h"
#include "achievements_private.h"
#include "bios.h"
#include "bus.h"
#include "cheats.h"
#include "core.h"
@ -29,7 +28,6 @@
#include "common/file_system.h"
#include "common/heap_array.h"
#include "common/log.h"
#include "common/md5_digest.h"
#include "common/path.h"
#include "common/progress_callback.h"
#include "common/ryml_helpers.h"
@ -346,45 +344,6 @@ void Achievements::ReportRCError(int err, fmt::format_string<T...> fmt, T&&... a
ReportError(str);
}
std::optional<Achievements::GameHash> Achievements::GetGameHash(CDImage* image)
{
if (!image)
return std::nullopt;
std::string executable_name;
std::vector<u8> executable_data;
if (!System::ReadExecutableFromImage(image, &executable_name, &executable_data))
return std::nullopt;
return GetGameHash(executable_name, executable_data);
}
std::optional<Achievements::GameHash> Achievements::GetGameHash(const std::string_view executable_name,
std::span<const u8> executable_data)
{
// NOTE: Assumes executable_data is aligned to 4 bytes at least.. it should be.
const BIOS::PSEXEHeader* header = reinterpret_cast<const BIOS::PSEXEHeader*>(executable_data.data());
if (executable_data.size() < sizeof(BIOS::PSEXEHeader) || !BIOS::IsValidPSExeHeader(*header, executable_data.size()))
{
ERROR_LOG("PS-EXE header is invalid in '{}' ({} bytes)", executable_name, executable_data.size());
return std::nullopt;
}
const u32 hash_size = std::min(header->file_size + 2048, static_cast<u32>(executable_data.size()));
MD5Digest digest;
digest.Update(executable_name.data(), static_cast<u32>(executable_name.size()));
if (hash_size > 0)
digest.Update(executable_data.data(), hash_size);
std::optional<GameHash> ret;
digest.Final(ret.emplace());
INFO_COLOR_LOG(StrongOrange, "RA Hash for '{}': {} ({} bytes hashed)", executable_name, GameHashToString(ret),
hash_size);
return ret;
}
std::string Achievements::GetImageURL(const char* image_name, u32 type)
{
@ -1061,15 +1020,10 @@ void Achievements::UpdateRichPresence(std::unique_lock<std::recursive_mutex>& lo
#endif
}
void Achievements::OnSystemStarting(CDImage* image, bool disable_hardcore_mode)
void Achievements::OnSystemStarting(bool disable_hardcore_mode)
{
const std::optional<GameHash> game_hash = GetGameHash(image);
std::unique_lock lock(s_state.mutex);
// Always set hash in case we late enable.
s_state.game_hash = game_hash;
if (!IsActive() || IsRAIntegrationInitializing())
return;
@ -1091,12 +1045,20 @@ void Achievements::OnSystemStarting(CDImage* image, bool disable_hardcore_mode)
else
{
// only enable hardcore mode if we're logged in, or waiting for a login response
if (image && !disable_hardcore_mode && g_settings.achievements_hardcore_mode && IsLoggedInOrLoggingIn())
if (!disable_hardcore_mode && g_settings.achievements_hardcore_mode && IsLoggedInOrLoggingIn())
EnableHardcoreMode(false, false);
}
}
void Achievements::OnSystemStarted()
{
const auto lock = GetLock();
if (!IsActive() || IsRAIntegrationInitializing())
return;
// now we can finally identify the game
BeginLoadGame();
if (!s_state.load_game_request)
BeginLoadGame();
}
void Achievements::OnSystemDestroyed()
@ -1140,19 +1102,26 @@ void Achievements::OnSystemReset()
}
}
void Achievements::GameChanged(CDImage* image)
void Achievements::SetGameHash(const std::optional<GameHash>& hash)
{
const auto game_hash = GetGameHash(image);
std::unique_lock lock(s_state.mutex);
const auto lock = GetLock();
// disc changed?
if (s_state.game_hash == game_hash)
if (s_state.game_hash == hash)
return;
s_state.game_hash = game_hash;
s_state.game_hash = hash;
INFO_COLOR_LOG(StrongOrange, "RA Hash: {}", GameHashToString(s_state.game_hash));
// just set the hash if inactive
if (!IsActive() || IsRAIntegrationInitializing())
{
DisableHardcoreMode(false, false);
return;
}
// if session hasn't started yet, don't treat this as a change
if (!System::IsValid())
return;
// cancel previous requests
@ -1170,6 +1139,12 @@ void Achievements::GameChanged(CDImage* image)
s_state.reload_game_on_reset = true;
}
std::optional<Achievements::GameHash> Achievements::GetGameHash()
{
const auto lock = GetLock();
return s_state.game_hash;
}
void Achievements::BeginLoadGame()
{
if (!s_state.game_hash.has_value())

@ -65,10 +65,6 @@ private:
/// Acquires the achievements lock. Must be held when accessing any achievement state from another thread.
std::unique_lock<std::recursive_mutex> GetLock();
/// Returns the achievements game hash for a given disc.
std::optional<GameHash> GetGameHash(CDImage* image);
std::optional<GameHash> GetGameHash(const std::string_view executable_name, std::span<const u8> executable_data);
/// Converts a game hash to a string for display. If the hash is nullopt, returns "[NO HASH]".
TinyString GameHashToString(const std::optional<GameHash>& hash);
@ -82,7 +78,10 @@ bool RefreshGameList(ProgressCallback* progress, Error* error);
bool RefreshAllProgressDatabase(ProgressCallback* progress, Error* error);
/// Called when the system is start. Engages hardcore mode if enabled.
void OnSystemStarting(CDImage* image, bool disable_hardcore_mode);
void OnSystemStarting(bool disable_hardcore_mode);
/// Called when the system has finished starting. Identifies the game, assuming the system has set it.
void OnSystemStarted();
/// Called when the system is shutting down. If this returns false, the shutdown should be aborted.
void OnSystemDestroyed();
@ -91,7 +90,10 @@ void OnSystemDestroyed();
void OnSystemReset();
/// Called when the system changes game.
void GameChanged(CDImage* image);
void SetGameHash(const std::optional<GameHash>& hash);
/// Returns the game hash for the currently loaded game, or nullopt if no hash is set.
std::optional<GameHash> GetGameHash();
/// Called once a frame at vsync time on the CPU thread.
void FrameUpdate();

@ -280,7 +280,7 @@ const GameDatabase::Entry* GameDatabase::GetEntryForDisc(CDImage* image)
{
std::string id;
GameHash hash;
System::GetGameDetailsFromImage(image, &id, &hash);
System::GetGameDetailsFromImage(image, &id, &hash, nullptr);
const Entry* entry = GetEntryForGameDetails(id, hash);
if (entry)
return entry;

@ -341,12 +341,12 @@ bool GameList::GetDiscListEntry(const std::string& path, Entry* entry)
// use the same buffer for game and achievement hashing, to avoid double decompression
std::string id, executable_name;
std::vector<u8> executable_data;
if (System::GetGameDetailsFromImage(cdi.get(), &id, &entry->hash, &executable_name, &executable_data))
std::optional<Achievements::GameHash> achievements_hash;
if (System::GetGameDetailsFromImage(cdi.get(), &id, &entry->hash, &achievements_hash) &&
achievements_hash.has_value())
{
// used for achievement count lookup later
const std::optional<Achievements::GameHash> hash = Achievements::GetGameHash(executable_name, executable_data);
if (hash.has_value())
entry->achievements_hash = hash.value();
entry->achievements_hash = achievements_hash.value();
}
// try the database first

@ -63,6 +63,7 @@
#include "common/file_system.h"
#include "common/layered_settings_interface.h"
#include "common/log.h"
#include "common/md5_digest.h"
#include "common/memmap.h"
#include "common/path.h"
#include "common/string_util.h"
@ -140,6 +141,10 @@ static bool ReadExecutableFromImage(IsoReader& iso, std::string* out_executable_
static GameHash GetGameHashFromBuffer(std::string_view exe_name, std::span<const u8> exe_buffer,
const IsoReader::ISOPrimaryVolumeDescriptor& iso_pvd, u32 track_1_length);
/// Returns the achievements game hash for a given executable.
static std::optional<Achievements::GameHash> GetAchievementsHash(const std::string& executable_name,
const std::vector<u8>& executable_data);
/// Settings that are looked up on demand.
static bool ShouldStartFullscreen();
static bool ShouldStartPaused();
@ -669,7 +674,7 @@ std::string System::GetGameHashId(GameHash hash)
}
bool System::GetGameDetailsFromImage(CDImage* cdi, std::string* out_id, GameHash* out_hash,
std::string* out_executable_name, std::vector<u8>* out_executable_data)
std::optional<std::array<u8, 16>>* out_achievements_hash)
{
IsoReader iso;
std::string id;
@ -681,10 +686,8 @@ bool System::GetGameDetailsFromImage(CDImage* cdi, std::string* out_id, GameHash
out_id->clear();
if (out_hash)
*out_hash = 0;
if (out_executable_name)
out_executable_name->clear();
if (out_executable_data)
out_executable_data->clear();
if (out_achievements_hash)
out_achievements_hash->reset();
return false;
}
@ -729,11 +732,8 @@ bool System::GetGameDetailsFromImage(CDImage* cdi, std::string* out_id, GameHash
if (out_hash)
*out_hash = hash;
if (out_executable_name)
*out_executable_name = std::move(exe_name);
if (out_executable_data)
*out_executable_data = std::move(exe_buffer);
if (out_achievements_hash)
*out_achievements_hash = GetAchievementsHash(exe_name, exe_buffer);
return true;
}
@ -901,6 +901,31 @@ GameHash System::GetGameHashFromBuffer(std::string_view exe_name, std::span<cons
return hash;
}
std::optional<Achievements::GameHash> System::GetAchievementsHash(const std::string& executable_name,
const std::vector<u8>& executable_data)
{
// NOTE: Assumes executable_data is aligned to 4 bytes at least.. it should be.
const BIOS::PSEXEHeader* header = reinterpret_cast<const BIOS::PSEXEHeader*>(executable_data.data());
if (executable_data.size() < sizeof(BIOS::PSEXEHeader) || !BIOS::IsValidPSExeHeader(*header, executable_data.size()))
{
ERROR_LOG("PS-EXE header is invalid in '{}' ({} bytes)", executable_name, executable_data.size());
return std::nullopt;
}
const u32 hash_size = std::min(header->file_size + 2048, static_cast<u32>(executable_data.size()));
MD5Digest digest;
digest.Update(executable_name.data(), static_cast<u32>(executable_name.size()));
if (hash_size > 0)
digest.Update(executable_data.data(), hash_size);
std::optional<Achievements::GameHash> ret;
digest.Final(ret.emplace());
DEV_LOG("RA Hash for '{}': {} ({} bytes hashed)", executable_name, Achievements::GameHashToString(ret), hash_size);
return ret;
}
DiscRegion System::GetRegionForSerial(const std::string_view serial)
{
static constexpr const std::pair<const char*, DiscRegion> region_prefixes[] = {
@ -1607,10 +1632,10 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
s_state.gpu_dump_player = std::move(gpu_dump);
Host::OnSystemStarting();
FullscreenUI::OnSystemStarting();
Achievements::OnSystemStarting(parameters.disable_achievements_hardcore_mode);
// Update running game, this will apply settings as well.
UpdateRunningGame(disc ? disc->GetPath() : parameters.path, disc.get(), true);
Achievements::OnSystemStarting(disc.get(), parameters.disable_achievements_hardcore_mode);
// Determine console region. Has to be done here, because gamesettings can override it.
s_state.region = (g_settings.region == ConsoleRegion::Auto) ? auto_console_region : g_settings.region;
@ -1744,6 +1769,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
GDBServer::Initialize(g_settings.gdb_server_port);
#endif
Achievements::OnSystemStarted();
Host::OnSystemStarted();
if (parameters.load_image_to_ram || g_settings.cdrom_load_image_to_ram)
@ -2612,7 +2638,6 @@ void System::DoMemoryState(StateWrapper& sw, MemorySaveState& mss, bool update_d
#else
#define SAVE_COMPONENT(name, expr) expr
#endif
sw.Do(&s_state.frame_number);
sw.Do(&s_state.internal_frame_number);
@ -3988,6 +4013,7 @@ void System::UpdateRunningGame(const std::string& path, CDImage* image, bool boo
s_state.running_game_hash = 0;
s_state.running_game_custom_title = false;
std::optional<Achievements::GameHash> achievements_hash;
if (!path.empty())
{
s_state.running_game_path = path;
@ -4047,7 +4073,7 @@ void System::UpdateRunningGame(const std::string& path, CDImage* image, bool boo
if (image->GetTrack(1).mode != CDImage::TrackMode::Audio)
{
std::string id;
GetGameDetailsFromImage(image, &id, &s_state.running_game_hash);
GetGameDetailsFromImage(image, &id, &s_state.running_game_hash, &achievements_hash);
// Custom serial?
s_state.running_game_entry = s_state.running_game_serial.empty() ?
@ -4088,11 +4114,11 @@ void System::UpdateRunningGame(const std::string& path, CDImage* image, bool boo
if (!IsReplayingGPUDump())
{
Achievements::SetGameHash(achievements_hash);
// Cheats are loaded later in Initialize().
if (!booting)
{
Achievements::GameChanged(image);
const bool had_setting_overrides = Cheats::HasAnySettingOverrides();
Cheats::ReloadCheats(true, true, false, true, true);
if (had_setting_overrides)
@ -4155,6 +4181,8 @@ bool System::PopulateGameListEntryFromCurrentGame(GameList::Entry* entry, Error*
}
entry->achievements_game_id = Achievements::GetGameID();
if (const std::optional<Achievements::GameHash> achievements_hash = Achievements::GetGameHash())
entry->achievements_hash = achievements_hash.value();
entry->is_runtime_populated = true;
return true;

@ -8,6 +8,7 @@
#include "util/image.h"
#include <array>
#include <ctime>
#include <functional>
#include <memory>
@ -152,9 +153,8 @@ std::string GetExecutableNameForImage(CDImage* cdi, bool strip_subdirectories);
bool ReadExecutableFromImage(CDImage* cdi, std::string* out_executable_name, std::vector<u8>* out_executable_data);
std::string GetGameHashId(GameHash hash);
bool GetGameDetailsFromImage(CDImage* cdi, std::string* out_id = nullptr, GameHash* out_hash = nullptr,
std::string* out_executable_name = nullptr,
std::vector<u8>* out_executable_data = nullptr);
bool GetGameDetailsFromImage(CDImage* cdi, std::string* out_id, GameHash* out_hash,
std::optional<std::array<u8, 16>>* out_achievements_hash);
GameHash GetGameHashFromFile(const char* path);
GameHash GetGameHashFromBuffer(const std::string_view path, const std::span<const u8> data);
DiscRegion GetRegionForSerial(const std::string_view serial);

Loading…
Cancel
Save