Get it to recognize the hash by using the rcheevos hash implementation

This commit is contained in:
Henrik Rydgård 2023-06-17 01:52:28 +02:00
parent a3b7e99259
commit f6fdaa4f56
6 changed files with 80 additions and 62 deletions

View File

@ -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 {

View File

@ -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!

View File

@ -212,6 +212,10 @@ public:
std::vector<float> GetCurrentProgress();
size_t GetActiveCount() const {
return downloads_.size();
}
private:
std::vector<std::shared_ptr<Download>> downloads_;
// These get copied to downloads_ in Update(). It's so that callbacks can add new downloads

View File

@ -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);

View File

@ -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<Common::HTTPDownloader> 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<uint8_t> paramSFOContents;
std::vector<uint8_t> 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,33 +1485,14 @@ 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<CDImage> 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(image);
game_hash = GetGameHash(path);
if (s_game_hash == game_hash)
{
// only the path has changed - different format/save state/etc.
@ -1483,32 +1500,14 @@ void Achievements::GameChanged()
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<CDImage> 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()

View File

@ -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.