Refactored AchievementManager hash code to take a volume.

This change splits LoadGameAsync into three methods: two HashGame methods to accept either a string filepath or a volume, and a common LoadGameSync that both HashGames call to actually process the code. In the process, some minor cleanup, and the hash resolution now takes place on the work queue asynchronously.
This commit is contained in:
LillyJadeKatrin 2023-10-01 13:35:00 -04:00
parent a65246ec3f
commit 148c2f3c0d
4 changed files with 225 additions and 115 deletions

View File

@ -20,7 +20,7 @@
#include "Core/Core.h"
#include "Core/PowerPC/MMU.h"
#include "Core/System.h"
#include "DiscIO/Volume.h"
#include "DiscIO/Blob.h"
#include "VideoCommon/OnScreenDisplay.h"
#include "VideoCommon/VideoEvents.h"
@ -91,8 +91,7 @@ bool AchievementManager::IsLoggedIn() const
return !Config::Get(Config::RA_API_TOKEN).empty();
}
void AchievementManager::LoadGameByFilenameAsync(const std::string& iso_path,
const ResponseCallback& callback)
void AchievementManager::HashGame(const std::string& file_path, const ResponseCallback& callback)
{
if (!m_is_runtime_initialized)
{
@ -102,116 +101,149 @@ void AchievementManager::LoadGameByFilenameAsync(const std::string& iso_path,
return;
}
m_system = &Core::System::GetInstance();
m_queue.EmplaceItem([this, callback, file_path] {
Hash new_hash;
{
std::lock_guard lg{m_filereader_lock};
rc_hash_filereader volume_reader{
.open = &AchievementManager::FilereaderOpenByFilepath,
.seek = &AchievementManager::FilereaderSeek,
.tell = &AchievementManager::FilereaderTell,
.read = &AchievementManager::FilereaderRead,
.close = &AchievementManager::FilereaderClose,
};
rc_hash_init_custom_filereader(&volume_reader);
if (!rc_hash_generate_from_file(new_hash.data(), RC_CONSOLE_GAMECUBE, file_path.c_str()))
{
ERROR_LOG_FMT(ACHIEVEMENTS, "Unable to generate achievement hash from game file {}.",
file_path);
callback(AchievementManager::ResponseType::MALFORMED_OBJECT);
}
}
{
std::lock_guard lg{m_lock};
m_game_hash = std::move(new_hash);
}
LoadGameSync(callback);
});
}
void AchievementManager::HashGame(const DiscIO::Volume* volume, const ResponseCallback& callback)
{
if (!m_is_runtime_initialized)
{
ERROR_LOG_FMT(ACHIEVEMENTS,
"Attempted to load game achievements without Achievement Manager initialized.");
callback(AchievementManager::ResponseType::MANAGER_NOT_INITIALIZED);
return;
}
if (volume == nullptr)
{
INFO_LOG_FMT(ACHIEVEMENTS, "New volume is empty.");
return;
}
{
std::lock_guard lg{m_lock};
if (m_loading_volume.get() != nullptr)
{
return;
}
m_loading_volume = DiscIO::CreateVolume(volume->GetBlobReader().CopyReader());
}
m_system = &Core::System::GetInstance();
struct FilereaderState
{
int64_t position = 0;
std::unique_ptr<DiscIO::Volume> volume;
};
rc_hash_filereader volume_reader{
.open = [](const char* path_utf8) -> void* {
auto state = std::make_unique<FilereaderState>();
state->volume = DiscIO::CreateVolume(path_utf8);
if (!state->volume)
return nullptr;
return state.release();
},
.seek =
[](void* file_handle, int64_t offset, int origin) {
switch (origin)
{
case SEEK_SET:
reinterpret_cast<FilereaderState*>(file_handle)->position = offset;
break;
case SEEK_CUR:
reinterpret_cast<FilereaderState*>(file_handle)->position += offset;
break;
case SEEK_END:
// Unused
break;
}
},
.tell =
[](void* file_handle) {
return reinterpret_cast<FilereaderState*>(file_handle)->position;
},
.read =
[](void* file_handle, void* buffer, size_t requested_bytes) {
FilereaderState* filereader_state = reinterpret_cast<FilereaderState*>(file_handle);
bool success = (filereader_state->volume->Read(
filereader_state->position, requested_bytes, reinterpret_cast<u8*>(buffer),
DiscIO::PARTITION_NONE));
if (success)
{
filereader_state->position += requested_bytes;
return requested_bytes;
}
else
{
return static_cast<size_t>(0);
}
},
.close = [](void* file_handle) { delete reinterpret_cast<FilereaderState*>(file_handle); }};
rc_hash_init_custom_filereader(&volume_reader);
if (!rc_hash_generate_from_file(m_game_hash.data(), RC_CONSOLE_GAMECUBE, iso_path.c_str()))
{
ERROR_LOG_FMT(ACHIEVEMENTS, "Unable to generate achievement hash from game file.");
return;
}
m_queue.EmplaceItem([this, callback] {
const auto resolve_hash_response = ResolveHash(this->m_game_hash);
if (resolve_hash_response != ResponseType::SUCCESS || m_game_id == 0)
Hash new_hash;
{
callback(resolve_hash_response);
INFO_LOG_FMT(ACHIEVEMENTS, "No RetroAchievements data found for this game.");
OSD::AddMessage("No RetroAchievements data found for this game.", OSD::Duration::VERY_LONG,
OSD::Color::RED);
return;
std::lock_guard lg{m_filereader_lock};
rc_hash_filereader volume_reader{
.open = &AchievementManager::FilereaderOpenByVolume,
.seek = &AchievementManager::FilereaderSeek,
.tell = &AchievementManager::FilereaderTell,
.read = &AchievementManager::FilereaderRead,
.close = &AchievementManager::FilereaderClose,
};
rc_hash_init_custom_filereader(&volume_reader);
if (!rc_hash_generate_from_file(new_hash.data(), RC_CONSOLE_GAMECUBE, ""))
{
ERROR_LOG_FMT(ACHIEVEMENTS, "Unable to generate achievement hash from volume.");
callback(AchievementManager::ResponseType::MALFORMED_OBJECT);
return;
}
}
const auto start_session_response = StartRASession();
if (start_session_response != ResponseType::SUCCESS)
{
callback(start_session_response);
WARN_LOG_FMT(ACHIEVEMENTS, "Failed to connect to RetroAchievements server.");
OSD::AddMessage("Failed to connect to RetroAchievements server.", OSD::Duration::VERY_LONG,
OSD::Color::RED);
return;
}
const auto fetch_game_data_response = FetchGameData();
if (fetch_game_data_response != ResponseType::SUCCESS)
{
ERROR_LOG_FMT(ACHIEVEMENTS, "Unable to retrieve data from RetroAchievements server.");
OSD::AddMessage("Unable to retrieve data from RetroAchievements server.",
OSD::Duration::VERY_LONG, OSD::Color::RED);
return;
}
INFO_LOG_FMT(ACHIEVEMENTS, "Loading achievements for {}.", m_game_data.title);
// Claim the lock, then queue the fetch unlock data calls, then initialize the unlock map in
// ActivateDeactiveAchievements. This allows the calls to process while initializing the
// unlock map but then forces them to wait until it's initialized before making modifications to
// it.
{
std::lock_guard lg{m_lock};
m_is_game_loaded = true;
m_framecount = 0;
LoadUnlockData([](ResponseType r_type) {});
ActivateDeactivateAchievements();
ActivateDeactivateLeaderboards();
ActivateDeactivateRichPresence();
m_game_hash = std::move(new_hash);
m_loading_volume.reset();
}
FetchBadges();
// Reset this to zero so that RP immediately triggers on the first frame
m_last_ping_time = 0;
INFO_LOG_FMT(ACHIEVEMENTS, "RetroAchievements successfully loaded for {}.", m_game_data.title);
if (m_update_callback)
m_update_callback();
callback(fetch_game_data_response);
LoadGameSync(callback);
});
}
void AchievementManager::LoadGameSync(const ResponseCallback& callback)
{
Hash current_hash;
{
std::lock_guard lg{m_lock};
current_hash = m_game_hash;
}
const auto resolve_hash_response = ResolveHash(current_hash);
if (resolve_hash_response != ResponseType::SUCCESS || m_game_id == 0)
{
INFO_LOG_FMT(ACHIEVEMENTS, "No RetroAchievements data found for this game.");
OSD::AddMessage("No RetroAchievements data found for this game.", OSD::Duration::VERY_LONG,
OSD::Color::RED);
callback(resolve_hash_response);
return;
}
const auto start_session_response = StartRASession();
if (start_session_response != ResponseType::SUCCESS)
{
WARN_LOG_FMT(ACHIEVEMENTS, "Failed to connect to RetroAchievements server.");
OSD::AddMessage("Failed to connect to RetroAchievements server.", OSD::Duration::VERY_LONG,
OSD::Color::RED);
callback(start_session_response);
return;
}
const auto fetch_game_data_response = FetchGameData();
if (fetch_game_data_response != ResponseType::SUCCESS)
{
ERROR_LOG_FMT(ACHIEVEMENTS, "Unable to retrieve data from RetroAchievements server.");
OSD::AddMessage("Unable to retrieve data from RetroAchievements server.",
OSD::Duration::VERY_LONG, OSD::Color::RED);
return;
}
INFO_LOG_FMT(ACHIEVEMENTS, "Loading achievements for {}.", m_game_data.title);
// Claim the lock, then queue the fetch unlock data calls, then initialize the unlock map in
// ActivateDeactiveAchievements. This allows the calls to process while initializing the
// unlock map but then forces them to wait until it's initialized before making modifications to
// it.
{
std::lock_guard lg{m_lock};
m_is_game_loaded = true;
m_framecount = 0;
LoadUnlockData([](ResponseType r_type) {});
ActivateDeactivateAchievements();
ActivateDeactivateLeaderboards();
ActivateDeactivateRichPresence();
}
FetchBadges();
// Reset this to zero so that RP immediately triggers on the first frame
m_last_ping_time = 0;
INFO_LOG_FMT(ACHIEVEMENTS, "RetroAchievements successfully loaded for {}.", m_game_data.title);
if (m_update_callback)
m_update_callback();
callback(fetch_game_data_response);
}
bool AchievementManager::IsGameLoaded() const
{
return m_is_game_loaded;
@ -790,6 +822,69 @@ void AchievementManager::Shutdown()
INFO_LOG_FMT(ACHIEVEMENTS, "Achievement Manager shut down.");
}
void* AchievementManager::FilereaderOpenByFilepath(const char* path_utf8)
{
auto state = std::make_unique<FilereaderState>();
state->volume = DiscIO::CreateVolume(path_utf8);
if (!state->volume)
return nullptr;
return state.release();
}
void* AchievementManager::FilereaderOpenByVolume(const char* path_utf8)
{
auto state = std::make_unique<FilereaderState>();
{
std::lock_guard lg{*AchievementManager::GetInstance()->GetLock()};
state->volume = std::move(AchievementManager::GetInstance()->GetLoadingVolume());
}
if (!state->volume)
return nullptr;
return state.release();
}
void AchievementManager::FilereaderSeek(void* file_handle, int64_t offset, int origin)
{
switch (origin)
{
case SEEK_SET:
static_cast<FilereaderState*>(file_handle)->position = offset;
break;
case SEEK_CUR:
static_cast<FilereaderState*>(file_handle)->position += offset;
break;
case SEEK_END:
// Unused
break;
}
}
int64_t AchievementManager::FilereaderTell(void* file_handle)
{
return static_cast<FilereaderState*>(file_handle)->position;
}
size_t AchievementManager::FilereaderRead(void* file_handle, void* buffer, size_t requested_bytes)
{
FilereaderState* filereader_state = static_cast<FilereaderState*>(file_handle);
bool success = (filereader_state->volume->Read(filereader_state->position, requested_bytes,
static_cast<u8*>(buffer), DiscIO::PARTITION_NONE));
if (success)
{
filereader_state->position += requested_bytes;
return requested_bytes;
}
else
{
return 0;
}
}
void AchievementManager::FilereaderClose(void* file_handle)
{
delete static_cast<FilereaderState*>(file_handle);
}
AchievementManager::ResponseType AchievementManager::VerifyCredentials(const std::string& password)
{
rc_api_login_response_t login_data{};
@ -826,8 +921,7 @@ AchievementManager::ResponseType AchievementManager::VerifyCredentials(const std
return r_type;
}
AchievementManager::ResponseType
AchievementManager::ResolveHash(std::array<char, HASH_LENGTH> game_hash)
AchievementManager::ResponseType AchievementManager::ResolveHash(Hash game_hash)
{
rc_api_resolve_hash_response_t hash_data{};
std::string username, api_token;

View File

@ -18,6 +18,7 @@
#include "Common/Event.h"
#include "Common/WorkQueueThread.h"
#include "DiscIO/Volume.h"
namespace Core
{
@ -51,6 +52,8 @@ public:
u32 soft_points;
};
static constexpr size_t HASH_SIZE = 33;
using Hash = std::array<char, HASH_SIZE>;
using AchievementId = u32;
static constexpr size_t FORMAT_SIZE = 24;
using FormattedValue = std::array<char, FORMAT_SIZE>;
@ -105,7 +108,8 @@ public:
ResponseType Login(const std::string& password);
void LoginAsync(const std::string& password, const ResponseCallback& callback);
bool IsLoggedIn() const;
void LoadGameByFilenameAsync(const std::string& iso_path, const ResponseCallback& callback);
void HashGame(const std::string& file_path, const ResponseCallback& callback);
void HashGame(const DiscIO::Volume* volume, const ResponseCallback& callback);
bool IsGameLoaded() const;
void LoadUnlockData(const ResponseCallback& callback);
@ -140,15 +144,29 @@ public:
private:
AchievementManager() = default;
static constexpr int HASH_LENGTH = 33;
struct FilereaderState
{
int64_t position = 0;
std::unique_ptr<DiscIO::Volume> volume;
};
static void* FilereaderOpenByFilepath(const char* path_utf8);
static void* FilereaderOpenByVolume(const char* path_utf8);
static void FilereaderSeek(void* file_handle, int64_t offset, int origin);
static int64_t FilereaderTell(void* file_handle);
static size_t FilereaderRead(void* file_handle, void* buffer, size_t requested_bytes);
static void FilereaderClose(void* file_handle);
ResponseType VerifyCredentials(const std::string& password);
ResponseType ResolveHash(std::array<char, HASH_LENGTH> game_hash);
ResponseType ResolveHash(Hash game_hash);
void LoadGameSync(const ResponseCallback& callback);
ResponseType StartRASession();
ResponseType FetchGameData();
ResponseType FetchUnlockData(bool hardcore);
ResponseType FetchBoardInfo(AchievementId leaderboard_id);
std::unique_ptr<DiscIO::Volume>& GetLoadingVolume() { return m_loading_volume; };
void ActivateDeactivateAchievement(AchievementId id, bool enabled, bool unofficial, bool encore);
void GenerateRichPresence();
@ -174,10 +192,11 @@ private:
Core::System* m_system{};
bool m_is_runtime_initialized = false;
UpdateCallback m_update_callback;
std::unique_ptr<DiscIO::Volume> m_loading_volume;
std::string m_display_name;
u32 m_player_score = 0;
BadgeStatus m_player_badge;
std::array<char, HASH_LENGTH> m_game_hash{};
Hash m_game_hash{};
u32 m_game_id = 0;
rc_api_fetch_game_data_response_t m_game_data{};
bool m_is_game_loaded = false;
@ -192,6 +211,7 @@ private:
Common::WorkQueueThread<std::function<void()>> m_queue;
Common::WorkQueueThread<std::function<void()>> m_image_queue;
mutable std::recursive_mutex m_lock;
std::recursive_mutex m_filereader_lock;
}; // class AchievementManager
#endif // USE_RETRO_ACHIEVEMENTS

View File

@ -164,16 +164,6 @@ bool BootCore(std::unique_ptr<BootParameters> boot, const WindowSystemInfo& wsi)
}
}
#ifdef USE_RETRO_ACHIEVEMENTS
std::string path = "";
if (std::holds_alternative<BootParameters::Disc>(boot->parameters))
{
path = std::get<BootParameters::Disc>(boot->parameters).path;
}
AchievementManager::GetInstance()->LoadGameByFilenameAsync(
path, [](AchievementManager::ResponseType r_type) {});
#endif // USE_RETRO_ACHIEVEMENTS
const bool load_ipl = !StartUp.bWii && !Config::Get(Config::MAIN_SKIP_IPL) &&
std::holds_alternative<BootParameters::Disc>(boot->parameters);
if (load_ipl)

View File

@ -20,6 +20,7 @@
#include "Common/Config/Config.h"
#include "Common/Logging/Log.h"
#include "Core/AchievementManager.h"
#include "Core/Config/MainSettings.h"
#include "Core/Config/SessionSettings.h"
#include "Core/CoreTiming.h"
@ -396,6 +397,11 @@ void DVDInterface::SetDisc(std::unique_ptr<DiscIO::VolumeDisc> disc,
m_auto_disc_change_index = 0;
}
#ifdef USE_RETRO_ACHIEVEMENTS
AchievementManager::GetInstance()->HashGame(disc.get(),
[](AchievementManager::ResponseType r_type) {});
#endif // USE_RETRO_ACHIEVEMENTS
// Assume that inserting a disc requires having an empty disc before
if (had_disc != has_disc)
ExpansionInterface::g_rtc_flags[ExpansionInterface::RTCFlag::DiscChanged] = true;