diff --git a/Common/File/Path.cpp b/Common/File/Path.cpp index b80a5e7ca1..7105a66307 100644 --- a/Common/File/Path.cpp +++ b/Common/File/Path.cpp @@ -258,6 +258,15 @@ std::wstring Path::ToWString() const { } return w; } +std::string Path::ToCString() const { + std::string w = path_; + for (size_t i = 0; i < w.size(); i++) { + if (w[i] == '/') { + w[i] = '\\'; + } + } + return w; +} #endif std::string Path::ToVisualString(const char *relativeRoot) const { diff --git a/Common/File/Path.h b/Common/File/Path.h index 3e09c0c3c4..41fdb919d9 100644 --- a/Common/File/Path.h +++ b/Common/File/Path.h @@ -90,6 +90,11 @@ public: #if PPSSPP_PLATFORM(WINDOWS) std::wstring ToWString() const; + std::string ToCString() const; // Flips the slashes back to Windows standard, but string still UTF-8. +#else + std::string ToCSTring() const { + return ToString(); + } #endif // Pass in a relative root to turn the path into a relative path - if it is one! diff --git a/Common/Net/HTTPClient.h b/Common/Net/HTTPClient.h index eb1338cabc..5cf57d1bd0 100644 --- a/Common/Net/HTTPClient.h +++ b/Common/Net/HTTPClient.h @@ -212,6 +212,10 @@ public: std::vector GetCurrentProgress(); + size_t GetActiveCount() const { + return downloads_.size(); + } + private: std::vector> downloads_; // These get copied to downloads_ in Update(). It's so that callbacks can add new downloads diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 26449cb963..7f68329c7d 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -347,6 +347,8 @@ void EmuScreen::bootGame(const Path &filename) { g_OSD.Show(OSDType::MESSAGE_WARNING, gr->T("DefaultCPUClockRequired", "Warning: This game requires the CPU clock to be set to default."), 10.0f); } + Achievements::GameChanged(filename); + loadingViewColor_->Divert(0xFFFFFFFF, 0.75f); loadingViewVisible_->Divert(UI::V_VISIBLE, 0.75f); diff --git a/UI/RetroAchievements.cpp b/UI/RetroAchievements.cpp index e56b8de245..63c53325b5 100644 --- a/UI/RetroAchievements.cpp +++ b/UI/RetroAchievements.cpp @@ -15,6 +15,8 @@ #include "ext/rcheevos/include/rc_api_runtime.h" #include "ext/rcheevos/include/rc_api_user.h" #include "ext/rcheevos/include/rc_url.h" +#include "ext/rcheevos/include/rc_hash.h" +#include "ext/rcheevos/src/rhash/md5.h" #include "UI/RetroAchievements.h" #include "ext/rapidjson/include/rapidjson/document.h" @@ -46,8 +48,7 @@ #include "RA_Interface.h" #endif -// Simply wrap our current HTTP backend. -// Which will need replacement anyway for HTTPS... +// Simply wrap our current HTTP backend to fit the DuckStation-derived code. namespace Common { class HTTPDownloader { public: @@ -66,6 +67,9 @@ namespace Common { void WaitForAllRequests() { downloader_.WaitForAll(); } + bool HasAnyRequests() const { + return downloader_.GetActiveCount() > 0; + } void CreateRequest(std::string &&url, Request::Callback &&callback) { Request::Callback movedCallback = std::move(callback); downloader_.StartDownloadWithCallback(url, Path(), [=](http::Download &download) { @@ -110,6 +114,14 @@ void OSDCloseBackgroundProgressDialog(const char *str_id) { NOTICE_LOG(ACHIEVEMENTS, "Progress dialog closed: %s", str_id); } +void OSDAddKeyedMessage(const char *str_id, std::string message, float duration) { + NOTICE_LOG(ACHIEVEMENTS, "Keyed message: %s %s (%0.1f s)", str_id, message.c_str(), duration); +} + +void OSDDisplayLoadingScreen(const char *text) { + +} + namespace Host { void OnAchievementsRefreshed() { System_PostUIMessage("achievements_refreshed", ""); @@ -168,7 +180,7 @@ static void GetUserUnlocks(); static void GetPatchesCallback(s32 status_code, std::string content_type, Common::HTTPDownloader::Request::Data data); static void GetLbInfoCallback(s32 status_code, std::string content_type, Common::HTTPDownloader::Request::Data data); static void GetPatches(u32 game_id); -static std::string GetGameHash(CDImage *image); +static std::string GetGameHash(const Path &path); static void SetChallengeMode(bool enabled); static void SendGetGameId(); static void GetGameIdCallback(s32 status_code, std::string content_type, Common::HTTPDownloader::Request::Data data); @@ -199,7 +211,7 @@ static std::unique_ptr s_http_downloader; static std::string s_username; static std::string s_api_token; -static std::string s_game_path; +static Path s_game_path; static std::string s_game_hash; static std::string s_game_title; static std::string s_game_icon; @@ -453,8 +465,8 @@ void Achievements::ClearGameInfo(bool clear_achievements, bool clear_leaderboard void Achievements::ClearGameHash() { - s_game_path = {}; - std::string().swap(s_game_hash); + s_game_path.clear(); + s_game_hash.clear(); } std::string Achievements::GetUserAgent() @@ -547,6 +559,9 @@ void Achievements::Initialize() s_api_token = g_Config.sAchievementsToken; s_logged_in = (!s_username.empty() && !s_api_token.empty()); + // this is just the non-SSL path. + rc_api_set_host("http://retroachievements.org"); + // if (System::IsValid()) // GameChanged(); } @@ -1389,13 +1404,27 @@ void Achievements::GetPatches(u32 game_id) request.Send(GetPatchesCallback); } -std::string Achievements::GetGameHash(CDImage *image) +std::string Achievements::GetGameHash(const Path &path) { // According to https://docs.retroachievements.org/Game-Identification/, we should simply // concatenate param.sfo and eboot.bin, and hash the result, to obtain the game hash. + // UNFORTUNATELY, it's borked. Turns out that retroarch's rc_hash_cd_file is broken and will read + // outside the last sector in every case. Doubly unfortunately, all the hashes on retroachievements + // are generated like that. Oh well. + + // We will need to reimplement it properly (hash some zeroes I guess, below) to handle file types + // that the cdreader can't handle (or we make a custom cdreader) but for now we just return orig_hash_str. + + rc_hash_init_default_cdreader(); + + char orig_hash_str[33]{}; + std::string ppath = path.ToCString(); + + rc_hash_generate_from_file(orig_hash_str, RC_CONSOLE_PSP, ppath.c_str()); + const char *paramSFO = "disc0:/PSP_GAME/PARAM.SFO"; - const char *ebootBIN = "disc0:/PSP_GAME/EBOOT.BIN"; + const char *ebootBIN = "disc0:/PSP_GAME/SYSDIR/EBOOT.BIN"; std::vector paramSFOContents; std::vector ebootContents; @@ -1404,21 +1433,28 @@ std::string Achievements::GetGameHash(CDImage *image) pspFileSystem.ReadEntireFile(ebootBIN, ebootContents); uint8_t hash[16]{}; + md5_state_t md5; + md5_init(&md5); + md5_append(&md5, paramSFOContents.data(), (int)paramSFOContents.size()); + md5_append(&md5, ebootContents.data(), (int)ebootContents.size()); + md5_finish(&md5, hash); + /* md5_context md5ctx{}; ppsspp_md5_starts(&md5ctx); ppsspp_md5_update(&md5ctx, paramSFOContents.data(), (int)paramSFOContents.size()); ppsspp_md5_update(&md5ctx, ebootContents.data(), (int)ebootContents.size()); ppsspp_md5_finish(&md5ctx, hash); + */ - // digest.Final(hash); + // This is straight from rc_hash_finalize size_t hash_size = 0; std::string hash_str(StringFromFormat( "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", hash[0], hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7], hash[8], hash[9], hash[10], hash[11], hash[12], hash[13], hash[14], hash[15])); - INFO_LOG(ACHIEVEMENTS, "Hash for '%s' & '%s' (%zu bytes, %u bytes hashed): %s", paramSFO, ebootBIN, (int)paramSFOContents.size(), (int)ebootContents.size()); - return hash_str; + INFO_LOG(ACHIEVEMENTS, "Hash for '%s' & '%s' (%u bytes, %u bytes hashed): %s", paramSFO, ebootBIN, (int)paramSFOContents.size(), (int)ebootContents.size(), hash_str.c_str()); + return std::string(orig_hash_str); } void Achievements::GetGameIdCallback(s32 status_code, std::string content_type, @@ -1434,7 +1470,7 @@ void Achievements::GetGameIdCallback(s32 status_code, std::string content_type, return; const u32 game_id = response.game_id; - INFO_LOG(ACHIEVEMENTS, "Server returned GameID % u", game_id); + NOTICE_LOG(ACHIEVEMENTS, "Server returned GameID %u", game_id); if (game_id == 0) { // We don't want to block saving/loading states when there's no achievements. @@ -1449,66 +1485,29 @@ void Achievements::LeftGame() { // Should just uninitialize } -void Achievements::GameChanged() +void Achievements::GameChanged(const Path &path) { - /* if (!IsActive() || s_game_path == path) return; - std::unique_ptr temp_image; - if (!path.empty() && (!image || (g_settings.achievements_use_first_disc_from_playlist && image->HasSubImages() && - image->GetCurrentSubImage() != 0))) - { - temp_image = CDImage::Open(path.c_str(), g_settings.cdrom_load_image_patches, nullptr); - image = temp_image.get(); - if (!temp_image) - { - ERROR_LOG(ACHIEVEMENTS, "Failed to open temporary CD image '%s'", path.c_str()); - s_http_downloader->WaitForAllRequests(); - std::unique_lock lock(s_achievements_mutex); - DisableChallengeMode(); - ClearGameInfo(); - return; - } - } - std::string game_hash; - if (image) + + game_hash = GetGameHash(path); + if (s_game_hash == game_hash) { - game_hash = GetGameHash(image); - if (s_game_hash == game_hash) - { - // only the path has changed - different format/save state/etc. - INFO_LOG(ACHIEVEMENTS, "Detected path change from '%s' to '%s'", s_game_path.c_str(), path.c_str()); - s_game_path = path; - return; - } + // only the path has changed - different format/save state/etc. + INFO_LOG(ACHIEVEMENTS, "Detected path change from '%s' to '%s'", s_game_path.c_str(), path.c_str()); + s_game_path = path; + return; } if (!IsUsingRAIntegration() && s_http_downloader->HasAnyRequests()) { - if (image) - Host::DisplayLoadingScreen("Downloading achievements data..."); + OSDDisplayLoadingScreen("Downloading achievements data..."); s_http_downloader->WaitForAllRequests(); } - if (image && image->HasSubImages() && image->GetCurrentSubImage() != 0) - { - std::unique_ptr image_copy( - CDImage::Open(image->GetFileName().c_str(), g_settings.cdrom_load_image_patches, nullptr)); - if (!image_copy) - { - ERROR_LOG(ACHIEVEMENTS, "Failed to reopen image '%s'", image->GetFileName().c_str()); - return; - } - - // this will go to subimage zero automatically - Assert(image_copy->GetCurrentSubImage() == 0); - GameChanged(path, image_copy.get()); - return; - } - std::unique_lock lock(s_achievements_mutex); if (!IsUsingRAIntegration()) s_http_downloader->WaitForAllRequests(); @@ -1531,7 +1530,7 @@ void Achievements::GameChanged() // when we're booting the bios, this will fail if (!s_game_path.empty()) { - Host::AddKeyedOSDMessage("retroachievements_disc_read_failed", + OSDAddKeyedMessage("retroachievements_disc_read_failed", "Failed to read executable from disc. Achievements disabled.", 10.0f); } @@ -1541,7 +1540,6 @@ void Achievements::GameChanged() if (IsLoggedIn()) SendGetGameId(); - */ } void Achievements::SendGetGameId() diff --git a/UI/RetroAchievements.h b/UI/RetroAchievements.h index 99d6fc1eaf..c410d904ac 100644 --- a/UI/RetroAchievements.h +++ b/UI/RetroAchievements.h @@ -20,7 +20,7 @@ #define ALWAYS_INLINE __forceinline -class CDImage; +class Path; class PointerWrap; namespace Achievements { @@ -127,7 +127,7 @@ bool LoginAsync(const char *username, const char *password); bool Login(const char *username, const char *password); void Logout(); -void GameChanged(); +void GameChanged(const Path &path); void LeftGame(); /// Re-enables hardcode mode if it is enabled in the settings.