diff --git a/CMakeLists.txt b/CMakeLists.txt index 12e86d4..d659a4d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,10 @@ cmake_minimum_required(VERSION 3.4) set(CMAKE_CXX_STANDARD 17) +# TODO: This shouldn't be necessary. +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd") + project(Kiseki.Patcher VERSION 1.0.0) option(COMPILE_PLAYER "Include player-specific code" OFF) diff --git a/Kiseki.Patcher/CMakeLists.txt b/Kiseki.Patcher/CMakeLists.txt index b322ad0..2b48ff7 100644 --- a/Kiseki.Patcher/CMakeLists.txt +++ b/Kiseki.Patcher/CMakeLists.txt @@ -26,6 +26,12 @@ if(COMPILE_PLAYER OR COMPILE_SERVER) list(APPEND SOURCE Source/Hooks/CRoblox.cpp) list(APPEND HEADER Header/Hooks/CRoblox.hpp) + if(COMPILE_PLAYER) + # Discord Rich Presence integration + list(APPEND SOURCE Source/Discord.cpp) + list(APPEND HEADER Header/Discord.hpp) + endif() + if(COMPILE_SERVER) # Hook ServerReplicator list(APPEND SOURCE Source/Hooks/ServerReplicator.cpp) @@ -47,6 +53,11 @@ target_link_libraries(Kiseki.Patcher PRIVATE CURL::libcurl ${DETOURS_LIBRARY} ra # Target-specific linking and compile options if(COMPILE_PLAYER) + find_path(DISCORD_RPC_INCLUDE_DIRS "discord_rpc.h") + find_library(DISCORD_RPC_LIBRARY discord-rpc REQUIRED) + + target_include_directories(Kiseki.Patcher PRIVATE ${DISCORD_RPC_INCLUDE_DIRS}) + target_link_libraries(Kiseki.Patcher PRIVATE ${DISCORD_RPC_LIBRARY}) target_compile_definitions(Kiseki.Patcher PRIVATE PLAYER) endif() diff --git a/Kiseki.Patcher/Header/Discord.hpp b/Kiseki.Patcher/Header/Discord.hpp new file mode 100644 index 0000000..c5a635f --- /dev/null +++ b/Kiseki.Patcher/Header/Discord.hpp @@ -0,0 +1,23 @@ +#ifdef PLAYER + +#pragma once + +#include + +#include +#include +#include + +#include "Hooks/CRoblox.hpp" + +#include "Globals.hpp" + +class Discord { +public: + static void Initialize(const std::string joinScriptUrl); + static void Cleanup(); +private: + static void Update(); +}; + +#endif \ No newline at end of file diff --git a/Kiseki.Patcher/Header/Helpers.hpp b/Kiseki.Patcher/Header/Helpers.hpp index ddee178..b72031b 100644 --- a/Kiseki.Patcher/Header/Helpers.hpp +++ b/Kiseki.Patcher/Header/Helpers.hpp @@ -8,6 +8,7 @@ #include #include +#include #include "Globals.hpp" @@ -31,4 +32,5 @@ public: static std::pair> parseURL(const std::string url); static std::string getRedirectURL(const std::string url); static std::pair httpGet(const std::string url); + static std::string getBaseUrl(); }; \ No newline at end of file diff --git a/Kiseki.Patcher/Header/Hooks/CRoblox.hpp b/Kiseki.Patcher/Header/Hooks/CRoblox.hpp index 06db5d0..6038f7a 100644 --- a/Kiseki.Patcher/Header/Hooks/CRoblox.hpp +++ b/Kiseki.Patcher/Header/Hooks/CRoblox.hpp @@ -7,6 +7,10 @@ #include "Globals.hpp" #include "Helpers.hpp" +#ifdef PLAYER +#include "Discord.hpp" +#endif + class CWorkspace; const auto CWorkspace__ExecUrlScript = (HRESULT(__stdcall*)(CWorkspace * workspace, LPCWSTR, VARIANTARG, VARIANTARG, VARIANTARG, VARIANTARG, LPVOID))ADDRESS_CWORKSPACE__EXECURLSCRIPT; diff --git a/Kiseki.Patcher/Source/Discord.cpp b/Kiseki.Patcher/Source/Discord.cpp new file mode 100644 index 0000000..df4ef3e --- /dev/null +++ b/Kiseki.Patcher/Source/Discord.cpp @@ -0,0 +1,122 @@ +#ifdef PLAYER + +#include "Discord.hpp" + +bool isRunning = false; +std::string username; +int placeId; + +void Discord::Initialize(const std::string joinScriptUrl) +{ + std::pair> parsed = Helpers::parseURL(joinScriptUrl); + + if (!parsed.first) + { + return; + } + + if (parsed.second["query"].empty()) + { + return; + } + + std::map query = Helpers::parseQueryString(parsed.second["query"]); + + if (query.find("ticket") == query.end()) + { + return; + } + + if (query.find("discord") == query.end()) + { + return; + } + + if (query["ticket"].empty()) + { + return; + } + + if (query["discord"] == "0") + { + return; + } + + // Get the username and placeId + std::pair response = Helpers::httpGet(Helpers::getBaseUrl() + std::string("/api/places/information?ticket=" + query["ticket"])); + if (!response.first) + { + return; + } + + rapidjson::Document document; + document.Parse(response.second.c_str()); + + if (document.HasParseError() || !document.HasMember("username") || !document.HasMember("placeId")) + { + return; + } + + username = document["username"].GetString(); + placeId = document["placeId"].GetInt(); + + Discord::Update(); + + // Run the updater + std::thread updater(Discord::Update); + updater.join(); +} + +void Discord::Update() +{ + isRunning = true; + + while (isRunning) + { + std::this_thread::sleep_for(std::chrono::milliseconds(60 * 1000)); + + std::string title = ""; + int size = 0; + int max = 0; + + // Get title, size, and max + std::pair response = Helpers::httpGet(Helpers::getBaseUrl() + std::string("/api/places/information?id=" + placeId)); + if (!response.first) + { + return; + } + + rapidjson::Document document; + document.Parse(response.second.c_str()); + + if (document.HasParseError() || !document.HasMember("title") || !document.HasMember("size") || !document.HasMember("max")) + { + return; + } + + title = document["title"].GetString(); + size = document["size"].GetInt(); + max = document["max"].GetInt(); + + DiscordRichPresence presence; + memset(&presence, 0, sizeof(presence)); + + presence.largeImageText = username.c_str(); + presence.largeImageKey = "logo"; + presence.smallImageText = "2011"; + presence.smallImageKey = "2011"; + + presence.details = title.c_str(); + presence.state = "In a game"; + presence.partySize = size; + presence.partyMax = max; + } +} + +void Discord::Cleanup() +{ + isRunning = false; + Discord_Shutdown(); +} + +#endif \ No newline at end of file diff --git a/Kiseki.Patcher/Source/Helpers.cpp b/Kiseki.Patcher/Source/Helpers.cpp index 637dfb0..2a5e5d4 100644 --- a/Kiseki.Patcher/Source/Helpers.cpp +++ b/Kiseki.Patcher/Source/Helpers.cpp @@ -250,3 +250,27 @@ std::pair Helpers::httpGet(const std::string url) return std::make_pair(true, data); } + +std::string Helpers::getBaseUrl() +{ + std::string path = Helpers::getModulePath(); + path = path.substr(0, path.find_last_of("\\/")); + + pugi::xml_document document; + pugi::xml_parse_result result = document.load_file((path + "\\AppSettings.xml").c_str()); + + if (!result) + { + return ""; + } + + pugi::xml_node settings = document.child("Settings"); + pugi::xml_node baseUrl = settings.child("BaseUrl"); + + if (!baseUrl) + { + return ""; + } + + return baseUrl.child_value(); +} \ No newline at end of file diff --git a/Kiseki.Patcher/Source/Hooks/CRoblox.cpp b/Kiseki.Patcher/Source/Hooks/CRoblox.cpp index 7c73baa..5c7d0fa 100644 --- a/Kiseki.Patcher/Source/Hooks/CRoblox.cpp +++ b/Kiseki.Patcher/Source/Hooks/CRoblox.cpp @@ -79,6 +79,10 @@ void __fastcall CRobloxCommandLineInfo__ParseParam_hook(CRobloxCommandLineInfo* _this->m_bRunAutomated = TRUE; CCommandLineInfo__ParseLast(_this, bLast); + +#ifdef PLAYER + Discord::Initialize(Helpers::ws2s(joinScriptUrl)); +#endif } if (hasAuthenticationUrl && authenticationUrl.empty()) diff --git a/Kiseki.Patcher/Source/main.cpp b/Kiseki.Patcher/Source/main.cpp index 53a3e2e..a56db09 100644 --- a/Kiseki.Patcher/Source/main.cpp +++ b/Kiseki.Patcher/Source/main.cpp @@ -3,6 +3,10 @@ #include "Globals.hpp" #include "Patcher.hpp" +#ifdef PLAYER +#include "Discord.hpp" +#endif + #include "Hooks/Http.hpp" #include "Hooks/Crypt.hpp" #include "Hooks/CRoblox.hpp" @@ -60,6 +64,10 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv if (ul_reason_for_call == DLL_PROCESS_DETACH) { curl_global_cleanup(); + +#ifdef PLAYER + Discord::Cleanup(); +#endif } return TRUE; diff --git a/vcpkg.json b/vcpkg.json index c51d25a..339d7a0 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -5,6 +5,7 @@ "dependencies": [ "curl", "detours", + "discord-rpc", "rapidjson" ] } \ No newline at end of file