Skip to content

Commit

Permalink
RetroPlayer: Add Achievements
Browse files Browse the repository at this point in the history
  • Loading branch information
Shardul555 committed Oct 12, 2021
1 parent 5f72008 commit 16ad08b
Show file tree
Hide file tree
Showing 10 changed files with 185 additions and 17 deletions.
2 changes: 1 addition & 1 deletion tools/buildsteps/windows/make-addons.bat
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ IF "%store%" NEQ "" (
)

rem execute cmake to generate makefiles processable by nmake
cmake "%ADDONS_PATH%" -G "NMake Makefiles" ^
cmake "%ADDONS_PATH%" -G "Visual Studio 16 2019" -A x64 ^
-DCMAKE_BUILD_TYPE=Release ^
-DCMAKE_USER_MAKE_RULES_OVERRIDE="%SCRIPTS_PATH%/CFlagOverrides.cmake" ^
-DCMAKE_USER_MAKE_RULES_OVERRIDE_CXX="%SCRIPTS_PATH%/CXXFlagOverrides.cmake" ^
Expand Down
43 changes: 42 additions & 1 deletion xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/Game.h
Original file line number Diff line number Diff line change
Expand Up @@ -967,6 +967,12 @@ class ATTRIBUTE_HIDDEN CInstanceGame : public IAddonInstance
return GAME_ERROR_NOT_IMPLEMENTED;
}

virtual GAME_ERROR SetRetroAchievementsCredentials(const char* username,
const char* token)
{
return GAME_ERROR_NOT_IMPLEMENTED;
}

virtual GAME_ERROR RCPostRichPresenceUrl(char* url,
size_t urlSize,
char* postData,
Expand All @@ -985,6 +991,16 @@ class ATTRIBUTE_HIDDEN CInstanceGame : public IAddonInstance
}

virtual GAME_ERROR GetRichPresenceEvaluation(char* evaluation, size_t size)
{
return GAME_ERROR_NOT_IMPLEMENTED;
}

virtual GAME_ERROR ActivateAchievement(unsigned cheevo_id, const char* memaddr)
{
return GAME_ERROR_NOT_IMPLEMENTED;
}

virtual GAME_ERROR GetCheevo_URL_ID(void (*Callback)(const char* achievement_url, unsigned cheevo_id))
{
return GAME_ERROR_NOT_IMPLEMENTED;
}
Expand Down Expand Up @@ -1041,9 +1057,12 @@ class ATTRIBUTE_HIDDEN CInstanceGame : public IAddonInstance
m_instanceData->toAddon->RCGenerateHashFromFile = ADDON_RCGenerateHashFromFile;
m_instanceData->toAddon->RCGetGameIDUrl = ADDON_RCGetGameIDUrl;
m_instanceData->toAddon->RCGetPatchFileUrl = ADDON_RCGetPatchFileUrl;
m_instanceData->toAddon->SetRetroAchievementsCredentials = ADDON_SetRetroAchievementsCredentials;
m_instanceData->toAddon->RCPostRichPresenceUrl = ADDON_RCPostRichPresenceUrl;
m_instanceData->toAddon->EnableRichPresence = ADDON_EnableRichPresence;
m_instanceData->toAddon->GetRichPresenceEvaluation = ADDON_GetRichPresenceEvaluation;
m_instanceData->toAddon->ActivateAchievement = ADDON_ActivateAchievement;
m_instanceData->toAddon->GetCheevo_URL_ID = ADDON_GetCheevo_URL_ID;
m_instanceData->toAddon->RCResetRuntime = ADDON_RCResetRuntime;
}

Expand Down Expand Up @@ -1264,7 +1283,14 @@ class ATTRIBUTE_HIDDEN CInstanceGame : public IAddonInstance
return static_cast<CInstanceGame*>(instance->toAddon->addonInstance)
->RCGetPatchFileUrl(url, size, username, token, gameID);
}


inline static GAME_ERROR ADDON_SetRetroAchievementsCredentials(
const AddonInstance_Game* instance, const char* username, const char* token)
{
return static_cast<CInstanceGame*>(instance->toAddon->addonInstance)
->SetRetroAchievementsCredentials(username, token);
}

inline static GAME_ERROR ADDON_RCPostRichPresenceUrl(const AddonInstance_Game* instance,
char* url,
size_t urlSize,
Expand Down Expand Up @@ -1295,6 +1321,21 @@ class ATTRIBUTE_HIDDEN CInstanceGame : public IAddonInstance
->GetRichPresenceEvaluation(evaluation, size);
}

inline static GAME_ERROR ADDON_ActivateAchievement(const AddonInstance_Game* instance,
unsigned cheevo_id,
const char* memaddr)
{
return static_cast<CInstanceGame*>(instance->toAddon->addonInstance)
->ActivateAchievement(cheevo_id, memaddr);
}

inline static GAME_ERROR ADDON_GetCheevo_URL_ID(const AddonInstance_Game* instance,
void (*Callback)(const char* achievement_url,
unsigned cheevo_id))
{
return static_cast<CInstanceGame*>(instance->toAddon->addonInstance)->GetCheevo_URL_ID(Callback);
}

inline static GAME_ERROR ADDON_RCResetRuntime(const AddonInstance_Game* instance)
{
return static_cast<CInstanceGame*>(instance->toAddon->addonInstance)->RCResetRuntime();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1193,6 +1193,8 @@ extern "C"
GAME_ERROR(__cdecl* RCGetGameIDUrl)(const AddonInstance_Game*, char*, size_t, const char*);
GAME_ERROR(__cdecl* RCGetPatchFileUrl)
(const AddonInstance_Game*, char*, size_t, const char*, const char*, unsigned);
GAME_ERROR(__cdecl* SetRetroAchievementsCredentials)
(const AddonInstance_Game*, const char*, const char*);
GAME_ERROR(__cdecl* RCPostRichPresenceUrl)
(const AddonInstance_Game*,
char*,
Expand All @@ -1205,6 +1207,10 @@ extern "C"
const char*);
GAME_ERROR(__cdecl* EnableRichPresence)(const AddonInstance_Game*, const char*);
GAME_ERROR(__cdecl* GetRichPresenceEvaluation)(const AddonInstance_Game*, char*, size_t);

GAME_ERROR(__cdecl* ActivateAchievement)(const AddonInstance_Game*, unsigned, const char*);
GAME_ERROR(__cdecl* GetCheevo_URL_ID)
(const AddonInstance_Game*, void (*Callback)(const char* achievement_url, unsigned cheevo_id));
GAME_ERROR(__cdecl* RCResetRuntime)(const AddonInstance_Game*);
} KodiToAddonFuncTable_Game;

Expand Down
4 changes: 2 additions & 2 deletions xbmc/addons/kodi-dev-kit/include/kodi/versions.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@
#define ADDON_INSTANCE_VERSION_AUDIOENCODER_DEPENDS "c-api/addon-instance/audio_encoder.h" \
"addon-instance/AudioEncoder.h"

#define ADDON_INSTANCE_VERSION_GAME "2.1.0"
#define ADDON_INSTANCE_VERSION_GAME_MIN "2.1.0"
#define ADDON_INSTANCE_VERSION_GAME "4.0.0"
#define ADDON_INSTANCE_VERSION_GAME_MIN "4.0.0"
#define ADDON_INSTANCE_VERSION_GAME_XML_ID "kodi.binary.instance.game"
#define ADDON_INSTANCE_VERSION_GAME_DEPENDS "addon-instance/Game.h"

Expand Down
1 change: 1 addition & 0 deletions xbmc/cores/RetroPlayer/RetroPlayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ bool CRetroPlayer::OpenFile(const CFileItem& file, const CPlayerOptions& options

m_cheevos->EnableRichPresence();

m_cheevos->ActivateAchievement();
// Calls to external code could mutate file item, so make a copy
CFileItem fileCopy(*m_fileItem);

Expand Down
79 changes: 72 additions & 7 deletions xbmc/cores/RetroPlayer/cheevos/Cheevos.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
#include "utils/auto_buffer.h"
#include "utils/log.h"

#include "xbmc/dialogs/GUIDialogKaiToast.h"
#include<vector>
#include<unordered_map>

using namespace KODI;
using namespace RETRO;

Expand All @@ -36,11 +40,20 @@ constexpr auto GENRE = "Genre";
constexpr auto CONSOLE_NAME = "ConsoleName";
//constexpr auto RELEASED = "Released"; // TODO: Currently unused

constexpr auto ACHIEVEMENTS = "Achievements";
constexpr auto MEM_ADDR = "MemAddr";
constexpr auto CHEEVO_ID = "ID";
constexpr auto FLAGS = "Flags";
constexpr auto CHEEVO_TITLE = "Title";
constexpr auto BADGE_NAME = "BadgeName";

constexpr int HASH_SIZE = 33;
constexpr int RESPORNSE_SIZE = 64;
constexpr int URL_SIZE = 512;
constexpr int POST_DATA_SIZE = 1024;

std::unordered_map<unsigned, vector<std::string>> CCheevos::activated_cheevo_map;

CCheevos::CCheevos(GAME::CGameClient* gameClient,
CFileItem& fileItem,
std::string userName,
Expand All @@ -50,6 +63,7 @@ CCheevos::CCheevos(GAME::CGameClient* gameClient,
m_userName(std::move(userName)),
m_loginToken(std::move(loginToken))
{
m_gameClient->SetRetroAchievementsCredentials(m_userName.c_str(), m_loginToken.c_str());
}

void CCheevos::ResetRuntime()
Expand All @@ -76,7 +90,6 @@ bool CCheevos::LoadData()
{
return false;
}

m_romHash = hash;
}

Expand All @@ -96,20 +109,18 @@ bool CCheevos::LoadData()

CVariant data(CVariant::VariantTypeObject);
CJSONVariantParser::Parse(responseStr, data);

if (!data[SUCCESS].asBoolean())
return false;

m_gameID = data[GAME_ID].asUnsignedInteger32();

// For some reason RetroAchievements returns Success = true when the hash isn't found
if (m_gameID == 0)
return false;

if (!m_gameClient->RCGetPatchFileUrl(requestURL, URL_SIZE, m_userName.c_str(),
m_loginToken.c_str(), m_gameID))
return false;

m_loginToken.c_str(), m_gameID))
return false;
CURL curl(requestURL);
XUTILS::auto_buffer patchData;
response.LoadFile(curl, patchData);
Expand All @@ -123,6 +134,22 @@ bool CCheevos::LoadData()
m_richPresenceScript = data[PATCH_DATA][RICH_PRESENCE].asString();
m_richPresenceLoaded = true;

const CVariant& achievements = data[PATCH_DATA][ACHIEVEMENTS];
for (auto it = achievements.begin_array(); it != achievements.end_array(); it++)
{
const CVariant& achievement = *it;
if (achievement[FLAGS].asUnsignedInteger() == 3)
{
activated_cheevo_map[achievement[CHEEVO_ID].asUnsignedInteger()] = {
achievement[MEM_ADDR].asString(), achievement[CHEEVO_TITLE].asString(),
achievement[BADGE_NAME].asString()};
}
else
{
CLog::Log(LOGINFO, "We are not considering unofficial achievements");
}
}

GAME::CGameInfoTag& tag = *m_fileItem.GetGameInfoTag();

tag.SetTitle(data[PATCH_DATA][GAME_TITLE].asString());
Expand All @@ -144,11 +171,32 @@ void CCheevos::EnableRichPresence()
return;
}
}

m_gameClient->EnableRichPresence(m_richPresenceScript.c_str());
m_richPresenceScript.clear();
}

void CCheevos::ActivateAchievement()
{
if (activated_cheevo_map.size() == 0)
{
if (!LoadData())
{
CLog::Log(LOGERROR, "Cheevos: Couldn't load patch file");
return;
}
else
{
CLog::Log(LOGERROR, "No active core achievement for the game");
}
}
for (auto &it:activated_cheevo_map)
{
m_gameClient->ActivateAchievement(it.first, it.second[0].c_str());
}
//call for checking triggered achievement
CheckTriggeredAchievement();
}

bool CCheevos::GetRichPresenceEvaluation(char* evaluation, size_t size)
{
if (!m_richPresenceLoaded)
Expand Down Expand Up @@ -180,3 +228,20 @@ bool CCheevos::GetRichPresenceEvaluation(char* evaluation, size_t size)

return true;
}

void CCheevos::Callback_URL_ID(const char* achievement_url, unsigned cheevo_id)
{
XFILE::CCurlFile curl;
std::string res;
curl.Get(achievement_url, res);
std::string description = activated_cheevo_map[cheevo_id][1];
std::string header =
std::string("Congratulations, ") + std::string("Achievement Unlocked");

CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, header, description);
}

void CCheevos::CheckTriggeredAchievement()
{
m_gameClient->GetAchievement_URL_ID(Callback_URL_ID); // Callback for triggered achievement URL and id
}
12 changes: 11 additions & 1 deletion xbmc/cores/RetroPlayer/cheevos/Cheevos.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@

#include <map>
#include <string>
#include <vector>
#include <unordered_map>
using std::vector;

class CFileItem;

Expand All @@ -35,9 +38,15 @@ class CCheevos
void EnableRichPresence();
bool GetRichPresenceEvaluation(char* evaluation, size_t size);

void ActivateAchievement();
static void Callback_URL_ID(const char* achievement_url, unsigned cheevo_id);
void CheckTriggeredAchievement();

static std::unordered_map<unsigned, vector<std::string> > activated_cheevo_map;

private:
bool LoadData();

// Construction parameters
GAME::CGameClient* const m_gameClient;
CFileItem& m_fileItem;
Expand All @@ -49,6 +58,7 @@ class CCheevos
std::string m_richPresenceScript;
unsigned m_gameID = 0;
bool m_richPresenceLoaded = false;


const std::map<std::string, int> m_extensionToConsole = {{".a26", RC_CONSOLE_ATARI_2600},
{".a78", RC_CONSOLE_ATARI_7800},
Expand Down
43 changes: 43 additions & 0 deletions xbmc/games/addons/GameClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,20 @@ bool CGameClient::RCPostRichPresenceUrl(char* url,
return true;
}

void CGameClient::SetRetroAchievementsCredentials(const char* username, const char* token)
{
GAME_ERROR error = GAME_ERROR_NO_ERROR;
try
{
LogError(error = m_struct.toAddon->SetRetroAchievementsCredentials(&m_struct, username, token),
"SetRetroAchievementsCredentials");
}
catch(...)
{
LogException("SetRetroAchievementsCredentials");
}
}

void CGameClient::EnableRichPresence(const char* script)
{
GAME_ERROR error = GAME_ERROR_NO_ERROR;
Expand Down Expand Up @@ -672,6 +686,35 @@ void CGameClient::GetRichPresenceEvaluation(char*& evaluation, size_t size)
}
}

void CGameClient::ActivateAchievement(unsigned cheevo_id, const char* memaddr)
{
GAME_ERROR error = GAME_ERROR_NO_ERROR;

try
{
LogError(error = m_struct.toAddon->ActivateAchievement(&m_struct, cheevo_id, memaddr),
"ActivateAchievement()");
}
catch (...)
{
LogException("ActivateAchievement()");
}
}

void CGameClient::GetAchievement_URL_ID(void (*Callback)(const char* achievement_url, unsigned cheevo_id))
{
GAME_ERROR error = GAME_ERROR_NO_ERROR;

try
{
LogError(error = m_struct.toAddon->GetCheevo_URL_ID(&m_struct, Callback), "GetCheevo_ID_URL()");
}
catch (...)
{
LogException("GetCheevoID()");
}
}

void CGameClient::RCResetRuntime()
{
GAME_ERROR error = GAME_ERROR_NO_ERROR;
Expand Down
8 changes: 6 additions & 2 deletions xbmc/games/addons/GameClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,8 @@ class CGameClient : public ADDON::CAddonDll, private CGameClientStruct
// RCheevos
bool RCGenerateHashFromFile(char* hash, int consoleID, const char* filePath);
bool RCGetGameIDUrl(char* url, size_t size, const char* hash);
bool RCGetPatchFileUrl(
char* url, size_t size, const char* username, const char* token, unsigned gameID);
bool RCGetPatchFileUrl(char* url, size_t size, const char* username, const char* token, unsigned gameID);
void SetRetroAchievementsCredentials(const char* username, const char* token);
bool RCPostRichPresenceUrl(char* url,
size_t urlSize,
char* postData,
Expand All @@ -159,6 +159,10 @@ class CGameClient : public ADDON::CAddonDll, private CGameClientStruct
void EnableRichPresence(const char* script);
void GetRichPresenceEvaluation(char*& evaluation, size_t size);
// When the game is reset, the runtime should also be reset

void ActivateAchievement(unsigned cheevo_id, const char* memaddr);
void GetAchievement_URL_ID(void (*Callback)(const char* achievement_url, unsigned cheevo_id));

void RCResetRuntime();

/*!
Expand Down
Loading

0 comments on commit 16ad08b

Please sign in to comment.