From 16ad08b93c2a69de420b1913f3229be46c38adbe Mon Sep 17 00:00:00 2001 From: Shardul555 Date: Sat, 21 Aug 2021 13:51:00 +0530 Subject: [PATCH] RetroPlayer: Add Achievements --- tools/buildsteps/windows/make-addons.bat | 2 +- .../include/kodi/addon-instance/Game.h | 43 +++++++++- .../include/kodi/c-api/addon-instance/game.h | 6 ++ .../kodi-dev-kit/include/kodi/versions.h | 4 +- xbmc/cores/RetroPlayer/RetroPlayer.cpp | 1 + xbmc/cores/RetroPlayer/cheevos/Cheevos.cpp | 79 +++++++++++++++++-- xbmc/cores/RetroPlayer/cheevos/Cheevos.h | 12 ++- xbmc/games/addons/GameClient.cpp | 43 ++++++++++ xbmc/games/addons/GameClient.h | 8 +- xbmc/interfaces/swig/CMakeLists.txt | 4 +- 10 files changed, 185 insertions(+), 17 deletions(-) diff --git a/tools/buildsteps/windows/make-addons.bat b/tools/buildsteps/windows/make-addons.bat index b8e0ac59ac7c2..d53c3a1e46960 100644 --- a/tools/buildsteps/windows/make-addons.bat +++ b/tools/buildsteps/windows/make-addons.bat @@ -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" ^ diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/Game.h b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/Game.h index 1d9f1f7d2e746..137a5a6ba06af 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/Game.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/addon-instance/Game.h @@ -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, @@ -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; } @@ -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; } @@ -1264,7 +1283,14 @@ class ATTRIBUTE_HIDDEN CInstanceGame : public IAddonInstance return static_cast(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(instance->toAddon->addonInstance) + ->SetRetroAchievementsCredentials(username, token); + } + inline static GAME_ERROR ADDON_RCPostRichPresenceUrl(const AddonInstance_Game* instance, char* url, size_t urlSize, @@ -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(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(instance->toAddon->addonInstance)->GetCheevo_URL_ID(Callback); + } + inline static GAME_ERROR ADDON_RCResetRuntime(const AddonInstance_Game* instance) { return static_cast(instance->toAddon->addonInstance)->RCResetRuntime(); diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/game.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/game.h index e685dfc79e9e7..a4cb7287786a5 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/game.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/game.h @@ -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*, @@ -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; diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/versions.h b/xbmc/addons/kodi-dev-kit/include/kodi/versions.h index 70683fc3aa11e..4b3c9b155f993 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/versions.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/versions.h @@ -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" diff --git a/xbmc/cores/RetroPlayer/RetroPlayer.cpp b/xbmc/cores/RetroPlayer/RetroPlayer.cpp index a3982ce421cba..19c6e48dd8b84 100644 --- a/xbmc/cores/RetroPlayer/RetroPlayer.cpp +++ b/xbmc/cores/RetroPlayer/RetroPlayer.cpp @@ -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); diff --git a/xbmc/cores/RetroPlayer/cheevos/Cheevos.cpp b/xbmc/cores/RetroPlayer/cheevos/Cheevos.cpp index fe5f3a0cff1b6..3bb98ca1aefe4 100644 --- a/xbmc/cores/RetroPlayer/cheevos/Cheevos.cpp +++ b/xbmc/cores/RetroPlayer/cheevos/Cheevos.cpp @@ -21,6 +21,10 @@ #include "utils/auto_buffer.h" #include "utils/log.h" +#include "xbmc/dialogs/GUIDialogKaiToast.h" +#include +#include + using namespace KODI; using namespace RETRO; @@ -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> CCheevos::activated_cheevo_map; + CCheevos::CCheevos(GAME::CGameClient* gameClient, CFileItem& fileItem, std::string userName, @@ -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() @@ -76,7 +90,6 @@ bool CCheevos::LoadData() { return false; } - m_romHash = hash; } @@ -96,10 +109,8 @@ 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 @@ -107,9 +118,9 @@ bool CCheevos::LoadData() 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); @@ -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()); @@ -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) @@ -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 +} \ No newline at end of file diff --git a/xbmc/cores/RetroPlayer/cheevos/Cheevos.h b/xbmc/cores/RetroPlayer/cheevos/Cheevos.h index e8d3d4ea1937b..376ba12f06d38 100644 --- a/xbmc/cores/RetroPlayer/cheevos/Cheevos.h +++ b/xbmc/cores/RetroPlayer/cheevos/Cheevos.h @@ -12,6 +12,9 @@ #include #include +#include +#include +using std::vector; class CFileItem; @@ -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 > activated_cheevo_map; + private: bool LoadData(); - + // Construction parameters GAME::CGameClient* const m_gameClient; CFileItem& m_fileItem; @@ -49,6 +58,7 @@ class CCheevos std::string m_richPresenceScript; unsigned m_gameID = 0; bool m_richPresenceLoaded = false; + const std::map m_extensionToConsole = {{".a26", RC_CONSOLE_ATARI_2600}, {".a78", RC_CONSOLE_ATARI_7800}, diff --git a/xbmc/games/addons/GameClient.cpp b/xbmc/games/addons/GameClient.cpp index 91b8828d08422..37173c3de997c 100644 --- a/xbmc/games/addons/GameClient.cpp +++ b/xbmc/games/addons/GameClient.cpp @@ -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; @@ -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; diff --git a/xbmc/games/addons/GameClient.h b/xbmc/games/addons/GameClient.h index 277925c3b5fc7..cb50a7fabe80d 100644 --- a/xbmc/games/addons/GameClient.h +++ b/xbmc/games/addons/GameClient.h @@ -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, @@ -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(); /*! diff --git a/xbmc/interfaces/swig/CMakeLists.txt b/xbmc/interfaces/swig/CMakeLists.txt index 1884b2a85bdad..e17f0d8d34da2 100644 --- a/xbmc/interfaces/swig/CMakeLists.txt +++ b/xbmc/interfaces/swig/CMakeLists.txt @@ -14,16 +14,14 @@ function(generate_file file) if(CLANGFORMAT_FOUND) set(CLANG_FORMAT_COMMAND COMMAND ${CLANG_FORMAT_EXECUTABLE} ARGS -i ${CPP_FILE}) endif() - if(Java_VERSION_MAJOR GREATER 8) set(JAVA_OPEN_OPTS --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.regex=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED) endif() - add_custom_command(OUTPUT ${CPP_FILE} COMMAND ${SWIG_EXECUTABLE} ARGS -w401 -c++ -o ${file}.xml -xml -I${CMAKE_SOURCE_DIR}/xbmc -xmllang python ${CMAKE_CURRENT_SOURCE_DIR}/../swig/${file} COMMAND ${Java_JAVA_EXECUTABLE} - ARGS ${JAVA_OPEN_OPTS} -cp "${classpath}" groovy.ui.GroovyMain ${CMAKE_SOURCE_DIR}/tools/codegenerator/Generator.groovy ${file}.xml ${CMAKE_CURRENT_SOURCE_DIR}/../python/PythonSwig.cpp.template ${file}.cpp > ${devnull} + ARGS ${JAVA_OPEN_OPTS} -cp "${classpath}" groovy.ui.GroovyMain ${CMAKE_SOURCE_DIR}/tools/codegenerator/Generator.groovy ${file}.xml ${CMAKE_CURRENT_SOURCE_DIR}/../python/PythonSwig.cpp.template ${file}.cpp > ${devnull} ${CLANG_FORMAT_COMMAND} DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/../swig/${file} ${CMAKE_CURRENT_SOURCE_DIR}/../python/PythonSwig.cpp.template) set(SOURCES ${SOURCES} "${CPP_FILE}" PARENT_SCOPE)