mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-30 09:39:46 -06:00
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:
@ -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;
|
||||
|
Reference in New Issue
Block a user