mirror of
https://github.com/stenzek/duckstation.git
synced 2026-07-11 01:24:11 +02:00
Achievements: Move game hash calculation to system
Saves reading the executable a second time.
This commit is contained in:
+30
-55
@@ -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
|
||||
|
||||
+43
-15
@@ -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;
|
||||
|
||||
+3
-3
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user