diff --git a/Kiseki.Patcher/CMakeLists.txt b/Kiseki.Patcher/CMakeLists.txt index 438f54d..2b48ff7 100644 --- a/Kiseki.Patcher/CMakeLists.txt +++ b/Kiseki.Patcher/CMakeLists.txt @@ -43,14 +43,13 @@ add_library(Kiseki.Patcher SHARED ${SOURCE} ${HEADER}) # Packages find_package(CURL CONFIG REQUIRED) -find_package(rapidjson CONFIG REQUIRED) -find_package(pugixml CONFIG REQUIRED) +find_package(RapidJSON CONFIG REQUIRED) find_path(DETOURS_INCLUDE_DIRS "detours/detours.h") find_library(DETOURS_LIBRARY detours REQUIRED) target_include_directories(Kiseki.Patcher PRIVATE Header ${DETOURS_INCLUDE_DIRS}) -target_link_libraries(Kiseki.Patcher PRIVATE CURL::libcurl ${DETOURS_LIBRARY} pugixml::static pugixml::pugixml rapidjson) +target_link_libraries(Kiseki.Patcher PRIVATE CURL::libcurl ${DETOURS_LIBRARY} rapidjson) # Target-specific linking and compile options if(COMPILE_PLAYER) diff --git a/Kiseki.Patcher/Header/Globals.hpp b/Kiseki.Patcher/Header/Globals.hpp index 1d080a1..aad9f74 100644 --- a/Kiseki.Patcher/Header/Globals.hpp +++ b/Kiseki.Patcher/Header/Globals.hpp @@ -1,20 +1,15 @@ #pragma once -#define ALLOWED_WILDCARD_DOMAINS "kiseki.lol", "rbxcdn.com", "roblox.com" -#define ALLOWED_DOMAINS +#define ALLOWED_WILDCARD_DOMAINS "kiseki.loc", "kiseki.lol", "rbxcdn.com", "roblox.com" +#define ALLOWED_DOMAINS "sets.pizzaboxer.xyz" #define ALLOWED_SCHEMES "http", "https" #define ALLOWED_EMBEDDED_SCHEMES "javascript", "jscript", "res" -#ifdef _DEBUG -#undef ALLOWED_WILDCARD_DOMAINS -#define ALLOWED_WILDCARD_DOMAINS "kiseki.loc", "kiseki.lol", "rbxcdn.com", "roblox.com" -#endif - #define PUBLIC_KEY 0x06, 0x02, 0x00, 0x00, 0x00, 0xA4, 0x00, 0x00, 0x52, 0x53, 0x41, 0x31, 0x00, 0x04, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0xA5, 0x11, 0xD0, 0x9F, 0xAB, 0x9B, 0x3A, 0x96, 0xC5, 0xBE, 0x50, 0xBB, 0xCA, 0x0C, 0xBB, 0xC8, 0x1A, 0x9C, 0xC1, 0x2F, 0x22, 0x7A, 0x80, 0x2C, 0x31, 0x01, 0xE1, 0x21, 0xC2, 0x7F, 0xE0, 0x10, 0xA1, 0x2D, 0x09, 0xED, 0x10, 0x3E, 0x28, 0xB9, 0xBD, 0x0F, 0x38, 0xDB, 0x52, 0x78, 0xC0, 0xEC, 0x04, 0xD4, 0x00, 0xAD, 0x45, 0xD3, 0xC7, 0x78, 0xF2, 0x83, 0xB5, 0x5B, 0x16, 0x0C, 0x32, 0x5D, 0xB3, 0x3B, 0xDA, 0xA2, 0x9C, 0x73, 0xB2, 0x0C, 0x09, 0x93, 0xD8, 0xF8, 0xD9, 0xC5, 0x75, 0xAB, 0x33, 0x19, 0xD3, 0x6A, 0xAF, 0x20, 0x21, 0x6C, 0x78, 0x31, 0x41, 0xD1, 0xCD, 0x6D, 0x4D, 0xA1, 0x6D, 0x49, 0x3A, 0x6A, 0x33, 0x10, 0x6D, 0x52, 0x33, 0x4A, 0x10, 0x32, 0x3D, 0x42, 0xE6, 0xEC, 0x38, 0x97, 0x2F, 0x05, 0x41, 0xDD, 0xD6, 0xB6, 0xAC, 0x17, 0x4B, 0x7E, 0xFA, 0xFE, 0xA4, 0xD2 +#define ADDRESS_HTTP__HTTPGETPOSTWININET 0x006F03B0 #define ADDRESS_HTTP__TRUSTCHECK 0x005B7050 #define ADDRESS_CRYPT__VERIFYSIGNATUREBASE64 0x00809EC0 -#define ADDRESS_STANDARDOUT__PRINT 0x005B25E0 #define ADDRESS_SERVERREPLICATOR__SENDTOP 0x00513E80 #define ADDRESS_SERVERREPLICATOR__PROCESSTICKET 0x00514B60 diff --git a/Kiseki.Patcher/Header/Helpers.hpp b/Kiseki.Patcher/Header/Helpers.hpp index 0468832..b72031b 100644 --- a/Kiseki.Patcher/Header/Helpers.hpp +++ b/Kiseki.Patcher/Header/Helpers.hpp @@ -30,6 +30,7 @@ public: static std::string getModulePath(); static std::string getISOTimestamp(); 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/Http.hpp b/Kiseki.Patcher/Header/Hooks/Http.hpp index 068826b..e81505b 100644 --- a/Kiseki.Patcher/Header/Hooks/Http.hpp +++ b/Kiseki.Patcher/Header/Hooks/Http.hpp @@ -3,12 +3,24 @@ #include #include +#include #include "Globals.hpp" #include "Helpers.hpp" +// TODO: This breaks on Release +struct Http +{ + std::string alternateUrl; + void* padding; + std::string url; +}; + +typedef void (__thiscall* Http__httpGetPostWinInet_t)(Http* _this, bool isPost, int a3, bool compressData, LPCSTR additionalHeaders, int a6); typedef bool(__thiscall* Http__trustCheck_t)(const char* url); +void __fastcall Http__httpGetPostWinInet_hook(Http* _this, void*, bool isPost, int a3, bool compressData, LPCSTR additionalHeaders, int a6); bool __fastcall Http__trustCheck_hook(const char* url); +extern Http__httpGetPostWinInet_t Http__httpGetPostWinInet; extern Http__trustCheck_t Http__trustCheck; \ No newline at end of file diff --git a/Kiseki.Patcher/Resource/Information.h b/Kiseki.Patcher/Resource/Information.h index 7ceb66e..477ddfa 100644 --- a/Kiseki.Patcher/Resource/Information.h +++ b/Kiseki.Patcher/Resource/Information.h @@ -7,6 +7,6 @@ #define VERSION_RESOURCE_STR VERSION_FULL_STR #define APP_NAME "Kiseki.Patcher" -#define APP_DESCRIPTION "Client Functionality Library" +#define APP_DESCRIPTION "Client functionality library" #define APP_ORGANIZATION "Kiseki" -#define APP_COPYRIGHT "Copyright (c) Kiseki 2022-2023" +#define APP_COPYRIGHT "Copyright (c) Kiseki 2023" diff --git a/Kiseki.Patcher/Source/Helpers.cpp b/Kiseki.Patcher/Source/Helpers.cpp index 724c5f6..2a5e5d4 100644 --- a/Kiseki.Patcher/Source/Helpers.cpp +++ b/Kiseki.Patcher/Source/Helpers.cpp @@ -138,16 +138,55 @@ std::string Helpers::getISOTimestamp() return std::string(buffer); } +std::string Helpers::getRedirectURL(const std::string url) +{ + CURL* curl = curl_easy_init(); + CURLcode result; + + if (!curl) + { + return url; + } + + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0); + curl_easy_setopt(curl, CURLOPT_NOBODY, 1); + curl_easy_setopt(curl, CURLOPT_HEADER, 1); + + result = curl_easy_perform(curl); + + if (result != CURLE_OK) + { + return url; + } + + long response = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response); + + if (response != 301 && response != 302) + { + return url; + } + + char* redirectURL; + curl_easy_getinfo(curl, CURLINFO_REDIRECT_URL, &redirectURL); + + std::string location = redirectURL ? std::string(redirectURL) : ""; + + curl_easy_cleanup(curl); + + if (location.empty()) + { + return url; + } + + return location; +} + std::pair> Helpers::parseURL(const std::string url) { - // This is an ugly hack to url-encode the query before CURL actually parses the URL. - // We do this because CURL throws a fit if the query is not properly URL encoded; so URLs such as "/Error/Dmp.ashx?filename=C:/Users/..." won't parse correctly. - char* encodedQuery = curl_escape(url.substr(url.find("?") + 1).c_str(), url.substr(url.find("?") + 1).length()); - std::string encodedUrl = url.substr(0, url.find("?")) + encodedQuery; - curl_free(encodedQuery); - CURLU* curl = curl_url(); - CURLUcode result = curl_url_set(curl, CURLUPART_URL, encodedUrl.c_str(), 0); + CURLUcode result = curl_url_set(curl, CURLUPART_URL, url.c_str(), 0); std::map map; bool success = false; @@ -195,7 +234,7 @@ std::pair Helpers::httpGet(const std::string url) return std::make_pair(false, ""); } - curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, Helpers::write); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data); diff --git a/Kiseki.Patcher/Source/Hooks/Http.cpp b/Kiseki.Patcher/Source/Hooks/Http.cpp index 0f976b2..54f18c4 100644 --- a/Kiseki.Patcher/Source/Hooks/Http.cpp +++ b/Kiseki.Patcher/Source/Hooks/Http.cpp @@ -1,7 +1,112 @@ #include "Hooks/Http.hpp" +#define CHECK(condition, code) \ + if(!error) { \ + if ((condition)) { \ + error = code; \ + } \ + } + +Http__httpGetPostWinInet_t Http__httpGetPostWinInet = (Http__httpGetPostWinInet_t)ADDRESS_HTTP__HTTPGETPOSTWININET; Http__trustCheck_t Http__trustCheck = (Http__trustCheck_t)ADDRESS_HTTP__TRUSTCHECK; +void __fastcall Http__httpGetPostWinInet_hook(Http* _this, void*, bool isPost, int a3, bool compressData, LPCSTR additionalHeaders, int a6) +{ + std::pair> parsed = Helpers::parseURL(_this->url); + Http _changed = *_this; + + if (parsed.first) + { + std::map url = parsed.second; + + if (!url["path"].empty() && !url["host"].empty() && !url["query"].empty()) + { + url["path"] = Helpers::toLower(url["path"]); + + if (url["host"] == "roblox.com" || url["host"] == "www.roblox.com") + { + if (url["path"] == "/game/tools/insertasset.ashx") + { + _changed.url = "https://sets.pizzaboxer.xyz/" + url["path"] + "?" + url["query"]; + _this = &_changed; + } + else if (url["path"] == "/asset" || url["path"] == "/asset/" || url["path"] == "/asset/default.ashx") + { + _changed.url = "https://assetdelivery.roblox.com/v1/asset/?" + url["query"]; + _this = &_changed; + } + else if (url["path"] == "/thumbs/asset.ashx" || url["path"] == "/thumbs/avatar.ashx") + { + std::string api = "https://thumbnails.roblox.com"; + + api += url["path"] == "/thumbs/asset.ashx" ? "/v1/assets" : "/v1/users/avatar"; + + std::map source = Helpers::parseQueryString(url["query"]); + std::map fixed = {}; + + for (auto& pair : source) + { + fixed[Helpers::toLower(pair.first)] = pair.second; + } + + if (fixed.find("id") != fixed.end()) + { + auto handler = fixed.extract("id"); + handler.key() = url["path"] == "/thumbs/asset.ashx" ? "assetIds" : "userIds"; + + fixed.insert(std::move(handler)); + } + + if (fixed.find("x") != fixed.end() && fixed.find("y") != fixed.end()) + { + fixed["size"] = fixed["x"] + "x" + fixed["y"]; + + fixed.erase("x"); + fixed.erase("y"); + } + + if (fixed.find("format") == fixed.end()) + { + fixed["format"] = "Png"; + } + + api += Helpers::joinQueryString(fixed); + + // Get the API response + std::pair response = Helpers::httpGet(api); + if (!response.first) + { + throw std::runtime_error("Unexpected error occurred when fetching Roblox API: 0"); + } + + std::string data = response.second; + + rapidjson::Document document; + document.Parse(data.c_str()); + + int error = 0; + + CHECK(document.HasParseError(), 1); + CHECK(!document.HasMember("data"), 2); + CHECK(document["data"].Size() == 0, 3); + CHECK(!document["data"][0].HasMember("imageUrl"), 4); + CHECK(!document["data"][0]["imageUrl"].IsString(), 5); + + if (error != 0) + { + throw std::runtime_error("Unexpected error occurred when fetching Roblox API: " + std::to_string(error)); + } + + _changed.url = document["data"][0]["imageUrl"].GetString(); + _this = &_changed; + } + } + } + } + + Http__httpGetPostWinInet(_this, isPost, a3, compressData, additionalHeaders, a6); +} + bool __fastcall Http__trustCheck_hook(const char* url) { if (strlen(url) == 7 && !Helpers::isASCII(url)) diff --git a/Kiseki.Patcher/Source/main.cpp b/Kiseki.Patcher/Source/main.cpp index 6aadd36..a56db09 100644 --- a/Kiseki.Patcher/Source/main.cpp +++ b/Kiseki.Patcher/Source/main.cpp @@ -17,6 +17,7 @@ START_PATCH_LIST() +ADD_PATCH(Http__httpGetPostWinInet, Http__httpGetPostWinInet_hook) ADD_PATCH(Http__trustCheck, Http__trustCheck_hook) ADD_PATCH(Crypt__verifySignatureBase64, Crypt__verifySignatureBase64_hook) diff --git a/README.md b/README.md index c523762..fdda377 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ # Kiseki.Patcher -Client add-on DLL used for extending game engine and server functionality -# License -Copyright (c) Kiseki 2022-2023. All rights reserved. Not for public use. \ No newline at end of file +> [!IMPORTANT] +> This project is currently in the process of being migrated to [@lrre-foss/lure](https://github.com/lrre-foss/lure). This repository will no longer receive updates. Please send all pull requests and bug fixes to that repository instead. + +Client functionality library + +## License + +Kiseki.Patcher is a fork of [@lrre-foss/lure](https://github.com/lrre-foss/lure), a project licensed under the [Apache License 2.0](https://github.com/lrre-foss/lure/blob/trunk/LICENSE); Kiseki.Patcher is thereby licensed under the Apache License 2.0 as well. diff --git a/vcpkg.json b/vcpkg.json index 543de3a..339d7a0 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -6,7 +6,6 @@ "curl", "detours", "discord-rpc", - "pugixml", "rapidjson" ] } \ No newline at end of file