Merge branch 'trunk' into feature/enhanced-server
This commit is contained in:
commit
ad5470b331
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 DataModel, ServerReplicator, and StandardOut as well as include our custom server interface
|
||||
list(APPEND SOURCE
|
||||
|
|
@ -50,15 +56,21 @@ add_library(Kiseki.Patcher SHARED ${SOURCE} ${HEADER})
|
|||
|
||||
# Packages
|
||||
find_package(CURL 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})
|
||||
target_link_libraries(Kiseki.Patcher PRIVATE CURL::libcurl ${DETOURS_LIBRARY} rapidjson)
|
||||
|
||||
# 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()
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
#ifdef PLAYER
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include <discord_rpc.h>
|
||||
#include <discord_register.h>
|
||||
#include <rapidjson/document.h>
|
||||
|
||||
#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
|
||||
|
|
@ -1,15 +1,10 @@
|
|||
#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 RBX__MESSAGE_INFO 0
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include <map>
|
||||
|
||||
#include <curl/curl.h>
|
||||
#include <pugixml.hpp>
|
||||
|
||||
#include "Globals.hpp"
|
||||
|
||||
|
|
@ -29,5 +30,7 @@ public:
|
|||
static std::string getModulePath();
|
||||
static std::string getISOTimestamp();
|
||||
static std::pair<bool, std::map<std::string, std::string>> parseURL(const std::string url);
|
||||
static std::string getRedirectURL(const std::string url);
|
||||
static std::pair<bool, std::string> httpGet(const std::string url);
|
||||
static std::string getBaseUrl();
|
||||
};
|
||||
|
|
@ -11,6 +11,10 @@
|
|||
#include "Server.hpp"
|
||||
#endif
|
||||
|
||||
#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;
|
||||
|
|
|
|||
|
|
@ -3,12 +3,24 @@
|
|||
#include <string>
|
||||
|
||||
#include <curl/curl.h>
|
||||
#include <rapidjson/document.h>
|
||||
|
||||
#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;
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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<bool, std::map<std::string, std::string>> parsed = Helpers::parseURL(joinScriptUrl);
|
||||
|
||||
if (!parsed.first)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (parsed.second["query"].empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> 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<bool, std::string> 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<bool, std::string> 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
|
||||
|
|
@ -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<bool, std::map<std::string, std::string>> 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<std::string, std::string> map;
|
||||
bool success = false;
|
||||
|
|
@ -195,7 +234,7 @@ std::pair<bool, std::string> 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);
|
||||
|
||||
|
|
@ -211,3 +250,27 @@ std::pair<bool, std::string> 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();
|
||||
}
|
||||
|
|
@ -94,6 +94,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())
|
||||
|
|
|
|||
|
|
@ -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<bool, std::map<std::string, std::string>> parsed = Helpers::parseURL(_this->url);
|
||||
Http _changed = *_this;
|
||||
|
||||
if (parsed.first)
|
||||
{
|
||||
std::map<std::string, std::string> 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<std::string, std::string> source = Helpers::parseQueryString(url["query"]);
|
||||
std::map<std::string, std::string> 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<bool, std::string> 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))
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
#include "Server.hpp"
|
||||
#endif
|
||||
|
||||
#ifdef PLAYER
|
||||
#include "Discord.hpp"
|
||||
#endif
|
||||
|
||||
#include "Hooks/Http.hpp"
|
||||
#include "Hooks/Crypt.hpp"
|
||||
#include "Hooks/CRoblox.hpp"
|
||||
|
|
@ -18,6 +22,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)
|
||||
|
|
@ -68,6 +73,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;
|
||||
|
|
|
|||
11
README.md
11
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.
|
||||
> [!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.
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
"version": "1.0.0",
|
||||
"dependencies": [
|
||||
"curl",
|
||||
"detours"
|
||||
"detours",
|
||||
"discord-rpc",
|
||||
"rapidjson"
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue