23 Commits
Author SHA1 Message Date
alice 6beabba9e5 small fix 2026-07-28 03:26:12 +02:00
alice eb6341882d mysql fix 2026-07-28 02:53:12 +02:00
alice fe1fe6fe1c small fix 2026-07-28 01:33:36 +02:00
alice db0357ac19 fix build 2026-07-27 22:47:25 +02:00
alice b98796d28d fs_homepath working dir 2026-07-27 22:42:36 +02:00
alice b69b906175 merge changes from t6-gsc-utils 2026-07-27 22:42:27 +02:00
alice 087fbee1e3 add method opt to http::request 2026-07-27 21:49:27 +02:00
alice 2e0c364ac8 small change 2026-07-27 06:15:31 +02:00
aliceandGitHub d91dc80333 Update README.md [skip ci] 2026-07-27 06:07:12 +02:00
alice 4969d6f28f mysql & http refactor 2026-07-27 06:06:23 +02:00
alice 05be91f6bc fix #25 2025-08-19 05:27:31 +02:00
alice 3229243529 fix #23 2025-08-17 03:20:08 +02:00
aliceandGitHub 4ddb2ccd05 Update README.md [skip ci] 2025-08-11 18:25:24 +02:00
aliceandGitHub 4a62e193dc Update README.md [skip ci] 2025-08-11 18:19:31 +02:00
alice 13960b9291 add int64 funcs 2025-08-11 18:17:43 +02:00
alice 459ecf1695 add mysql + update build 2025-08-11 17:52:46 +02:00
aliceandGitHub 589c5b5a48 Merge pull request #20 from mjkzy/main
plutonium sdk support
2025-02-08 02:15:54 +01:00
mjkzy 7c0c9b0c36 fix action? 2025-02-07 14:58:02 -06:00
mjkzy 9adb001d08 better params check 2025-02-07 14:53:10 -06:00
mjkzy 936c3a5548 sdk dep 2025-02-07 14:47:36 -06:00
mjkzy 27ae961fd8 plutonium sdk support 2025-02-07 14:44:43 -06:00
mjkzy 3020770cb3 fix bad params crashing 2025-02-07 14:44:37 -06:00
aliceandGitHub 762cb0d93f Update README.md [skip ci] 2024-08-24 19:27:42 +02:00
57 changed files with 3465 additions and 491 deletions
+37 -3
View File
@@ -8,6 +8,9 @@ on:
branches:
- "*"
types: [opened, synchronize, reopened]
concurrency:
group: ${{ github.ref }}
cancel-in-progress: false
jobs:
build:
name: Build binaries
@@ -23,11 +26,10 @@ jobs:
with:
submodules: true
fetch-depth: 0
# NOTE - If LFS ever starts getting used during builds, switch this to true!
lfs: false
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v1.3.1
uses: microsoft/setup-msbuild@v2
- name: Generate project files
run: tools/premake5 vs2022
@@ -39,8 +41,40 @@ jobs:
run: msbuild /m /v:minimal /p:Configuration=${{matrix.configuration}} /p:PlatformTarget=x86 build/t5-gsc-utils.sln
- name: Upload ${{matrix.configuration}} binaries
uses: actions/upload-artifact@v3.1.3
uses: actions/upload-artifact@v4
with:
name: ${{matrix.configuration}} binaries
path: |
build/bin/${{matrix.configuration}}/t5-gsc-utils.dll
deploy:
name: Deploy artifacts
needs: build
runs-on: ubuntu-latest
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop')
steps:
- name: Setup main environment
if: github.ref == 'refs/heads/main'
run: echo "ALICE_MASTER_PATH=${{ secrets.ALICE_MASTER_SSH_PATH }}" >> $GITHUB_ENV
- name: Download Release binaries
uses: actions/download-artifact@v4
with:
name: Release binaries
- name: Install SSH key
uses: shimataro/[email protected]
with:
key: ${{ secrets.ALICE_MASTER_SSH_PRIVATE_KEY }}
known_hosts: 'just-a-placeholder-so-we-dont-get-errors'
- name: Add known hosts
run: ssh-keyscan -H ${{ secrets.ALICE_MASTER_SSH_ADDRESS }} >> ~/.ssh/known_hosts
- name: Remove old data files
run: ssh ${{ secrets.ALICE_MASTER_SSH_USER }}@${{ secrets.ALICE_MASTER_SSH_ADDRESS }} rm -rf ${{ env.ALICE_MASTER_PATH }}/t5-gsc-utils/*
- name: Upload binary
run: rsync -avz t5-gsc-utils.dll ${{ secrets.ALICE_MASTER_SSH_USER }}@${{ secrets.ALICE_MASTER_SSH_ADDRESS }}:${{ env.ALICE_MASTER_PATH }}/t5-gsc-utils/
- name: Publish changes
run: ssh ${{ secrets.ALICE_MASTER_SSH_USER }}@${{ secrets.ALICE_MASTER_SSH_ADDRESS }} ${{ secrets.ALICE_MASTER_SSH_CHANGE_PUBLISH_COMMAND }}
+2
View File
@@ -148,3 +148,5 @@ UpgradeLog*.htm
# User scripts
user*.bat
*.code-workspace
deps/mysql
+15
View File
@@ -28,3 +28,18 @@
[submodule "deps/curl"]
path = deps/curl
url = https://github.com/curl/curl.git
[submodule "deps/plutonium-sdk"]
path = deps/plutonium-sdk
url = https://github.com/plutoniummod/plutonium-sdk.git
[submodule "deps/sqlpp11"]
path = deps/sqlpp11
url = https://github.com/rbock/sqlpp11.git
[submodule "deps/date"]
path = deps/date
url = https://github.com/HowardHinnant/date
[submodule "deps/libtommath"]
path = deps/libtommath
url = https://github.com/libtom/libtommath.git
[submodule "deps/libtomcrypt"]
path = deps/libtomcrypt
url = https://github.com/libtom/libtomcrypt.git
+93 -5
View File
@@ -1,10 +1,10 @@
# t5-gsc-utils
T5 version of [t6-gsc-utils](https://github.com/fedddddd/t6-gsc-utils).
If you wish for any feature to be added please create an [issue](https://github.com/fedddddd/t5-gsc-utils/issues/new).
T5 version of [t6-gsc-utils](https://github.com/alicealys/t6-gsc-utils).
If you wish for any feature to be added please create an [issue](https://github.com/alicealys/t5-gsc-utils/issues/new).
# Installation
* Download the latest version from the [releases](https://github.com/fedddddd/t5-gsc-utils/releases)
* Copy it to `Plutonium/storage/t5/plugins`
* Download the latest version [t5-gsc-utils.dll](https://github.alicent.cat/t5-gsc-utils/t5-gsc-utils.dll)
* Copy it to `Plutonium/plugins/`
# Features
@@ -68,7 +68,7 @@ If you wish for any feature to be added please create an [issue](https://github.
}
```
More examples can be found [here](https://github.com/fedddddd/t5-gsc-utils/tree/main/src/component)
More examples can be found [here](https://github.com/alicealys/t5-gsc-utils/tree/main/src/component)
# Functions & Methods
@@ -201,3 +201,91 @@ A list of all the functions and methods that are added by this plugin.
assert(type(array()) == "array");
}
```
## MySQL
You can access a mysql database using the following functions:
* `mysql::set_config(config)`: Must be called before calling other mysql functions, config should be a struct of this kind:
```gsc
init()
{
config = spawnstruct();
config.host = "localhost";
config.user = "root";
config.password = "password";
config.port = 3306;
config.database = "database_name";
mysql::set_config(config);
}
```
* `mysql::query(query)`: Executes an sql statement, returns a query object:
```gsc
init()
{
// call mysql::set_config
query = mysql::query("select * from `players` where guid=1");
query waittill("done", result);
if (result.size > 0)
{
print("player name " + result[0]["name"]);
}
}
```
* `mysql::prepared_statement(query: string, params...)`: Executes a prepared statement, params can be a list of arguments or an array:
```gsc
init()
{
// call mysql::set_config
// use variadic args for the parameters
{
query = mysql::prepared_statement("insert into `players` (`guid`, `name`) values (?, ?)", 123, "foo");
query waittill("done", result, affected_rows, error);
}
// use an array for the parameters
{
params = array(123, "foo");
query = mysql::prepared_statement("insert into `players` (`guid`, `name`) values (?, ?)", params);
query waittill("done", result, affected_rows, error);
}
}
```
## Int64
These int64 functions allow you to use int64 values within GSC, which normally only supports 32 bit integers.
This is useful if you store int64 values in a file or a database and need to access them from GSC.
The values are stored as strings but all the functions below can take either a string or int for an int64 argument.
`int64` represents a value that is either a `string` or an `int`.
* `int64::op(a: int64, op: string, b: int64): int64|bool`: Performs an operation between 2 int64 values (stored as strings)
```gsc
{
result = int64::op("2583528945238953952", "+", "12039152933205235");
assert(result == "2595568098172159187");
result = int64::op("5834258295282538925", ">", 1);
assert(result == true);
}
```
List of supported operators:
* `+`
* `-`
* `*`
* `/`
* `&`
* `^`
* `|`
* `~`
* `%`
* `>>`
* `<<`
* `++`
* `--`
* `int64::is_int(value: int64): bool`: Returns true if an int64 value fits within an int32 value.
* `int64::to_int(value: int64): int`: Converts an int64 value to an int32 value.
* `int64::min(a: int64, b: int64): int64`: Returns the minimum between 2 int64 values.
* `int64::max(a: int64, b: int64): int64`: Returns the maximum between 2 int64 values.
Vendored Submodule
+1
Submodule deps/date added at e32a0d809c
Vendored Submodule
+1
Submodule deps/libtomcrypt added at a8ed78c2ca
Vendored Submodule
+1
Submodule deps/libtommath added at ae40a87a92
Vendored Submodule
+1
Submodule deps/plutonium-sdk added at 17e9a0a4d5
+61
View File
@@ -0,0 +1,61 @@
libtomcrypt = {
source = path.join(dependencies.basePath, "libtomcrypt"),
}
function libtomcrypt.import()
links {
"libtomcrypt"
}
libtomcrypt.includes()
end
function libtomcrypt.includes()
includedirs {
path.join(libtomcrypt.source, "src/headers")
}
defines {
"LTC_NO_FAST",
"LTC_NO_PROTOTYPES",
"LTC_NO_RSA_BLINDING",
}
end
function libtomcrypt.project()
project "libtomcrypt"
language "C"
libtomcrypt.includes()
libtommath.import()
files {
path.join(libtomcrypt.source, "src/**.c"),
}
removefiles {
path.join(libtomcrypt.source, "src/**/*tab.c"),
path.join(libtomcrypt.source, "src/encauth/ocb3/**.c"),
}
defines {
"_CRT_SECURE_NO_WARNINGS",
"LTC_SOURCE",
"_LIB",
"USE_LTM"
}
removedefines {
"_DLL",
"_USRDLL"
}
linkoptions {
"-IGNORE:4221"
}
warnings "Off"
kind "StaticLib"
end
table.insert(dependencies, libtomcrypt)
+52
View File
@@ -0,0 +1,52 @@
libtommath = {
source = path.join(dependencies.basePath, "libtommath"),
}
function libtommath.import()
links {
"libtommath"
}
libtommath.includes()
end
function libtommath.includes()
includedirs {
libtommath.source
}
defines {
"LTM_DESC",
"__STDC_IEC_559__",
"MP_NO_DEV_URANDOM",
}
end
function libtommath.project()
project "libtommath"
language "C"
libtommath.includes()
files {
path.join(libtommath.source, "*.c"),
}
defines {
"_LIB"
}
removedefines {
"_DLL",
"_USRDLL"
}
linkoptions {
"-IGNORE:4221"
}
warnings "Off"
kind "StaticLib"
end
table.insert(dependencies, libtommath)
+52
View File
@@ -0,0 +1,52 @@
if (os.host() ~= "windows") then
error("automatic mysql installation is not supported on your os")
end
mysql = {
source = path.join(dependencies.basePath, "mysql"),
version = "5.7.43",
download = "https://cdn.alicent.cat/mysql-5.7.43-win32.zip",
}
function mysql.install()
local hfile = io.open(string.format("%s/include/mysql.h", mysql.source), "r")
if (hfile) then
return
end
os.execute(string.format("mkdir \"%s\" 2> nul", mysql.source))
local folder = path.join(mysql.source, "mysql-5.7.43-win32")
local archive = path.join(mysql.source, "mysql-5.7.43-win32.zip")
print("Downloading MYSQL")
os.execute(string.format("curl \"%s\" -L -o \"%s\"", mysql.download, archive))
os.execute(string.format("powershell -command \"Expand-Archive -Force \\\"%s\\\" \\\"%s\\\"\"", archive, mysql.source))
os.execute(string.format("powershell -command \"mv \\\"%s/*\\\" \\\"%s\\\"\"", folder, mysql.source))
os.execute(string.format("powershell -command \"rm \\\"%s\\\"\"", archive))
os.execute(string.format("rmdir \"%s\"", folder))
end
function mysql.import()
mysql.install()
mysql.includes()
end
function mysql.includes()
includedirs {
path.join(mysql.source, "include"),
}
end
function mysql.project()
project "mysql"
language "C"
mysql.includes()
warnings "Off"
kind "StaticLib"
end
table.insert(dependencies, mysql)
+18
View File
@@ -0,0 +1,18 @@
plutonium_sdk = {
source = path.join(dependencies.basePath, "plutonium-sdk"),
}
function plutonium_sdk.import()
plutonium_sdk.includes()
end
function plutonium_sdk.includes()
includedirs {
plutonium_sdk.source,
}
end
function plutonium_sdk.project()
end
table.insert(dependencies, plutonium_sdk)
+26
View File
@@ -0,0 +1,26 @@
sqlpp11 = {
source = path.join(dependencies.basePath, "sqlpp11"),
}
function sqlpp11.import()
sqlpp11.includes()
end
function sqlpp11.includes()
includedirs {
path.join(sqlpp11.source, "include"),
path.join(dependencies.basePath, "date/include"),
}
end
function sqlpp11.project()
project "sqlpp11"
language "C++"
sqlpp11.includes()
warnings "Off"
kind "StaticLib"
end
table.insert(dependencies, sqlpp11)
Vendored Submodule
+1
Submodule deps/sqlpp11 added at 1806fe4dd8
+6
View File
@@ -77,19 +77,25 @@ workspace "t5-gsc-utils"
"./src/**.h",
"./src/**.hpp",
"./src/**.cpp",
"./src/**.rc",
}
includedirs
{
"%{prj.location}/src",
"./src",
"./deps/mysql/include"
}
libdirs {"./deps/mysql/lib"}
resincludedirs
{
"$(ProjectDir)src"
}
linkoptions {"/DELAYLOAD:libmysql.dll"}
pchheader "stdinc.hpp"
pchsource "src/stdinc.cpp"
+10 -4
View File
@@ -74,9 +74,14 @@ namespace command
void client_command_stub(const int client_num)
{
params_sv params = {};
if (params.size() < 1)
{
client_command_hook.invoke<void>(client_num);
return;
}
const auto command = utils::string::to_lower(params[0]);
if ((command == "say" || command == "say_team") &&
if (params.size() > 1 && (command == "say" || command == "say_team") &&
handle_chat_command(client_num, params))
{
return;
@@ -262,9 +267,10 @@ namespace command
class component final : public component_interface
{
public:
void post_unpack() override
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
{
scripting::on_shutdown(clear);
client_command_hook.create(SELECT_VALUE(0x4AF770, 0x63DB70), client_command_stub);
gsc::function::add_multiple([](const std::string& command)
@@ -283,7 +289,7 @@ namespace command
for (auto i = 0; i < params.size(); i++)
{
array.push(params[i]);
array.emplace_back(params[i]);
}
function({array});
@@ -304,7 +310,7 @@ namespace command
for (auto i = 0; i < params.size(); i++)
{
array.push(params[i]);
array.emplace_back(params[i]);
}
function(player, {array});
+3 -3
View File
@@ -134,12 +134,12 @@ namespace exception
class component final : public component_interface
{
public:
void post_unpack() override
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
{
#ifdef DEBUG
//#ifdef DEBUG
SetUnhandledExceptionFilter(exception_filter);
utils::hook::jump(reinterpret_cast<uintptr_t>(&SetUnhandledExceptionFilter), set_unhandled_exception_filter_stub);
#endif
//#endif
}
};
}
+5 -3
View File
@@ -126,6 +126,7 @@ namespace gsc
// Scr_NotifyId doesn't exist, Scr_NotifyNum_Internal calls FindVariableId to get the variable id from entnum, classnum & clientNum
// to not have to recreate Scr_NotifyId we simply make FindVariableId return `entnum` (which in this case will be the id) if `clientNum` == -1
std::size_t find_variable_id_stub_plutonium = 0;
unsigned int find_variable_id_stub(int inst, int entnum, unsigned int classnum, int client_num)
{
if (client_num == -1)
@@ -133,7 +134,7 @@ namespace gsc
return entnum;
}
return utils::hook::invoke<unsigned int>(SELECT_VALUE(0x5E96E0, 0x40BEF0), inst, entnum, classnum, client_num);
return utils::hook::invoke<unsigned int>(find_variable_id_stub_plutonium, inst, entnum, classnum, client_num);
}
}
@@ -180,7 +181,7 @@ namespace gsc
class component final : public component_interface
{
public:
void post_unpack() override
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
{
// Don't com_error on gsc errors
utils::hook::nop(SELECT_VALUE(0x5A17E1, 0x4D9BB1), 5);
@@ -197,6 +198,7 @@ namespace gsc
utils::hook::set<char>(SELECT_VALUE(0x9FC5C0 + 40, 0xAABA68 + 40), '\n');
utils::hook::set<char>(SELECT_VALUE(0x9FC5C0 + 41, 0xAABA68 + 41), '\0');
find_variable_id_stub_plutonium = utils::hook::extract<size_t>(SELECT_VALUE(0x41D2B5, 0x416325) + 1);
utils::hook::call(SELECT_VALUE(0x41D2B5, 0x416325), find_variable_id_stub);
gsc::function::add("array", [](const scripting::variadic_args& va)
@@ -205,7 +207,7 @@ namespace gsc
for (const auto& arg : va)
{
array.push(arg);
array.emplace_back(arg);
}
return array;
+141 -85
View File
@@ -9,86 +9,130 @@
#include "scripting.hpp"
#include <utils/http.hpp>
#include <utils/concurrency.hpp>
#include <curl/curl.h>
namespace http
{
std::unordered_map<uint64_t, bool> active_requests{};
uint64_t request_id{};
namespace
{
constexpr const auto max_result_size = 0x5000u;
class component final : public component_interface
struct http_request_params_t
{
public:
void post_unpack() override
{
scripting::on_shutdown([]()
{
active_requests.clear();
});
std::string method;
std::string url;
std::string fields;
utils::http::headers headers;
};
gsc::function::add_multiple([](const std::string& url)
struct http_request_t
{
const auto id = request_id++;
active_requests[id] = true;
http_request_params_t params;
scripting::object handle;
std::optional<utils::http::result> result;
std::atomic_bool completed;
};
const auto object = scripting::object{};
const auto object_id = object.get_entity_id();
std::vector<std::shared_ptr<http_request_t>> requests;
scheduler::once([id, object_id, url]()
void notify_request_result(std::shared_ptr<http_request_t>& request)
{
const auto data = utils::http::get_data(url);
scheduler::once([id, object_id, data]()
{
if (active_requests.find(id) == active_requests.end())
const auto handle_id = request->handle.get_entity_id();
if (!request->result.has_value())
{
scripting::notify(handle_id, "done", {{}, false, "unknown error"});
return;
}
if (!data.has_value())
{
scripting::notify(object_id, "done", {{}, false, "Unknown error"});
return;
}
const auto& result = data.value();
auto& result = request->result.value();
const auto error = curl_easy_strerror(result.code);
if (result.code != CURLE_OK)
{
scripting::notify(object_id, "done", {{}, false, error});
scripting::notify(handle_id, "done", {{}, false, error});
return;
}
if (result.buffer.size() >= 0x5000)
if (result.buffer.size() >= max_result_size)
{
printf("^3WARNING: http result size bigger than 20480 bytes (%i), truncating!", static_cast<int>(result.buffer.size()));
printf("^3WARNING: http result size bigger than %i bytes (%i), truncating!", max_result_size,
static_cast<int>(result.buffer.size()));
result.buffer.resize(max_result_size);
}
scripting::notify(object_id, "done", {result.buffer.substr(0, 0x5000), true});
}, scheduler::pipeline::server);
}, scheduler::pipeline::async);
scripting::notify(handle_id, "done", {result.buffer, true});
}
return object;
}, "http::get", "httpget", "curl");
gsc::function::add("http::request", [](const std::string& url, const scripting::variadic_args& va)
void check_requests()
{
const auto id = request_id++;
active_requests[id] = true;
const auto object = scripting::object{};
const auto object_id = object.get_entity_id();
std::string fields_string{};
std::unordered_map<std::string, std::string> headers_map{};
if (va.size() > 0)
for (auto i = requests.begin(); i != requests.end(); )
{
const auto options = va[0].as<scripting::array>();
auto& request = *i;
if (!request->completed)
{
++i;
continue;
}
else
{
notify_request_result(request);
i = requests.erase(i);
}
}
}
scripting::object create_request(const http_request_params_t& params)
{
const auto request = std::make_shared<http_request_t>();
requests.emplace_back(request);
request->params = params;
scheduler::thread_pool.push([request]
{
request->result = utils::http::get_data(
request->params.url,
request->params.fields,
request->params.headers,
request->params.method);
request->completed = true;
});
return request->handle;
}
template <typename T>
void push_request(T&& r)
{
const auto request = std::make_shared<http_request_t>(std::forward<T>(r));
requests.emplace_back(request);
}
void wait_and_clear_requests()
{
for (auto& task : requests)
{
while (!task->completed)
{
std::this_thread::sleep_for(10ms);
}
}
requests.clear();
}
void parse_request_options(http_request_params_t& params, const scripting::array& options)
{
const auto fields = options["parameters"];
const auto body = options["body"];
const auto headers = options["headers"];
const auto method = options["method"];
if (method.is<std::string>())
{
params.method = method.as<std::string>();
}
if (fields.is<scripting::array>())
{
@@ -104,20 +148,19 @@ namespace http
const auto key_ = key.as<std::string>();
const auto value = fields_[key].to_string();
fields_string += key_ + "=" + value + "&";
params.fields += key_ + "=" + value + "&";
}
}
if (body.is<std::string>())
else if (body.is<std::string>())
{
fields_string = body.as<std::string>();
params.fields = body.as<std::string>();
}
if (headers.is<scripting::array>())
{
const auto headers_ = headers.as<scripting::array>();
const auto keys = headers_.get_keys();
const auto headers_arr = headers.as<scripting::array>();
const auto keys = headers_arr.get_keys();
for (const auto& key : keys)
{
@@ -126,50 +169,63 @@ namespace http
continue;
}
const auto key_ = key.as<std::string>();
const auto value = headers_[key].to_string();
const auto key_str = key.as<std::string>();
const auto value = headers_arr[key].to_string();
headers_map[key_] = value;
params.headers[key_str] = value;
}
}
}
}
scheduler::once([id, object_id, url, fields_string, headers_map]()
class component final : public component_interface
{
const auto data = utils::http::get_data(url, fields_string, headers_map);
scheduler::once([data, object_id, id]
public:
void on_shutdown([[maybe_unused]] plugin::plugin* plugin) override
{
if (active_requests.find(id) == active_requests.end())
{
return;
scripting::on_shutdown(wait_and_clear_requests);
}
if (!data.has_value())
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
{
scripting::notify(object_id, "done", {{}, false, "Unknown error"});
return;
scheduler::loop(check_requests, scheduler::server);
scripting::on_shutdown(wait_and_clear_requests);
gsc::function::add_multiple([](const std::string& url)
{
http_request_params_t params{};
params.url = url;
return create_request(params);
}, "http::get", "httpget", "curl");
gsc::function::add("http::request", [](const std::string& url, const scripting::variadic_args& va)
{
http_request_params_t params{};
params.url = url;
if (va.size() > 0)
{
const auto options = va[0].as<scripting::array>();
parse_request_options(params, options);
}
const auto& result = data.value();
const auto error = curl_easy_strerror(result.code);
if (result.code != CURLE_OK)
{
scripting::notify(object_id, "done", {{}, false, error});
return;
}
if (result.buffer.size() >= 0x5000)
{
printf("^3WARNING: http result size bigger than 20480 bytes (%i), truncating!", static_cast<int>(result.buffer.size()));
}
scripting::notify(object_id, "done", {result.buffer.substr(0, 0x5000), true});
}, scheduler::pipeline::server);
}, scheduler::pipeline::async);
return object;
return create_request(params);
});
gsc::function::add_multiple([](const std::string& url, const scripting::variadic_args& va)
{
http_request_params_t params{};
params.url = url;
params.method = "POST";
if (va.size() > 0)
{
const auto options = va[0].as<scripting::array>();
parse_request_options(params, options);
}
return create_request(params);
}, "httppost", "http::post");
}
};
}
+119
View File
@@ -0,0 +1,119 @@
#include <stdinc.hpp>
#include "loader/component_loader.hpp"
#include "gsc.hpp"
#include <utils/io.hpp>
#include <utils/hook.hpp>
#define INT64_OPERATION(expr) \
[](const std::int64_t a, [[maybe_unused]] const std::int64_t b) \
{ \
return expr; \
} \
namespace int64
{
namespace
{
std::int64_t get_int64_arg(const scripting::variadic_args& args, const size_t index, bool optional)
{
if (optional && index >= static_cast<int>(args.size()))
{
return 0;
}
if (args[index].is<int>())
{
return static_cast<std::int64_t>(args[index].as<int>());
}
if (args[index].is<const char*>())
{
return std::strtoll(args[index].as<const char*>(), nullptr, 0);
}
throw std::runtime_error(std::format("parameter {} does not have type 'string' or 'int'", index));
}
std::unordered_map<std::string, std::function<std::int64_t(std::int64_t, std::int64_t)>> operations =
{
{"+", INT64_OPERATION(a + b)},
{"-", INT64_OPERATION(a - b)},
{"*", INT64_OPERATION(a * b)},
{"/", INT64_OPERATION(a / b)},
{"&", INT64_OPERATION(a & b)},
{"^", INT64_OPERATION(a ^ b)},
{"|", INT64_OPERATION(a | b)},
{"~", INT64_OPERATION(~a)},
{"%", INT64_OPERATION(a % b)},
{">>", INT64_OPERATION(a >> b)},
{"<<", INT64_OPERATION(a << b)},
{"++", INT64_OPERATION(a + 1)},
{"--", INT64_OPERATION(a - 1)},
};
std::unordered_map<std::string, std::function<bool(std::int64_t, std::int64_t)>> comparisons =
{
{">", INT64_OPERATION(a > b)},
{">=", INT64_OPERATION(a >= b)},
{"==", INT64_OPERATION(a == b)},
{"!=", INT64_OPERATION(a != b)},
{"<=", INT64_OPERATION(a <= b)},
{"<", INT64_OPERATION(a < b)},
};
}
class component final : public component_interface
{
public:
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
{
gsc::function::add_multiple([](const scripting::variadic_args& args)
{
auto value = get_int64_arg(args, 0, false);
return value <= std::numeric_limits<std::int32_t>::max() && value >= std::numeric_limits<std::int32_t>::min();
}, "int64_is_int", "int64::is_int");
gsc::function::add_multiple([](const scripting::variadic_args& args)
{
return static_cast<std::int32_t>(get_int64_arg(args, 0, false));
}, "int64_to_int", "int64::to_int");
gsc::function::add_multiple([](const scripting::variadic_args& args) -> scripting::script_value
{
auto a = get_int64_arg(args, 0, false);
const auto op = args[1].as<std::string>();
auto b = get_int64_arg(args, 2, true);
if (const auto iter = operations.find(op); iter != operations.end())
{
return std::to_string(iter->second(a, b));
}
if (const auto iter = comparisons.find(op); iter != comparisons.end())
{
return iter->second(a, b);
}
throw std::runtime_error("invalid int64 operation");
}, "int64_op", "int64::op");
gsc::function::add_multiple([](const scripting::variadic_args& args)
{
const auto a = get_int64_arg(args, 0, false);
const auto b = get_int64_arg(args, 1, false);
return std::to_string(std::min(a, b));
}, "int64_min", "int64::min");
gsc::function::add_multiple([](const scripting::variadic_args& args)
{
const auto a = get_int64_arg(args, 0, false);
const auto b = get_int64_arg(args, 1, false);
return std::to_string(std::max(a, b));
}, "int64_max", "int64::max");
}
};
}
REGISTER_COMPONENT(int64::component)
+12 -3
View File
@@ -13,11 +13,20 @@ namespace io
class component final : public component_interface
{
public:
void post_unpack() override
void on_after_dvar_init([[maybe_unused]] plugin::plugin* plugin) override
{
const auto fs_basegame = game::Dvar_FindVar("fs_basegame");
std::filesystem::current_path(fs_basegame->current.string);
const auto fs_homepath = game::Dvar_FindVar("fs_homepath");
if (fs_homepath == nullptr)
{
return;
}
std::filesystem::current_path(fs_homepath->current.string);
printf("working directory: %s", fs_homepath->current.string);
}
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
{
gsc::function::add_multiple([](const std::string& file, const std::string& data,
const scripting::variadic_args& va)
{
+2 -2
View File
@@ -173,7 +173,7 @@ namespace json
for (const auto& [key, value] : obj.items())
{
array.push(json_to_gsc(value));
array.emplace_back(json_to_gsc(value));
}
return array.get_raw();
@@ -204,7 +204,7 @@ namespace json
class component final : public component_interface
{
public:
void post_unpack() override
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
{
gsc::function::add_multiple([](const scripting::variadic_args& va)
{
+545
View File
@@ -0,0 +1,545 @@
#include <stdinc.hpp>
#include "loader/component_loader.hpp"
#include "mysql.hpp"
#include "component/gsc.hpp"
#include "component/scheduler.hpp"
#include "component/scripting.hpp"
#include <utils/string.hpp>
namespace mysql
{
namespace
{
struct mysql_result_t
{
MYSQL_RES* result;
MYSQL_STMT* stmt;
uint64_t affected_rows;
std::string error;
};
struct task_t
{
scripting::object handle;
mysql_result_t result{};
std::atomic_bool completed;
};
std::vector<std::shared_ptr<task_t>> tasks;
std::array<connection, max_connections> connection_pool;
scripting::script_value field_to_value(const MYSQL_FIELD* field, const std::string& row)
{
switch (field->type)
{
case enum_field_types::MYSQL_TYPE_INT24:
case enum_field_types::MYSQL_TYPE_LONG:
case enum_field_types::MYSQL_TYPE_SHORT:
return std::atoi(row.data());
case enum_field_types::MYSQL_TYPE_LONGLONG:
return row;
case enum_field_types::MYSQL_TYPE_FLOAT:
case enum_field_types::MYSQL_TYPE_DOUBLE:
return static_cast<float>(std::atof(row.data()));
}
return row;
}
scripting::script_value bind_to_value(const MYSQL_BIND* bind)
{
switch (bind->buffer_type)
{
case enum_field_types::MYSQL_TYPE_INT24:
case enum_field_types::MYSQL_TYPE_LONG:
case enum_field_types::MYSQL_TYPE_SHORT:
return *reinterpret_cast<int*>(bind->buffer);
case enum_field_types::MYSQL_TYPE_LONGLONG:
return std::to_string(*reinterpret_cast<std::int64_t*>(bind->buffer));
case enum_field_types::MYSQL_TYPE_FLOAT:
return *reinterpret_cast<float*>(bind->buffer);
case enum_field_types::MYSQL_TYPE_DOUBLE:
return static_cast<float>(*reinterpret_cast<double*>(bind->buffer));
case enum_field_types::MYSQL_TYPE_STRING:
return std::string{reinterpret_cast<char*>(bind->buffer), bind->buffer_length};
}
return {};
}
scripting::array generate_result(MYSQL_STMT* stmt)
{
if (stmt == nullptr)
{
return {};
}
utils::memory::allocator allocator;
scripting::array result_arr;
const auto meta = mysql_stmt_result_metadata(stmt);
if (meta == nullptr)
{
return {};
}
const auto column_count = mysql_num_fields(meta);
const auto fields = mysql_fetch_fields(meta);
const auto is_null = allocator.allocate_array<my_bool>(column_count);
const auto errors = allocator.allocate_array<my_bool>(column_count);
const auto real_lengths = allocator.allocate_array<unsigned long>(column_count);
const auto binds = allocator.allocate_array<MYSQL_BIND>(column_count);
for (auto i = 0u; i < column_count; i++)
{
binds[i].length = &real_lengths[i];
binds[i].is_null = &is_null[i];
binds[i].error = &errors[i];
}
if (mysql_stmt_bind_result(stmt, binds) != 0 ||
mysql_stmt_store_result(stmt) != 0)
{
return {};
}
while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA)
{
for (auto i = 0u; i < column_count; i++)
{
switch (fields[i].type)
{
case enum_field_types::MYSQL_TYPE_INT24:
case enum_field_types::MYSQL_TYPE_LONG:
case enum_field_types::MYSQL_TYPE_SHORT:
{
binds[i].buffer_type = MYSQL_TYPE_LONG;
binds[i].buffer = allocator.allocate<int>();
binds[i].buffer_length = sizeof(int);
break;
};
case enum_field_types::MYSQL_TYPE_LONGLONG:
{
binds[i].buffer_type = MYSQL_TYPE_LONGLONG;
binds[i].buffer = allocator.allocate<std::int64_t>();
binds[i].buffer_length = sizeof(std::int64_t);
break;
};
case enum_field_types::MYSQL_TYPE_FLOAT:
binds[i].buffer_type = MYSQL_TYPE_FLOAT;
binds[i].buffer = allocator.allocate<float>();
binds[i].buffer_length = sizeof(float);
break;
case enum_field_types::MYSQL_TYPE_DOUBLE:
{
binds[i].buffer_type = MYSQL_TYPE_DOUBLE;
binds[i].buffer = allocator.allocate<double>();
binds[i].buffer_length = sizeof(double);
break;
}
default:
binds[i].buffer_type = MYSQL_TYPE_STRING;
binds[i].buffer = allocator.allocate_array<char>(real_lengths[i]);
binds[i].buffer_length = real_lengths[i];
break;
}
mysql_stmt_fetch_column(stmt, &binds[i], i, 0);
}
scripting::array row_arr;
for (auto i = 0u; i < column_count; i++)
{
const auto field = &fields[i];
const std::string field_name = {field->name, field->name_length};
row_arr[field_name] = bind_to_value(&binds[i]);
}
result_arr.emplace_back(row_arr);
}
mysql_free_result(meta);
return result_arr;
}
scripting::array generate_result(MYSQL_RES* result)
{
if (result == nullptr)
{
return {};
}
scripting::array result_arr;
const auto num_rows = mysql_num_rows(result);
const auto num_fields = mysql_num_fields(result);
const auto fields = mysql_fetch_fields(result);
for (auto i = 0u; i < num_rows; i++)
{
scripting::array row_arr;
const auto row = mysql_fetch_row(result);
const auto lengths = mysql_fetch_lengths(result);
for (auto f = 0u; f < num_fields; f++)
{
const auto field = &fields[f];
const std::string field_str = {field->name, field->name_length};
const std::string row_str = {row[f], lengths[f]};
const auto value = field_to_value(field, row_str);
row_arr[field_str] = value;
}
result_arr.emplace_back(row_arr);
}
return result_arr;
}
template <typename F>
scripting::object create_mysql_query(F&& cb)
{
auto task = std::make_shared<task_t>();
tasks.emplace_back(task);
scheduler::thread_pool.push([=]
{
try
{
mysql::access([&](database_t& db)
{
task->result = cb(db);
task->completed = true;
});
}
catch (const std::exception& e)
{
printf("^1MySQL ERROR: %s\n", e.what());
task->completed = true;
}
});
return task->handle;
}
void wait_and_clear_tasks()
{
for (auto& task : tasks)
{
while (!task->completed)
{
std::this_thread::sleep_for(10ms);
}
}
tasks.clear();
}
void notify_task_result(std::shared_ptr<task_t>& task)
{
const auto result = task->result.result
? generate_result(task->result.result)
: generate_result(task->result.stmt);
const auto rows = static_cast<std::size_t>(task->result.affected_rows);
scripting::notify(task->handle.get_entity_id(), "done", {result, rows, task->result.error});
if (task->result.result != nullptr)
{
mysql_free_result(task->result.result);
task->result.result = nullptr;
}
if (task->result.stmt != nullptr)
{
mysql_stmt_close(task->result.stmt);
task->result.stmt = nullptr;
}
}
void check_tasks()
{
for (auto i = tasks.begin(); i != tasks.end(); )
{
auto& task = *i;
if (!task->completed)
{
++i;
continue;
}
else
{
notify_task_result(task);
i = tasks.erase(i);
}
}
}
template <typename T>
MYSQL_BIND* bind_statement_args(const T& args, std::size_t& bind_count)
{
bind_count = args.size();
const auto binds = utils::memory::allocate_array<MYSQL_BIND>(bind_count);
for (auto i = 0u; i < args.size(); i++)
{
const auto& arg = args[i];
const auto& raw_value = arg.get_raw();
switch (raw_value.type)
{
case game::SCRIPT_FLOAT:
{
binds[i].buffer = utils::memory::allocate<float>();
binds[i].buffer_type = MYSQL_TYPE_FLOAT;
*reinterpret_cast<float*>(binds[i].buffer) = raw_value.u.floatValue;
break;
}
case game::SCRIPT_INTEGER:
{
binds[i].buffer = utils::memory::allocate<int>();
binds[i].buffer_type = MYSQL_TYPE_LONG;
*reinterpret_cast<int*>(binds[i].buffer) = raw_value.u.intValue;
break;
}
case game::SCRIPT_STRING:
{
const auto str = arg.as<std::string>();
const auto str_copy = utils::memory::duplicate_string(str);
binds[i].buffer = str_copy;
binds[i].buffer_length = str.size();
binds[i].buffer_type = MYSQL_TYPE_STRING;
break;
}
default:
{
binds[i].buffer_type = MYSQL_TYPE_NULL;
break;
}
}
}
return binds;
}
void cleanup_connections()
{
for (auto& connection : connection_pool)
{
connection.cleanup();
}
}
void free_statement_binds(MYSQL_BIND* binds, const std::size_t bind_count)
{
if (binds == nullptr)
{
return;
}
for (auto i = 0u; i < bind_count; i++)
{
utils::memory::free(binds[i].buffer);
}
utils::memory::free(binds);
}
}
utils::concurrency::container<sql::connection_config>& get_config()
{
static utils::concurrency::container<sql::connection_config> config;
static auto initialized = false;
if (!initialized)
{
config.access([&](sql::connection_config& cfg)
{
cfg.user = "root";
cfg.password = "root";
cfg.host = "localhost";
cfg.port = 3306;
cfg.database = "default";
});
initialized = true;
}
return config;
}
void connection::check()
{
const auto now = std::chrono::high_resolution_clock::now();
const auto diff = now - this->start_;
if (this->db.get() == nullptr || !this->db->ping_server() || diff >= connection_timeout)
{
get_config().access([&](sql::connection_config& cfg)
{
this->db = std::make_unique<sql::connection>(cfg);
this->start_ = now;
});
}
this->last_access_ = now;
}
void connection::cleanup()
{
std::unique_lock<database_mutex_t> lock(this->mutex, std::try_to_lock);
if (!lock.owns_lock())
{
return;
}
const auto now = std::chrono::high_resolution_clock::now();
const auto diff = now - this->last_access_;
if (diff >= connection_timeout)
{
this->db.reset();
}
}
connection* get_connection(std::unique_lock<database_mutex_t>& lock)
{
static thread_local connection* last_connection{};
if (last_connection != nullptr)
{
lock = std::unique_lock(last_connection->mutex, std::try_to_lock);
if (lock.owns_lock())
{
return last_connection;
}
}
for (auto i = 0u; i < connection_pool.size(); i++)
{
auto connection = &connection_pool[i];
if (connection == last_connection)
{
continue;
}
lock = std::unique_lock(connection->mutex, std::try_to_lock);
if (!lock.owns_lock())
{
continue;
}
last_connection = connection;
return connection;
}
return nullptr;
}
class component final : public component_interface
{
public:
void on_shutdown([[maybe_unused]] plugin::plugin* plugin) override
{
wait_and_clear_tasks();
}
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
{
scripting::on_shutdown(wait_and_clear_tasks);
scheduler::loop(cleanup_connections, scheduler::async, 60s);
scheduler::loop(check_tasks, scheduler::server);
gsc::function::add("mysql::set_config", [](const scripting::object& config)
{
get_config().access([&](sql::connection_config& cfg)
{
cfg.host = config["host"].as<std::string>();
cfg.user = config["user"].as<std::string>();
cfg.password = config["password"].as<std::string>();
cfg.port = config["port"].as<unsigned short>();
cfg.database = config["database"].as<std::string>();
});
});
gsc::function::add_multiple([](const std::string& query)
{
return create_mysql_query([=](database_t& db)
{
const auto handle = db->get_handle();
mysql_result_t result{};
if (mysql_query(handle, query.data()) != 0)
{
result.error = mysql_error(handle);
return result;
}
result.result = mysql_store_result(handle);
result.affected_rows = mysql_affected_rows(handle);
return result;
});
}, "mysql::query", "mysql::execute");
gsc::function::add("mysql::prepared_statement", [](const std::string& query, const scripting::variadic_args& values)
{
MYSQL_BIND* binds = nullptr;
auto bind_count = 0u;
try
{
if (values.size() > 0 && values[0].is<scripting::array>())
{
binds = bind_statement_args(values[0].as<scripting::array>(), bind_count);
}
else
{
binds = bind_statement_args(values, bind_count);
}
const auto handle = create_mysql_query([binds, bind_count, query](database_t& db)
{
const auto _0 = gsl::finally([=]
{
free_statement_binds(binds, bind_count);
});
mysql_result_t result{};
const auto handle = db->get_handle();
const auto stmt = mysql_stmt_init(handle);
if (mysql_stmt_prepare(stmt, query.data(), query.size()) != 0 ||
(binds != nullptr && mysql_stmt_bind_param(stmt, binds) != 0) ||
mysql_stmt_execute(stmt) != 0)
{
result.error = mysql_stmt_error(stmt);
return result;
}
result.stmt = stmt;
result.affected_rows = mysql_stmt_affected_rows(stmt);
return result;
});
return handle;
}
catch (const std::exception& e)
{
free_statement_binds(binds, bind_count);
throw e;
}
});
}
};
}
REGISTER_COMPONENT(mysql::component)
+58
View File
@@ -0,0 +1,58 @@
#pragma once
#include <utils/concurrency.hpp>
#pragma warning(push)
#pragma warning(disable: 4127)
#pragma warning(disable: 4267)
#pragma warning(disable: 4018)
#pragma warning(disable: 4996)
#pragma warning(disable: 4244)
#include <sqlpp11/sqlpp11.h>
#include <sqlpp11/mysql/mysql.h>
#pragma warning(pop)
namespace sql = sqlpp::mysql;
namespace mysql
{
constexpr auto max_connections = 100;
constexpr auto connection_timeout = 200s;
using database_mutex_t = std::recursive_mutex;
using database_t = std::unique_ptr<sql::connection>;
utils::concurrency::container<sql::connection_config>& get_config();
class connection
{
public:
connection() = default;
void check();
void cleanup();
database_t db;
database_mutex_t mutex;
private:
std::chrono::high_resolution_clock::time_point start_;
std::chrono::high_resolution_clock::time_point last_access_;
};
connection* get_connection(std::unique_lock<database_mutex_t>& lock);
template <typename T = void, typename F>
T access(F&& accessor)
{
std::unique_lock<database_mutex_t> lock;
auto conn = get_connection(lock);
if (conn == nullptr)
{
throw std::runtime_error("out of connections");
}
conn->check();
return accessor(conn->db);
}
}
+8 -2
View File
@@ -9,6 +9,8 @@
namespace scheduler
{
utils::thread_pool thread_pool;
namespace
{
utils::hook::detour server_frame_hook;
@@ -140,8 +142,11 @@ namespace scheduler
class component final : public component_interface
{
public:
void post_unpack() override
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
{
thread_pool.initialize(8);
thread_pool.start();
thread = std::thread([]()
{
while (!kill_thread)
@@ -154,8 +159,9 @@ namespace scheduler
server_frame_hook.create(SELECT_VALUE(0x43E340, 0x46B680), server_frame_stub);
}
void pre_destroy() override
void on_shutdown([[maybe_unused]] plugin::plugin* plugin) override
{
thread_pool.stop();
kill_thread = true;
if (thread.joinable())
+4
View File
@@ -1,5 +1,7 @@
#pragma once
#include <utils/thread_pool.hpp>
namespace scheduler
{
enum pipeline
@@ -18,4 +20,6 @@ namespace scheduler
std::chrono::milliseconds delay = 0ms);
void once(const std::function<void()>& callback, pipeline type = pipeline::server,
std::chrono::milliseconds delay = 0ms);
extern utils::thread_pool thread_pool;
}
+1 -1
View File
@@ -67,7 +67,7 @@ namespace scripting
class component final : public component_interface
{
public:
void post_unpack() override
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
{
g_shutdown_game_hook.create(SELECT_VALUE(0x607700, 0x540950), g_shutdown_game_stub);
sl_get_canonical_string_hook.create(SELECT_VALUE(0x5F3F40, 0x622530), sl_transfer_canonical_string_stub);
+3 -8
View File
@@ -1,14 +1,8 @@
#include <stdinc.hpp>
#include "loader/component_loader.hpp"
#include "game/structs.hpp"
#include "game/game.hpp"
#include "gsc.hpp"
#include <utils/io.hpp>
#include <uchar.h>
// lua/lstrlib.c
#define MAX_FORMAT 32
#define L_FMTFLAGSF "-+#0 "
@@ -157,10 +151,11 @@ namespace string
}
}
class component final : public component_interface
{
public:
void post_unpack() override
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
{
gsc::function::add_multiple(format_string, "va", "string::va",
"formatstring", "string::format", "sprintf");
@@ -205,7 +200,7 @@ namespace string
{
for (const auto& s : match)
{
array_match.push((s.str()));
array_match.emplace_back((s.str()));
}
}
+1 -1
View File
@@ -70,7 +70,7 @@ namespace user_info
class component final : public component_interface
{
public:
void post_unpack() override
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
{
utils::hook::call(SELECT_VALUE(0x5D38EB, 0x4A75E2), sv_get_user_info_stub);
utils::hook::call(SELECT_VALUE(0x67FFE9, 0x548DB0), sv_get_user_info_stub);
+53 -14
View File
@@ -1,38 +1,77 @@
#include <stdinc.hpp>
#include "loader/component_loader.hpp"
#include "plugin.hpp"
#include "game/game.hpp"
#include <utils/hook.hpp>
#include <utils/binary_resource.hpp>
#include <utils/nt.hpp>
#include <utils/cryptography.hpp>
#include <utils/io.hpp>
namespace
{
void printf_stub(const char* fmt, ...)
utils::hook::detour load_library_hook;
std::string extract_resource(const std::string& name, const int resource)
{
char buffer[2048]{};
const auto data = utils::nt::load_resource(resource);
const auto path = std::filesystem::current_path() / "tmp" / name;
const auto path_str = path.generic_string();
va_list ap;
va_start(ap, fmt);
vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, fmt, ap);
va_end(ap);
game::Com_Printf(0, "%s", buffer);
if (!utils::io::write_file(path_str, data))
{
const auto current = utils::io::read_file(path_str);
const auto hash_current = utils::cryptography::md5::compute(current);
const auto hash_target = utils::cryptography::md5::compute(data);
if (hash_target != hash_current)
{
throw std::runtime_error("failed to extract libmysql.dll!");
}
}
BOOL APIENTRY DllMain(HMODULE /*module*/, DWORD ul_reason_for_call, LPVOID /*reserved*/)
return path_str;
}
HMODULE __stdcall load_library_stub(LPCSTR lib_name, HANDLE file, DWORD flags)
{
if (lib_name == "libmysql.dll"s)
{
const auto path = extract_resource(lib_name, LIBMYSQL_DLL);
const auto handle = load_library_hook.invoke_pascal<HMODULE>(path.data(), file, flags);
if (handle != nullptr)
{
return handle;
}
else
{
throw std::runtime_error(std::format("failed to load libmysql.dll: {}", GetLastError()));
}
}
return load_library_hook.invoke_pascal<HMODULE>(lib_name, file, flags);
}
}
PLUTONIUM_API plutonium::sdk::plugin* PLUTONIUM_CALLBACK on_initialize()
{
return plugin::get();
}
BOOL APIENTRY DllMain(HMODULE module, DWORD ul_reason_for_call, LPVOID /*reserved*/)
{
if (ul_reason_for_call == DLL_PROCESS_ATTACH)
{
utils::hook::jump(reinterpret_cast<size_t>(&printf), printf_stub);
component_loader::post_unpack();
utils::nt::library::set_current_handle(module);
load_library_hook.create(LoadLibraryExA, load_library_stub);
}
if (ul_reason_for_call == DLL_PROCESS_DETACH)
{
component_loader::pre_destroy();
component_loader::on_shutdown();
}
return TRUE;
+72 -78
View File
@@ -55,70 +55,21 @@ namespace scripting
}
}
array_value::array_value(unsigned int parent_id, unsigned int id)
: id_(id)
, parent_id_(parent_id)
array_value::array_value(const array* array, const script_value& key)
: array_(array)
, key_(key)
{
if (!this->id_)
{
return;
}
game::VariableValue variable_{};
if (game::environment::is_sp())
{
const auto variable = &game::scr_VarGlob->variableList_sp[this->id_ + 0x6000];
variable_.type = variable->w.type & 0x1F;
variable_.u = variable->u.u;
}
else
{
const auto variable = &game::scr_VarGlob->variableList_mp[this->id_ + 0x8000];
variable_.type = variable->w.type & 0x1F;
variable_.u = variable->u.u;
}
this->value_ = variable_;
const auto value = this->array_->get(key);
this->script_value::operator=(value);
}
void array_value::operator=(const script_value& value)
{
if (!this->id_)
{
return;
this->array_->set(this->key_, value);
this->script_value::operator=(value);
}
const auto& value_0 = value.get_raw();
game::VariableValue previous{};
if (game::environment::is_sp())
{
const auto variable = &game::scr_VarGlob->variableList_sp[this->id_ + 0x6000];
previous.type = variable->w.type & 0x1F;
previous.u = variable->u.u;
variable->w.type |= value_0.type;
variable->u.u = value_0.u;
}
else
{
const auto variable = &game::scr_VarGlob->variableList_mp[this->id_ + 0x8000];
previous.type = variable->w.type & 0x1F;
previous.u = variable->u.u;
variable->w.type |= value_0.type;
variable->u.u = value_0.u;
}
game::AddRefToValue(game::SCRIPTINSTANCE_SERVER, &value_0);
game::RemoveRefToValue(game::SCRIPTINSTANCE_SERVER, previous.type, previous.u);
this->value_ = value_0;
}
array::array(const unsigned int id)
array::array(const std::uint32_t id)
: id_(id)
{
this->add();
@@ -198,18 +149,18 @@ namespace scripting
return SELECT_VALUE(get_keys_sp, get_keys_mp)(this->id_);
}
int array::size() const
std::uint32_t array::size() const
{
return static_cast<int>(game::Scr_GetSelf(game::SCRIPTINSTANCE_SERVER, this->id_));
}
unsigned int array::push(const script_value& value) const
std::uint32_t array::push_back(const script_value& value) const
{
this->set(this->size(), value);
return this->size();
}
void array::erase(const unsigned int index) const
void array::erase(const std::uint32_t index) const
{
const auto variable_id = game::FindArrayVariable(game::SCRIPTINSTANCE_SERVER, this->id_, index);
if (variable_id)
@@ -228,13 +179,6 @@ namespace scripting
}
}
script_value array::pop() const
{
const auto value = this->get(this->size() - 1);
this->erase(this->size() - 1);
return value;
}
script_value array::get(const std::string& key) const
{
const auto string_value = game::SL_GetString(key.data(), 0, game::SCRIPTINSTANCE_SERVER);
@@ -263,6 +207,24 @@ namespace scripting
return variable_;
}
void array::erase(const script_value& key) const
{
if (key.is<int>())
{
return this->erase(key.as<int>());
}
if (key.is<std::string>())
{
return this->erase(key.as<std::string>());
}
}
void array::erase(const array_iterator& iter) const
{
this->erase(iter->first);
}
script_value array::get(const unsigned int index) const
{
const auto variable_id = game::FindArrayVariable(game::SCRIPTINSTANCE_SERVER, this->id_, index);
@@ -294,15 +256,15 @@ namespace scripting
{
if (key.is<int>())
{
this->get(key.as<int>());
return this->get(key.as<int>());
}
if (key.is<std::string>())
{
this->get(key.as<std::string>());
return this->get(key.as<std::string>());
}
return {};
throw std::runtime_error(std::format("invalid key type '{}'", key.type_name()));
}
void array::set(const std::string& key, const script_value& value) const
@@ -340,7 +302,7 @@ namespace scripting
game::RemoveRefToValue(game::SCRIPTINSTANCE_SERVER, previous.type, previous.u);
}
void array::set(const unsigned int index, const script_value& value) const
void array::set(const std::uint32_t index, const script_value& value) const
{
const auto& value_ = value.get_raw();
const auto variable_id = this->get_value_id(index);
@@ -375,25 +337,27 @@ namespace scripting
game::RemoveRefToValue(game::SCRIPTINSTANCE_SERVER, previous.type, previous.u);
}
void array::set(const script_value& key, const script_value& _value) const
void array::set(const script_value& key, const script_value& value) const
{
if (key.is<int>())
{
this->set(key.as<int>(), _value);
return this->set(key.as<int>(), value);
}
if (key.is<std::string>())
{
this->set(key.as<std::string>(), _value);
}
return this->set(key.as<std::string>(), value);
}
unsigned int array::get_entity_id() const
throw std::runtime_error(std::format("invalid key type '{}'", key.type_name()));
}
std::uint32_t array::get_entity_id() const
{
return this->id_;
}
unsigned int array::get_value_id(const std::string& key) const
std::uint32_t array::get_value_id(const std::string& key) const
{
const auto string_value = game::SL_GetString(key.data(), 0, game::SCRIPTINSTANCE_SERVER);
const auto variable_id = game::FindVariable(game::SCRIPTINSTANCE_SERVER, this->id_, string_value);
@@ -406,7 +370,7 @@ namespace scripting
return variable_id;
}
unsigned int array::get_value_id(const unsigned int index) const
std::uint32_t array::get_value_id(const std::uint32_t index) const
{
const auto variable_id = game::FindArrayVariable(game::SCRIPTINSTANCE_SERVER, this->id_, index);
if (!variable_id)
@@ -421,4 +385,34 @@ namespace scripting
{
return entity(this->id_);
}
array_value array::operator[](const script_value& key) const
{
return array_value(this, key);
}
array_iterator array::begin() const
{
const auto keys = this->get_keys();
return array_iterator(this, keys, 0);
}
array_iterator array::end() const
{
return array_iterator(this);
}
array_iterator array::find(const script_value& key) const
{
const auto keys = this->get_keys();
for (auto i = 0u; i < keys.size(); i++)
{
if (keys[i] == key)
{
return array_iterator(this, keys, i);
}
}
return array_iterator(this);
}
}
+62 -40
View File
@@ -1,23 +1,57 @@
#pragma once
#include "script_value.hpp"
#include "container_iterator.hpp"
namespace scripting
{
class array_value : public script_value
{
public:
array_value(unsigned int, unsigned int);
void operator=(const script_value&);
array_value(const array* array, const script_value& key);
void operator=(const script_value& value);
template <typename T>
T as() const
{
try
{
return script_value::as<T>();
}
catch (const std::exception& e)
{
throw std::runtime_error(std::format("array value '{}' {}", this->key_.to_string(), e.what()));
}
}
private:
unsigned int id_;
unsigned int parent_id_;
script_value key_;
const array* array_;
};
template <typename IteratorType>
class array_iterator_base : public IteratorType
{
public:
array_iterator_base(const array* container)
: IteratorType(container)
{
}
array_iterator_base(const array* container, const std::vector<script_value>& keys, const std::int64_t key_index)
: IteratorType(container, keys, key_index)
{
}
};
using array_iterator = array_iterator_base<container_iterator<array, script_value, array_value>>;
class array final
{
public:
array();
array(const unsigned int);
array(const std::uint32_t id);
array(const array& other);
array(array&& other) noexcept;
@@ -28,58 +62,46 @@ namespace scripting
array& operator=(array&& other) noexcept;
std::vector<script_value> get_keys() const;
int size() const;
std::uint32_t size() const;
unsigned int push(const script_value&) const;
void erase(const unsigned int) const;
void erase(const std::string&) const;
script_value pop() const;
std::uint32_t push_back(const script_value& value) const;
template <typename ...Args>
void emplace_back(Args&&... args)
{
this->push_back(std::forward<Args>(args)...);
}
void erase(const std::uint32_t index) const;
void erase(const std::string& key) const;
void erase(const script_value& key) const;
void erase(const array_iterator& iter) const;
script_value get(const script_value&) const;
script_value get(const std::string&) const;
script_value get(const unsigned int) const;
script_value get(const std::uint32_t) const;
void set(const script_value&, const script_value&) const;
void set(const std::string&, const script_value&) const;
void set(const unsigned int, const script_value&) const;
void set(const std::uint32_t, const script_value&) const;
unsigned int get_entity_id() const;
std::uint32_t get_entity_id() const;
unsigned int get_value_id(const std::string&) const;
unsigned int get_value_id(const unsigned int) const;
std::uint32_t get_value_id(const std::string&) const;
std::uint32_t get_value_id(const std::uint32_t) const;
entity get_raw() const;
array_value operator[](const int index) const
{
return {this->id_, this->get_value_id(index)};
}
array_value operator[](const script_value& key) const;
array_value operator[](const std::string& key) const
{
return {this->id_, this->get_value_id(key)};
}
template <typename I = int, typename S = std::string>
array_value operator[](const script_value& key) const
{
if (key.is<I>())
{
return {this->id_, this->get_value_id(key.as<I>())};
}
if (key.is<S>())
{
return {this->id_, this->get_value_id(key.as<S>())};
}
throw std::runtime_error("Invalid key type");
}
array_iterator begin() const;
array_iterator end() const;
array_iterator find(const script_value& key) const;
private:
void add() const;
void release() const;
unsigned int id_{};
std::uint32_t id_{};
};
}
+99
View File
@@ -0,0 +1,99 @@
#pragma once
#include "script_value.hpp"
namespace scripting
{
template <typename ContainerType, typename KeyType, typename ValueType>
class container_iterator
{
public:
static constexpr std::int64_t container_iterator_end_key = -1;
container_iterator(const ContainerType* container)
: container_(container)
, key_index_(container_iterator_end_key)
{
}
container_iterator(const ContainerType* container, const std::vector<KeyType>& keys, const std::int64_t key_index)
: container_(container)
, keys_(keys)
, key_index_(key_index)
{
this->update_pair();
}
std::pair<KeyType, ValueType>& operator*()
{
return this->pair_.value();
}
std::pair<KeyType, ValueType>* operator->()
{
return &this->pair_.value();
}
const std::pair<KeyType, ValueType>& operator*() const
{
return this->pair_.value();
}
const std::pair<KeyType, ValueType>* operator->() const
{
return &this->pair_.value();
}
container_iterator& operator++()
{
if (this->key_index_ == container_iterator_end_key)
{
return *this;
}
const auto size = this->keys_.size();
if (this->key_index_ + 1 >= size)
{
this->key_index_ = container_iterator_end_key;
return *this;
}
this->key_index_++;
this->update_pair();
return *this;
}
container_iterator operator++(int)
{
const auto pre = *this;
this->operator++();
return pre;
}
friend bool operator==(const container_iterator& a, const container_iterator& b)
{
return a.container_ == b.container_ && a.key_index_ == b.key_index_;
};
friend bool operator!=(const container_iterator& a, const container_iterator& b)
{
return a.container_ != b.container_ || a.key_index_ != b.key_index_;
};
private:
void update_pair()
{
const auto index = static_cast<size_t>(this->key_index_);
const auto& key = this->keys_[index];
const auto value = this->container_->operator[](key);
this->pair_.emplace(std::make_pair(key, value));
}
const ContainerType* container_;
std::int64_t key_index_;
std::vector<KeyType> keys_;
std::optional<std::pair<KeyType, ValueType>> pair_;
};
}
+76 -1
View File
@@ -147,12 +147,30 @@ namespace scripting
return this->is<int>();
}
template <>
bool script_value::is<unsigned short>() const
{
return this->is<int>();
}
template <>
bool script_value::is<bool>() const
{
return this->is<int>();
}
template <>
bool script_value::is<short>() const
{
return this->is<int>();
}
template <>
bool script_value::is<char>() const
{
return this->is<int>();
}
template <>
int script_value::get() const
{
@@ -171,6 +189,24 @@ namespace scripting
return this->get_raw().u.uintValue != 0;
}
template <>
unsigned short script_value::get() const
{
return static_cast<unsigned short>(this->get_raw().u.uintValue);
}
template <>
short script_value::get() const
{
return static_cast<short>(this->get_raw().u.intValue);
}
template <>
char script_value::get() const
{
return static_cast<char>(this->get_raw().u.intValue);
}
/***********************************************
* Float
**********************************************/
@@ -178,7 +214,8 @@ namespace scripting
template <>
bool script_value::is<float>() const
{
return this->get_raw().type == game::SCRIPT_FLOAT;
const auto type = this->get_raw().type;
return type == game::SCRIPT_FLOAT || type == game::SCRIPT_INTEGER;
}
template <>
@@ -190,12 +227,24 @@ namespace scripting
template <>
float script_value::get() const
{
const auto type = this->get_raw().type;
if (type == game::SCRIPT_INTEGER)
{
return static_cast<float>(this->get_raw().u.intValue);
}
return this->get_raw().u.floatValue;
}
template <>
double script_value::get() const
{
const auto type = this->get_raw().type;
if (type == game::SCRIPT_INTEGER)
{
return static_cast<double>(this->get_raw().u.intValue);
}
return static_cast<double>(this->get_raw().u.floatValue);
}
@@ -320,6 +369,12 @@ namespace scripting
return this->get_raw().type == game::SCRIPT_VECTOR;
}
template <>
bool script_value::is<float*>() const
{
return this->is<vector>();
}
template <>
vector script_value::get() const
{
@@ -413,4 +468,24 @@ namespace scripting
return std::vector<function_argument>::operator[](index);
}
function_argument function_arguments::operator[](const size_t index) const
{
if (index >= values_.size())
{
return {values_, {}, index, false};
}
return {values_, values_[index], index, true};
}
arguments function_arguments::get_raw() const
{
return this->values_;
}
size_t function_arguments::size() const
{
return this->values_.size();
}
}
+69 -15
View File
@@ -137,10 +137,21 @@ namespace scripting
template <typename T>
T get() const
{
if (std::is_constructible<T, std::string>::value && this->is<std::string>()) \
if constexpr (std::is_pointer<T>::value)
{
if (this->is<unsigned int>())
{
return reinterpret_cast<T>(this->as<unsigned int>());
}
}
if constexpr (std::is_constructible<T, std::string>::value)
{
if (this->is<std::string>())
{
return T(this->as<std::string>());
}
}
throw std::runtime_error("Invalid type");
}
@@ -157,7 +168,15 @@ public: \
template <typename T>
bool is() const
{
if (std::is_constructible<T, std::string>::value && this->is<std::string>()) \
if constexpr (std::is_pointer<T>::value)
{
if (this->is<unsigned int>())
{
return true;
}
}
if (std::is_constructible<T, std::string>::value && this->is<std::string>())
{
return true;
}
@@ -168,7 +187,9 @@ public: \
ADD_TYPE(bool)
ADD_TYPE(int)
ADD_TYPE(unsigned int)
ADD_TYPE(unsigned short)
ADD_TYPE(float)
ADD_TYPE(float*)
ADD_TYPE(double)
ADD_TYPE(const char*)
ADD_TYPE(std::string)
@@ -193,6 +214,17 @@ public: \
return get<T>();
}
template <typename T>
T as_or(const T& default_value) const
{
if (!this->is<T>())
{
return default_value;
}
return get<T>();
}
std::string type_name() const
{
return get_typename(this->get_raw());
@@ -205,7 +237,7 @@ public: \
for (const auto& value : container)
{
array_.push(value);
array_.emplace_back(value);
}
game::VariableValue value{};
@@ -223,6 +255,14 @@ public: \
std::string to_string() const;
friend bool operator==(const script_value& a, const script_value& b)
{
const auto& value_raw_a = a.get_raw();
const auto& value_raw_b = b.get_raw();
return value_raw_a.type != value_raw_b.type && value_raw_a.u.uintValue == value_raw_b.u.uintValue;
}
const game::VariableValue& get_raw() const;
variable_value value_{};
@@ -265,14 +305,29 @@ public: \
}
}
template <typename T, typename I = int>
T* as_ptr() const
{
const auto value = script_value::as<I>();
if (value == nullptr)
{
throw std::runtime_error("is null");
}
return reinterpret_cast<T*>(value);
}
template <>
variadic_args as() const
{
variadic_args args{this->index_};
for (auto i = this->index_; i < this->values_.size(); i++)
{
args.push_back({this->values_, this->values_[i], i, true});
args.emplace_back(this->values_, this->values_[i], i, true);
}
return args;
}
@@ -285,7 +340,7 @@ public: \
operator C<T, std::allocator<T>>() const
{
const auto container_type = get_c_typename<C<T, std::allocator<T>>>();
if (!script_value::as<ArrayType>())
if (!script_value::is<ArrayType>())
{
const auto type = get_typename(this->get_raw());
@@ -297,11 +352,11 @@ public: \
C<T, std::allocator<T>> container{};
const auto array = script_value::as<ArrayType>();
for (auto i = 0; i < array.size(); i++)
for (auto i = 0u; i < array.size(); i++)
{
try
{
container.push_back(array.get(i).as<T>());
container.emplace_back(array.get(i).as<T>());
}
catch (const std::exception& e)
{
@@ -323,6 +378,7 @@ public: \
arguments values_{};
size_t index_{};
bool exists_{};
};
class function_arguments
@@ -330,16 +386,14 @@ public: \
public:
function_arguments(const arguments& values);
function_argument operator[](const size_t index) const
{
if (index >= values_.size())
{
return {values_, {}, index, false};
}
function_argument operator[](const size_t index) const;
arguments get_raw() const;
size_t size() const;
return {values_, values_[index], index, true};
}
private:
arguments values_{};
};
}
+6 -6
View File
@@ -1,4 +1,5 @@
#pragma once
#include "../plugin.hpp"
class component_interface
{
@@ -7,25 +8,24 @@ public:
{
}
virtual void post_start()
virtual void on_startup([[maybe_unused]] plugin::plugin* plugin)
{
}
virtual void post_load()
virtual void on_dvar_init([[maybe_unused]] plugin::plugin* plugin)
{
}
virtual void pre_destroy()
virtual void on_after_dvar_init([[maybe_unused]] plugin::plugin* plugin)
{
}
virtual void post_unpack()
virtual void on_game_init([[maybe_unused]] plugin::plugin* plugin)
{
}
virtual void* load_import([[maybe_unused]] const std::string& library, [[maybe_unused]] const std::string& function)
virtual void on_shutdown([[maybe_unused]] plugin::plugin* plugin)
{
return nullptr;
}
virtual bool is_supported()
+34 -89
View File
@@ -1,87 +1,48 @@
#include <stdinc.hpp>
#include "component_loader.hpp"
void component_loader::register_component(std::unique_ptr<component_interface>&& component_)
void component_loader::register_component(const std::string& name, std::unique_ptr<component_interface>&& component_)
{
get_components().push_back(std::move(component_));
get_components().push_back(std::make_pair(name, std::move(component_)));
}
bool component_loader::post_start()
{
static auto handled = false;
if (handled) return true;
handled = true;
#define ON_CALLBACK(__name__) \
void component_loader::__name__() \
{ \
static auto handled = false; \
if (handled) \
{ \
return; \
} \
\
handled = true; \
\
for (const auto& component_ : get_components()) \
{ \
try \
{ \
component_.second->__name__(plugin::get()); \
} \
catch (const std::exception& e) \
{ \
printf("error executing component \"%s\" callback \"%s\": %s\n", component_.first.data(), #__name__, e.what()); \
} \
} \
} \
try
{
for (const auto& component_ : get_components())
{
component_->post_start();
}
}
catch (premature_shutdown_trigger&)
{
return false;
}
return true;
}
bool component_loader::post_load()
{
static auto handled = false;
if (handled) return true;
handled = true;
clean();
try
{
for (const auto& component_ : get_components())
{
component_->post_load();
}
}
catch (premature_shutdown_trigger&)
{
return false;
}
return true;
}
void component_loader::post_unpack()
{
static auto handled = false;
if (handled) return;
handled = true;
for (const auto& component_ : get_components())
{
component_->post_unpack();
}
}
void component_loader::pre_destroy()
{
static auto handled = false;
if (handled) return;
handled = true;
for (const auto& component_ : get_components())
{
component_->pre_destroy();
}
}
ON_CALLBACK(on_startup);
ON_CALLBACK(on_dvar_init);
ON_CALLBACK(on_after_dvar_init);
ON_CALLBACK(on_shutdown);
void component_loader::clean()
{
auto& components = get_components();
for (auto i = components.begin(); i != components.end();)
{
if (!(*i)->is_supported())
if (!(*i).second->is_supported())
{
(*i)->pre_destroy();
(*i).second->on_shutdown(plugin::get());
i = components.erase(i);
}
else
@@ -91,35 +52,19 @@ void component_loader::clean()
}
}
void* component_loader::load_import(const std::string& library, const std::string& function)
{
void* function_ptr = nullptr;
for (const auto& component_ : get_components())
{
auto* const component_function_ptr = component_->load_import(library, function);
if (component_function_ptr)
{
function_ptr = component_function_ptr;
}
}
return function_ptr;
}
void component_loader::trigger_premature_shutdown()
{
throw premature_shutdown_trigger();
}
std::vector<std::unique_ptr<component_interface>>& component_loader::get_components()
std::vector<std::pair<std::string, std::unique_ptr<component_interface>>>& component_loader::get_components()
{
using component_vector = std::vector<std::unique_ptr<component_interface>>;
using component_vector = std::vector<std::pair<std::string, std::unique_ptr<component_interface>>>;
using component_vector_container = std::unique_ptr<component_vector, std::function<void(component_vector*)>>;
static component_vector_container components(new component_vector, [](component_vector* component_vector)
{
pre_destroy();
on_shutdown();
delete component_vector;
});
+9 -11
View File
@@ -18,9 +18,9 @@ public:
static_assert(std::is_base_of<component_interface, T>::value, "component has invalid base class");
public:
installer()
installer(const std::string& name)
{
register_component(std::make_unique<T>());
register_component(name, std::make_unique<T>());
}
};
@@ -38,24 +38,22 @@ public:
return nullptr;
}
static void register_component(std::unique_ptr<component_interface>&& component);
static void register_component(const std::string& name, std::unique_ptr<component_interface>&& component);
static bool post_start();
static bool post_load();
static void post_unpack();
static void pre_destroy();
static void on_startup();
static void on_dvar_init();
static void on_after_dvar_init();
static void on_shutdown();
static void clean();
static void* load_import(const std::string& library, const std::string& function);
static void trigger_premature_shutdown();
private:
static std::vector<std::unique_ptr<component_interface>>& get_components();
static std::vector<std::pair<std::string, std::unique_ptr<component_interface>>>& get_components();
};
#define REGISTER_COMPONENT(name) \
namespace \
{ \
static component_loader::installer<name> __component; \
static component_loader::installer<name> __component(#name); \
}
+75
View File
@@ -0,0 +1,75 @@
#include <stdinc.hpp>
#include "loader/component_loader.hpp"
#include "plugin.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
namespace plugin
{
namespace
{
void printf_stub(const char* fmt, ...)
{
char buffer[0x2000] = {};
va_list ap;
va_start(ap, fmt);
vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, fmt, ap);
va_end(ap);
const auto trimmed = utils::string::trim(buffer);
get()->get_interface()->logging()->info(trimmed);
}
}
std::uint32_t plugin::plugin_version()
{
return 1;
}
const char* plugin::plugin_name()
{
return "t5-gsc-utils";
}
bool plugin::is_game_supported([[maybe_unused]] plutonium::sdk::game game)
{
return game == plutonium::sdk::game::t5;
}
void plugin::on_startup(plutonium::sdk::iinterface* interface_ptr, plutonium::sdk::game game)
{
this->interface_ = interface_ptr;
this->game_ = game;
utils::hook::jump(reinterpret_cast<uintptr_t>(&printf), printf_stub);
component_loader::on_startup();
interface_ptr->callbacks()->on_dvar_init(&component_loader::on_dvar_init);
interface_ptr->callbacks()->on_after_dvar_init(&component_loader::on_after_dvar_init);
}
void plugin::on_shutdown()
{
component_loader::on_shutdown();
}
plutonium::sdk::iinterface* plugin::get_interface()
{
return this->interface_;
}
plutonium::sdk::game plugin::get_game()
{
return this->game_;
}
plugin* get()
{
static plugin instance;
return &instance;
}
}
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#include <plutonium_sdk.hpp>
namespace plugin
{
class plugin : public plutonium::sdk::plugin
{
public:
~plugin() = default;
std::uint32_t plugin_version() override;
const char* plugin_name() override;
bool is_game_supported([[maybe_unused]] plutonium::sdk::game game) override;
void on_startup(plutonium::sdk::iinterface* interface_ptr, plutonium::sdk::game game) override;
void on_shutdown() override;
plutonium::sdk::iinterface* get_interface();
plutonium::sdk::game get_game();
private:
plutonium::sdk::iinterface* interface_{};
plutonium::sdk::game game_{};
};
plugin* get();
}
+3
View File
@@ -0,0 +1,3 @@
#pragma once
#define LIBMYSQL_DLL 100
+3
View File
@@ -0,0 +1,3 @@
#include "resource.hpp"
LIBMYSQL_DLL RCDATA "../../deps/mysql/lib/libmysql.dll"
+23
View File
@@ -1 +1,24 @@
#include <stdinc.hpp>
extern "C"
{
int s_read_arc4random(void*, size_t)
{
return -1;
}
int s_read_getrandom(void*, size_t)
{
return -1;
}
int s_read_urandom(void*, size_t)
{
return -1;
}
int s_read_ltm_rng(void*, size_t)
{
return -1;
}
}
+4
View File
@@ -52,5 +52,9 @@
#pragma comment(lib, "urlmon.lib" )
#pragma comment(lib, "iphlpapi.lib")
#pragma comment(lib, "Crypt32.lib")
#pragma comment(lib, "libmysql.lib")
#pragma comment(lib, "delayimp.lib")
#include "resource.hpp"
using namespace std::literals;
+76
View File
@@ -0,0 +1,76 @@
#include <stdinc.hpp>
#include "binary_resource.hpp"
#include <utility>
#include "nt.hpp"
#include "io.hpp"
namespace utils
{
namespace
{
std::string get_temp_folder()
{
char path[MAX_PATH] = {0};
if (!GetTempPathA(sizeof(path), path))
{
throw std::runtime_error("Unable to get temp path");
}
return path;
}
std::string write_exitisting_temp_file(const std::string& file, const std::string& data,
const bool fatal_if_overwrite_fails)
{
const auto temp = get_temp_folder();
auto file_path = temp + file;
std::string current_data;
if (!io::read_file(file_path, &current_data))
{
if (!io::write_file(file_path, data))
{
throw std::runtime_error("Failed to write file: " + file_path);
}
return file_path;
}
if (current_data == data || io::write_file(file_path, data) || !fatal_if_overwrite_fails)
{
return file_path;
}
throw std::runtime_error(
"Temporary file was already written, but differs. It can't be overwritten as it's still in use: " +
file_path);
}
}
binary_resource::binary_resource(const int id, std::string file)
: filename_(std::move(file))
{
this->resource_ = nt::load_resource(id);
if (this->resource_.empty())
{
throw std::runtime_error("Unable to load resource: " + std::to_string(id));
}
}
std::string binary_resource::get_extracted_file(const bool fatal_if_overwrite_fails)
{
if (this->path_.empty())
{
this->path_ = write_exitisting_temp_file(this->filename_, this->resource_, fatal_if_overwrite_fails);
}
return this->path_;
}
const std::string& binary_resource::get_data() const
{
return this->resource_;
}
}
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include <string>
namespace utils
{
class binary_resource
{
public:
binary_resource(int id, std::string file);
std::string get_extracted_file(bool fatal_if_overwrite_fails = false);
const std::string& get_data() const;
private:
std::string resource_;
std::string filename_;
std::string path_;
};
}
+691
View File
@@ -0,0 +1,691 @@
#include <stdinc.hpp>
#include "cryptography.hpp"
#include "nt.hpp"
#include <gsl/gsl>
#undef max
using namespace std::string_literals;
/// http://www.opensource.apple.com/source/CommonCrypto/CommonCrypto-55010/Source/libtomcrypt/doc/libTomCryptDoc.pdf
#pragma warning(push)
#pragma warning(disable: 4996)
namespace utils::cryptography
{
namespace
{
struct __
{
__()
{
ltc_mp = ltm_desc;
register_cipher(&aes_desc);
register_cipher(&des3_desc);
register_prng(&sprng_desc);
register_prng(&fortuna_desc);
register_prng(&yarrow_desc);
register_hash(&sha1_desc);
register_hash(&sha256_desc);
register_hash(&sha512_desc);
}
} ___;
[[maybe_unused]] const char* cs(const uint8_t* data)
{
return reinterpret_cast<const char*>(data);
}
[[maybe_unused]] char* cs(uint8_t* data)
{
return reinterpret_cast<char*>(data);
}
[[maybe_unused]] const uint8_t* cs(const char* data)
{
return reinterpret_cast<const uint8_t*>(data);
}
[[maybe_unused]] uint8_t* cs(char* data)
{
return reinterpret_cast<uint8_t*>(data);
}
[[maybe_unused]] unsigned long ul(const size_t value)
{
return static_cast<unsigned long>(value);
}
class prng
{
public:
prng(const ltc_prng_descriptor& descriptor, const bool autoseed = true)
: state_(std::make_unique<prng_state>())
, descriptor_(descriptor)
{
this->id_ = register_prng(&descriptor);
if (this->id_ == -1)
{
throw std::runtime_error("PRNG "s + this->descriptor_.name + " could not be registered!");
}
if (autoseed)
{
this->auto_seed();
}
else
{
this->descriptor_.start(this->state_.get());
}
}
~prng()
{
this->descriptor_.done(this->state_.get());
}
prng_state* get_state() const
{
this->descriptor_.ready(this->state_.get());
return this->state_.get();
}
int get_id() const
{
return this->id_;
}
void add_entropy(const void* data, const size_t length) const
{
this->descriptor_.add_entropy(static_cast<const uint8_t*>(data), ul(length), this->state_.get());
}
void read(void* data, const size_t length) const
{
this->descriptor_.read(static_cast<unsigned char*>(data), ul(length), this->get_state());
}
private:
int id_;
std::unique_ptr<prng_state> state_;
const ltc_prng_descriptor& descriptor_;
void auto_seed() const
{
rng_make_prng(128, this->id_, this->state_.get(), nullptr);
int i[4]; // uninitialized data
auto* i_ptr = &i;
this->add_entropy(reinterpret_cast<uint8_t*>(&i), sizeof(i));
this->add_entropy(reinterpret_cast<uint8_t*>(&i_ptr), sizeof(i_ptr));
auto t = time(nullptr);
this->add_entropy(reinterpret_cast<uint8_t*>(&t), sizeof(t));
}
};
const prng prng_(fortuna_desc);
}
ecc::key::key()
{
ZeroMemory(&this->key_storage_, sizeof(this->key_storage_));
}
ecc::key::~key()
{
this->free();
}
ecc::key::key(key&& obj) noexcept
: key()
{
this->operator=(std::move(obj));
}
ecc::key::key(const key& obj)
: key()
{
this->operator=(obj);
}
ecc::key& ecc::key::operator=(key&& obj) noexcept
{
if (this != &obj)
{
std::memmove(&this->key_storage_, &obj.key_storage_, sizeof(this->key_storage_));
ZeroMemory(&obj.key_storage_, sizeof(obj.key_storage_));
}
return *this;
}
ecc::key& ecc::key::operator=(const key& obj)
{
if (this != &obj && obj.is_valid())
{
this->deserialize(obj.serialize(obj.key_storage_.type));
}
return *this;
}
bool ecc::key::is_valid() const
{
return (!memory::is_set(&this->key_storage_, 0, sizeof(this->key_storage_)));
}
ecc_key& ecc::key::get()
{
return this->key_storage_;
}
const ecc_key& ecc::key::get() const
{
return this->key_storage_;
}
std::string ecc::key::get_public_key() const
{
uint8_t buffer[512] = {0};
unsigned long length = sizeof(buffer);
if (ecc_ansi_x963_export(&this->key_storage_, buffer, &length) == CRYPT_OK)
{
return std::string(cs(buffer), length);
}
return {};
}
void ecc::key::set(const std::string& pub_key_buffer)
{
this->free();
if (ecc_ansi_x963_import(cs(pub_key_buffer.data()),
ul(pub_key_buffer.size()),
&this->key_storage_) != CRYPT_OK)
{
ZeroMemory(&this->key_storage_, sizeof(this->key_storage_));
}
}
void ecc::key::deserialize(const std::string& key)
{
this->free();
if (ecc_import(cs(key.data()), ul(key.size()),
&this->key_storage_) != CRYPT_OK
)
{
ZeroMemory(&this->key_storage_, sizeof(this->key_storage_));
}
}
std::string ecc::key::serialize(const int type) const
{
uint8_t buffer[4096] = {0};
unsigned long length = sizeof(buffer);
if (ecc_export(buffer, &length, type, &this->key_storage_) == CRYPT_OK)
{
return std::string(cs(buffer), length);
}
return "";
}
void ecc::key::free()
{
if (this->is_valid())
{
ecc_free(&this->key_storage_);
}
ZeroMemory(&this->key_storage_, sizeof(this->key_storage_));
}
bool ecc::key::operator==(key& key) const
{
return (this->is_valid() && key.is_valid() && this->serialize(PK_PUBLIC) == key.serialize(PK_PUBLIC));
}
uint64_t ecc::key::get_hash() const
{
const auto hash = sha1::compute(this->get_public_key());
if (hash.size() >= 8)
{
return *reinterpret_cast<const uint64_t*>(hash.data());
}
return 0;
}
ecc::key ecc::generate_key(const int bits)
{
key key;
ecc_make_key(prng_.get_state(), prng_.get_id(), bits / 8, &key.get());
return key;
}
ecc::key ecc::generate_key(const int bits, const std::string& entropy)
{
key key{};
const prng yarrow(yarrow_desc, false);
yarrow.add_entropy(entropy.data(), entropy.size());
ecc_make_key(yarrow.get_state(), yarrow.get_id(), bits / 8, &key.get());
return key;
}
std::string ecc::sign_message(const key& key, const std::string& message)
{
if (!key.is_valid()) return "";
uint8_t buffer[512];
unsigned long length = sizeof(buffer);
ecc_sign_hash(cs(message.data()), ul(message.size()), buffer, &length, prng_.get_state(), prng_.get_id(),
&key.get());
return std::string(cs(buffer), length);
}
bool ecc::verify_message(const key& key, const std::string& message, const std::string& signature)
{
if (!key.is_valid()) return false;
auto result = 0;
return (ecc_verify_hash(cs(signature.data()),
ul(signature.size()),
cs(message.data()),
ul(message.size()), &result,
&key.get()) == CRYPT_OK && result != 0);
}
bool ecc::encrypt(const key& key, std::string& data)
{
std::string out_data{};
out_data.resize(std::max(ul(data.size() * 3), ul(0x100)));
auto out_len = ul(out_data.size());
auto crypt = [&]()
{
return ecc_encrypt_key(cs(data.data()), ul(data.size()), cs(out_data.data()), &out_len,
prng_.get_state(), prng_.get_id(), find_hash("sha512"), &key.get());
};
auto res = crypt();
if (res == CRYPT_BUFFER_OVERFLOW)
{
out_data.resize(out_len);
res = crypt();
}
if (res != CRYPT_OK)
{
return false;
}
out_data.resize(out_len);
data = std::move(out_data);
return true;
}
bool ecc::decrypt(const key& key, std::string& data)
{
std::string out_data{};
out_data.resize(std::max(ul(data.size() * 3), ul(0x100)));
auto out_len = ul(out_data.size());
auto crypt = [&]()
{
return ecc_decrypt_key(cs(data.data()), ul(data.size()), cs(out_data.data()), &out_len, &key.get());
};
auto res = crypt();
if (res == CRYPT_BUFFER_OVERFLOW)
{
out_data.resize(out_len);
res = crypt();
}
if (res != CRYPT_OK)
{
return false;
}
out_data.resize(out_len);
data = std::move(out_data);
return true;
}
std::string rsa::encrypt(const std::string& data, const std::string& hash, const std::string& key)
{
rsa_key new_key;
rsa_import(cs(key.data()), ul(key.size()), &new_key);
const auto _ = gsl::finally([&]()
{
rsa_free(&new_key);
});
std::string out_data{};
out_data.resize(std::max(ul(data.size() * 3), ul(0x100)));
auto out_len = ul(out_data.size());
auto crypt = [&]()
{
return rsa_encrypt_key_ex(cs(data.data()), ul(data.size()), cs(out_data.data()), &out_len, cs(hash.data()),
ul(hash.size()), prng_.get_state(), prng_.get_id(), find_hash("sha512"), LTC_PKCS_1_V1_5, &new_key);
};
auto res = crypt();
if (res == CRYPT_BUFFER_OVERFLOW)
{
out_data.resize(out_len);
res = crypt();
}
if (res == CRYPT_OK)
{
out_data.resize(out_len);
return out_data;
}
return {};
}
std::string des3::encrypt(const std::string& data, const std::string& iv, const std::string& key)
{
std::string enc_data;
enc_data.resize(data.size());
symmetric_CBC cbc;
const auto des3 = find_cipher("3des");
cbc_start(des3, cs(iv.data()), cs(key.data()), static_cast<int>(key.size()), 0, &cbc);
cbc_encrypt(cs(data.data()), cs(enc_data.data()), ul(data.size()), &cbc);
cbc_done(&cbc);
return enc_data;
}
std::string des3::decrypt(const std::string& data, const std::string& iv, const std::string& key)
{
std::string dec_data;
dec_data.resize(data.size());
symmetric_CBC cbc;
const auto des3 = find_cipher("3des");
cbc_start(des3, cs(iv.data()), cs(key.data()), static_cast<int>(key.size()), 0, &cbc);
cbc_decrypt(cs(data.data()), cs(dec_data.data()), ul(data.size()), &cbc);
cbc_done(&cbc);
return dec_data;
}
std::string tiger::compute(const std::string& data, const bool hex)
{
return compute(cs(data.data()), data.size(), hex);
}
std::string tiger::compute(const uint8_t* data, const size_t length, const bool hex)
{
uint8_t buffer[24] = {0};
hash_state state;
tiger_init(&state);
tiger_process(&state, data, ul(length));
tiger_done(&state, buffer);
std::string hash(cs(buffer), sizeof(buffer));
if (!hex) return hash;
return string::dump_hex(hash, "");
}
std::string aes::encrypt(const std::string& data, const std::string& iv, const std::string& key)
{
std::string aligned_data = data;
const auto mod = aligned_data.size() % 16;
if (mod != 0)
{
aligned_data.resize(aligned_data.size() + (16 - mod));
}
std::string enc_data;
enc_data.resize(aligned_data.size());
symmetric_CBC cbc;
const auto aes = find_cipher("aes");
cbc_start(aes, cs(iv.data()), cs(key.data()),
static_cast<int>(key.size()), 0, &cbc);
cbc_encrypt(cs(aligned_data.data()),
cs(enc_data.data()),
ul(aligned_data.size()), &cbc);
cbc_done(&cbc);
return enc_data;
}
std::string aes::decrypt(const std::string& data, const std::string& iv, const std::string& key)
{
std::string dec_data;
dec_data.resize(data.size());
symmetric_CBC cbc;
const auto aes = find_cipher("aes");
cbc_start(aes, cs(iv.data()), cs(key.data()),
static_cast<int>(key.size()), 0, &cbc);
cbc_decrypt(cs(data.data()),
cs(dec_data.data()),
ul(data.size()), &cbc);
cbc_done(&cbc);
return dec_data;
}
std::string hmac_sha1::compute(const std::string& data, const std::string& key)
{
std::string buffer;
buffer.resize(20);
hmac_state state;
hmac_init(&state, find_hash("sha1"), cs(key.data()), ul(key.size()));
hmac_process(&state, cs(data.data()), static_cast<int>(data.size()));
auto out_len = ul(buffer.size());
hmac_done(&state, cs(buffer.data()), &out_len);
buffer.resize(out_len);
return buffer;
}
std::string sha1::compute(const std::string& data, const bool hex)
{
return compute(cs(data.data()), data.size(), hex);
}
std::string sha1::compute(const uint8_t* data, const size_t length, const bool hex)
{
uint8_t buffer[20] = {0};
hash_state state;
sha1_init(&state);
sha1_process(&state, data, ul(length));
sha1_done(&state, buffer);
std::string hash(cs(buffer), sizeof(buffer));
if (!hex) return hash;
return string::dump_hex(hash, "");
}
std::string sha256::compute(const std::string& data, const bool hex)
{
return compute(cs(data.data()), data.size(), hex);
}
std::string sha256::compute(const uint8_t* data, const size_t length, const bool hex)
{
uint8_t buffer[32] = {0};
hash_state state;
sha256_init(&state);
sha256_process(&state, data, ul(length));
sha256_done(&state, buffer);
std::string hash(cs(buffer), sizeof(buffer));
if (!hex) return hash;
return string::dump_hex(hash, "");
}
std::string sha512::compute(const std::string& data, const bool hex)
{
return compute(cs(data.data()), data.size(), hex);
}
std::string sha512::compute(const uint8_t* data, const size_t length, const bool hex)
{
uint8_t buffer[64] = {0};
hash_state state;
sha512_init(&state);
sha512_process(&state, data, ul(length));
sha512_done(&state, buffer);
std::string hash(cs(buffer), sizeof(buffer));
if (!hex) return hash;
return string::dump_hex(hash, "");
}
namespace md5
{
std::string compute(const std::string& data, bool hex)
{
return compute(cs(data.data()), data.size(), hex);
}
std::string compute(const uint8_t* data, size_t length, bool hex)
{
uint8_t buffer[16] = {0};
hash_state state;
md5_init(&state);
md5_process(&state, data, ul(length));
md5_done(&state, buffer);
std::string hash(cs(buffer), sizeof(buffer));
if (!hex) return hash;
return string::dump_hex(hash, "");
}
}
std::string base64::encode(const uint8_t* data, const size_t len)
{
std::string result;
result.resize((len + 2) * 2);
auto out_len = ul(result.size());
if (base64_encode(data, ul(len), result.data(), &out_len) != CRYPT_OK)
{
return {};
}
result.resize(out_len);
return result;
}
std::string base64::encode(const std::string& data)
{
return base64::encode(cs(data.data()), static_cast<unsigned>(data.size()));
}
std::string base64::decode(const std::string& data)
{
std::string result;
result.resize((data.size() + 2) * 2);
auto out_len = ul(result.size());
if (base64_decode(data.data(), ul(data.size()), cs(result.data()), &out_len) != CRYPT_OK)
{
return {};
}
result.resize(out_len);
return result;
}
unsigned int jenkins_one_at_a_time::compute(const std::string& data)
{
return compute(data.data(), data.size());
}
unsigned int jenkins_one_at_a_time::compute(const char* key, const size_t len)
{
unsigned int hash, i;
for (hash = i = 0; i < len; ++i)
{
hash += key[i];
hash += (hash << 10);
hash ^= (hash >> 6);
}
hash += (hash << 3);
hash ^= (hash >> 11);
hash += (hash << 15);
return hash;
}
uint32_t random::get_integer()
{
uint32_t result;
random::get_data(&result, sizeof(result));
return result;
}
uint32_t random::get_integer(const std::uint32_t min, const std::uint32_t max)
{
const auto range = max - min + 1;
const auto value = random::get_integer();
return value % range + min;
}
std::string random::get_challenge()
{
std::string result;
result.resize(sizeof(uint32_t));
random::get_data(result.data(), result.size());
return string::dump_hex(result, "");
}
void random::get_data(void* data, const size_t size)
{
prng_.read(data, size);
}
std::string random::get_data(const size_t size)
{
std::string data;
data.resize(size);
random::get_data(data.data(), size);
return data;
}
}
#pragma warning(pop)
+151
View File
@@ -0,0 +1,151 @@
#pragma once
#include <string>
#include <tomcrypt.h>
#include "string.hpp"
namespace utils::cryptography
{
namespace ecc
{
class key final
{
public:
key();
~key();
key(key&& obj) noexcept;
key(const key& obj);
key& operator=(key&& obj) noexcept;
key& operator=(const key& obj);
bool is_valid() const;
ecc_key& get();
const ecc_key& get() const;
std::string get_public_key() const;
void set(const std::string& pub_key_buffer);
void deserialize(const std::string& key);
std::string serialize(int type = PK_PRIVATE) const;
void free();
bool operator==(key& key) const;
uint64_t get_hash() const;
private:
ecc_key key_storage_{};
};
key generate_key(int bits);
key generate_key(int bits, const std::string& entropy);
std::string sign_message(const key& key, const std::string& message);
bool verify_message(const key& key, const std::string& message, const std::string& signature);
bool encrypt(const key& key, std::string& data);
bool decrypt(const key& key, std::string& data);
}
namespace rsa
{
std::string encrypt(const std::string& data, const std::string& hash, const std::string& key);
std::string decrypt(const std::string& data, const std::string& hash, const std::string& key);
}
namespace des3
{
std::string encrypt(const std::string& data, const std::string& iv, const std::string& key);
std::string decrypt(const std::string& data, const std::string& iv, const std::string& key);
}
namespace tiger
{
std::string compute(const std::string& data, bool hex = false);
std::string compute(const uint8_t* data, size_t length, bool hex = false);
}
namespace aes
{
std::string encrypt(const std::string& data, const std::string& iv, const std::string& key);
std::string decrypt(const std::string& data, const std::string& iv, const std::string& key);
}
namespace hmac_sha1
{
std::string compute(const std::string& data, const std::string& key);
}
namespace sha1
{
std::string compute(const std::string& data, bool hex = false);
std::string compute(const uint8_t* data, size_t length, bool hex = false);
}
namespace sha256
{
std::string compute(const std::string& data, bool hex = false);
std::string compute(const uint8_t* data, size_t length, bool hex = false);
}
namespace sha512
{
std::string compute(const std::string& data, bool hex = false);
std::string compute(const uint8_t* data, size_t length, bool hex = false);
}
namespace md5
{
std::string compute(const std::string& data, bool hex = false);
std::string compute(const uint8_t* data, size_t length, bool hex = false);
}
namespace argon2
{
template <size_t HashLen = 32, size_t SaltLen = 16, std::uint32_t TCost = 2,
std::uint32_t MCost = 1 << 6, std::uint32_t Threads = 1>
std::string compute(const std::string& data, bool hex = false)
{
std::uint8_t buffer[HashLen]{};
std::uint8_t salt[SaltLen]{};
argon2i_hash_raw(TCost, MCost, Threads, data.data(), data.size(), salt, SaltLen, buffer, HashLen);
const auto str = std::string{reinterpret_cast<char*>(buffer), HashLen};
if (hex)
{
return string::dump_hex(str, "", false);
}
else
{
return str;
}
}
}
namespace base64
{
std::string encode(const uint8_t* data, size_t len);
std::string encode(const std::string& data);
std::string decode(const std::string& data);
}
namespace jenkins_one_at_a_time
{
unsigned int compute(const std::string& data);
unsigned int compute(const char* key, size_t len);
};
namespace random
{
uint32_t get_integer();
uint32_t get_integer(const std::uint32_t min, const std::uint32_t max);
std::string get_challenge();
void get_data(void* data, size_t size);
std::string get_data(const size_t size);
}
}
+6
View File
@@ -71,6 +71,12 @@ namespace utils::hook
return static_cast<T(*)(Args ...)>(this->get_original())(args...);
}
template <typename T, typename... Args>
T invoke_pascal(Args... args)
{
return static_cast<T(__stdcall*)(Args ...)>(this->get_original())(args...);
}
[[nodiscard]] void* get_original() const;
private:
+13 -39
View File
@@ -9,36 +9,9 @@ namespace utils::http
{
namespace
{
struct progress_helper
{
const std::function<void(size_t)>* callback{};
std::exception_ptr exception{};
};
int progress_callback(void* clientp, const curl_off_t /*dltotal*/, const curl_off_t dlnow, const curl_off_t /*ultotal*/, const curl_off_t /*ulnow*/)
{
auto* helper = static_cast<progress_helper*>(clientp);
try
{
if (*helper->callback)
{
(*helper->callback)(static_cast<size_t>(dlnow));
}
}
catch (...)
{
helper->exception = std::current_exception();
return -1;
}
return 0;
}
size_t write_callback(void* contents, const size_t size, const size_t nmemb, void* userp)
{
auto* buffer = static_cast<std::string*>(userp);
const auto buffer = static_cast<std::string*>(userp);
const auto total_size = size * nmemb;
buffer->append(static_cast<char*>(contents), total_size);
return total_size;
@@ -46,10 +19,10 @@ namespace utils::http
}
std::optional<result> get_data(const std::string& url, const std::string& fields,
const headers& headers, const std::function<void(size_t)>& callback)
const headers& headers, const std::string& method)
{
curl_slist* header_list = nullptr;
auto* curl = curl_easy_init();
const auto curl = curl_easy_init();
if (!curl)
{
return {};
@@ -68,22 +41,25 @@ namespace utils::http
}
std::string buffer{};
progress_helper helper{};
helper.callback = &callback;
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_list);
curl_easy_setopt(curl, CURLOPT_URL, url.data());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback);
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &helper);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
if (!fields.empty())
{
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, fields.data());
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, fields.size());
}
if (!method.empty())
{
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, method.data());
}
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
const auto code = curl_easy_perform(curl);
if (code == CURLE_OK)
@@ -91,14 +67,12 @@ namespace utils::http
result result;
result.code = code;
result.buffer = std::move(buffer);
return result;
}
else
{
result result;
result.code = code;
return result;
}
}
+1 -2
View File
@@ -2,7 +2,6 @@
#include <string>
#include <optional>
#include <future>
#include <curl/curl.h>
@@ -17,5 +16,5 @@ namespace utils::http
using headers = std::unordered_map<std::string, std::string>;
std::optional<result> get_data(const std::string& url, const std::string& fields = {},
const headers& headers = {}, const std::function<void(size_t)>& callback = {});
const headers& headers = {}, const std::string& method = {});
}
+250
View File
@@ -0,0 +1,250 @@
#include <stdinc.hpp>
#include "nt.hpp"
#include "string.hpp"
namespace utils::nt
{
HMODULE library::current_handle_;
library library::load(const std::string& name)
{
return library(LoadLibraryA(name.data()));
}
library library::load(const std::filesystem::path& path)
{
return library::load(path.generic_string());
}
library library::get_by_address(void* address)
{
HMODULE handle = nullptr;
GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, static_cast<LPCSTR>(address), &handle);
return library(handle);
}
void library::set_current_handle(HMODULE handle)
{
current_handle_ = handle;
}
HMODULE library::get_current_handle()
{
return current_handle_;
}
library::library()
{
this->module_ = GetModuleHandleA(nullptr);
}
library::library(const std::string& name)
{
this->module_ = GetModuleHandleA(name.data());
}
library::library(const HMODULE handle)
{
this->module_ = handle;
}
bool library::operator==(const library& obj) const
{
return this->module_ == obj.module_;
}
library::operator bool() const
{
return this->is_valid();
}
library::operator HMODULE() const
{
return this->get_handle();
}
PIMAGE_NT_HEADERS library::get_nt_headers() const
{
if (!this->is_valid()) return nullptr;
return reinterpret_cast<PIMAGE_NT_HEADERS>(this->get_ptr() + this->get_dos_header()->e_lfanew);
}
PIMAGE_DOS_HEADER library::get_dos_header() const
{
return reinterpret_cast<PIMAGE_DOS_HEADER>(this->get_ptr());
}
PIMAGE_OPTIONAL_HEADER library::get_optional_header() const
{
if (!this->is_valid()) return nullptr;
return &this->get_nt_headers()->OptionalHeader;
}
std::vector<PIMAGE_SECTION_HEADER> library::get_section_headers() const
{
std::vector<PIMAGE_SECTION_HEADER> headers;
auto nt_headers = this->get_nt_headers();
auto section = IMAGE_FIRST_SECTION(nt_headers);
for (uint16_t i = 0; i < nt_headers->FileHeader.NumberOfSections; ++i, ++section)
{
if (section) headers.push_back(section);
else OutputDebugStringA("There was an invalid section :O");
}
return headers;
}
std::uint8_t* library::get_ptr() const
{
return reinterpret_cast<std::uint8_t*>(this->module_);
}
void library::unprotect() const
{
if (!this->is_valid()) return;
DWORD protection;
VirtualProtect(this->get_ptr(), this->get_optional_header()->SizeOfImage, PAGE_EXECUTE_READWRITE,
&protection);
}
size_t library::get_relative_entry_point() const
{
if (!this->is_valid()) return 0;
return this->get_nt_headers()->OptionalHeader.AddressOfEntryPoint;
}
void* library::get_entry_point() const
{
if (!this->is_valid()) return nullptr;
return this->get_ptr() + this->get_relative_entry_point();
}
bool library::is_valid() const
{
return this->module_ != nullptr && this->get_dos_header()->e_magic == IMAGE_DOS_SIGNATURE;
}
std::string library::get_name() const
{
if (!this->is_valid()) return "";
auto path = this->get_path();
const auto pos = path.find_last_of("/\\");
if (pos == std::string::npos) return path;
return path.substr(pos + 1);
}
std::string library::get_path() const
{
if (!this->is_valid()) return "";
char name[MAX_PATH] = {0};
GetModuleFileNameA(this->module_, name, sizeof name);
return name;
}
std::string library::get_folder() const
{
if (!this->is_valid()) return "";
const auto path = std::filesystem::path(this->get_path());
return path.parent_path().generic_string();
}
void library::free()
{
if (this->is_valid())
{
FreeLibrary(this->module_);
this->module_ = nullptr;
}
}
HMODULE library::get_handle() const
{
return this->module_;
}
void** library::get_iat_entry(const std::string& module_name, const std::string& proc_name) const
{
if (!this->is_valid()) return nullptr;
const library other_module(module_name);
if (!other_module.is_valid()) return nullptr;
auto* const target_function = other_module.get_proc<void*>(proc_name);
if (!target_function) return nullptr;
auto* header = this->get_optional_header();
if (!header) return nullptr;
auto* import_descriptor = reinterpret_cast<PIMAGE_IMPORT_DESCRIPTOR>(this->get_ptr() + header->DataDirectory
[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
while (import_descriptor->Name)
{
if (!_stricmp(reinterpret_cast<char*>(this->get_ptr() + import_descriptor->Name), module_name.data()))
{
auto* original_thunk_data = reinterpret_cast<PIMAGE_THUNK_DATA>(import_descriptor->
OriginalFirstThunk + this->get_ptr());
auto* thunk_data = reinterpret_cast<PIMAGE_THUNK_DATA>(import_descriptor->FirstThunk + this->
get_ptr());
while (original_thunk_data->u1.AddressOfData)
{
const size_t ordinal_number = original_thunk_data->u1.AddressOfData & 0xFFFFFFF;
if (ordinal_number > 0xFFFF) continue;
if (GetProcAddress(other_module.module_, reinterpret_cast<char*>(ordinal_number)) ==
target_function)
{
return reinterpret_cast<void**>(&thunk_data->u1.Function);
}
++original_thunk_data;
++thunk_data;
}
//break;
}
++import_descriptor;
}
return nullptr;
}
void raise_hard_exception()
{
int data = false;
const library ntdll("ntdll.dll");
ntdll.invoke_pascal<void>("RtlAdjustPrivilege", 19, true, false, &data);
ntdll.invoke_pascal<void>("NtRaiseHardError", 0xC000007B, 0, nullptr, nullptr, 6, &data);
}
std::string load_resource(const int id)
{
const auto self_handle = library::get_current_handle();
const auto res = FindResource(self_handle, MAKEINTRESOURCE(id), RT_RCDATA);
if (res == nullptr)
{
return {};
}
const auto handle = LoadResource(self_handle, res);
if (handle == nullptr)
{
return {};
}
const auto str = LPSTR(LockResource(handle));
const auto size = SizeofResource(self_handle, res);
return std::string{str, size};
}
}
+112
View File
@@ -0,0 +1,112 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
// min and max is required by gdi, therefore NOMINMAX won't work
#ifdef max
#undef max
#endif
#ifdef min
#undef min
#endif
#include <string>
#include <functional>
#include <filesystem>
namespace utils::nt
{
class library final
{
public:
static library load(const std::string& name);
static library load(const std::filesystem::path& path);
static library get_by_address(void* address);
static void set_current_handle(HMODULE handle);
static HMODULE get_current_handle();
library();
explicit library(const std::string& name);
explicit library(HMODULE handle);
library(const library& a) : module_(a.module_)
{
}
bool operator!=(const library& obj) const { return !(*this == obj); };
bool operator==(const library& obj) const;
operator bool() const;
operator HMODULE() const;
void unprotect() const;
void* get_entry_point() const;
size_t get_relative_entry_point() const;
bool is_valid() const;
std::string get_name() const;
std::string get_path() const;
std::string get_folder() const;
std::uint8_t* get_ptr() const;
void free();
HMODULE get_handle() const;
template <typename T>
T get_proc(const std::string& process) const
{
if (!this->is_valid()) T{};
return reinterpret_cast<T>(GetProcAddress(this->module_, process.data()));
}
template <typename T>
std::function<T> get(const std::string& process) const
{
if (!this->is_valid()) return std::function<T>();
return static_cast<T*>(this->get_proc<void*>(process));
}
template <typename T, typename... Args>
T invoke(const std::string& process, Args ... args) const
{
auto method = this->get<T(__cdecl)(Args ...)>(process);
if (method) return method(args...);
return T();
}
template <typename T, typename... Args>
T invoke_pascal(const std::string& process, Args ... args) const
{
auto method = this->get<T(__stdcall)(Args ...)>(process);
if (method) return method(args...);
return T();
}
template <typename T, typename... Args>
T invoke_this(const std::string& process, void* this_ptr, Args ... args) const
{
auto method = this->get<T(__thiscall)(void*, Args ...)>(this_ptr, process);
if (method) return method(args...);
return T();
}
std::vector<PIMAGE_SECTION_HEADER> get_section_headers() const;
PIMAGE_NT_HEADERS get_nt_headers() const;
PIMAGE_DOS_HEADER get_dos_header() const;
PIMAGE_OPTIONAL_HEADER get_optional_header() const;
void** get_iat_entry(const std::string& module_name, const std::string& proc_name) const;
private:
HMODULE module_;
static HMODULE current_handle_;
};
__declspec(noreturn) void raise_hard_exception();
std::string load_resource(int id);
}
+14
View File
@@ -140,4 +140,18 @@ namespace utils::string
return timestamp;
}
std::string trim(const std::string& str, const std::string& whitespace)
{
const auto first = str.find_first_not_of(whitespace);
if (first == std::string::npos)
{
return {};
}
const auto last = str.find_last_not_of(whitespace);
const auto range = last - first + 1;
return str.substr(first, range);
}
}
+2
View File
@@ -96,4 +96,6 @@ namespace utils::string
std::wstring convert(const std::string& str);
std::string get_timestamp();
std::string trim(const std::string& str, const std::string& whitespace = " \t\n\r\f\v");
}
+96
View File
@@ -0,0 +1,96 @@
#include <stdinc.hpp>
#include "thread_pool.hpp"
namespace utils
{
thread_pool::worker::worker()
{
}
void thread_pool::worker::start(thread_pool& pool)
{
this->thread_ = std::thread([&]
{
this->loop(pool);
});
}
void thread_pool::worker::stop()
{
if (this->thread_.joinable())
{
this->thread_.join();
}
}
void thread_pool::worker::loop(thread_pool& pool)
{
while (!pool.stopped_)
{
pool.run_job();
}
}
thread_pool::job_ptr thread_pool::pop_job()
{
thread_pool::job_ptr job = std::move(this->queue_.front());
this->queue_.pop_front();
return job;
}
void thread_pool::run_job()
{
std::unique_lock<std::mutex> lock(this->mutex_);
this->event_.wait(lock, [&]()
{
return !this->queue_.empty() || this->stopped_;
});
if (this->stopped_ || this->queue_.empty())
{
return;
}
auto job = this->pop_job();
lock.unlock();
job->operator()();
}
thread_pool::thread_pool(const std::size_t num_workers)
{
this->initialize(num_workers);
}
void thread_pool::initialize(const std::size_t num_workers)
{
for (auto i = 0u; i < num_workers; i++)
{
this->workers_.emplace_back(std::make_unique<thread_pool::worker>());
}
}
void thread_pool::start()
{
for (auto& worker : this->workers_)
{
worker->start(*this);
}
}
void thread_pool::update()
{
}
void thread_pool::stop()
{
this->stopped_ = true;
this->event_.notify_all();
for (auto& worker : this->workers_)
{
worker->stop();
}
}
}
+60
View File
@@ -0,0 +1,60 @@
#pragma once
namespace utils
{
class thread_pool
{
public:
using job = std::function<void()>;
using job_ptr = std::unique_ptr<job>;
friend class worker;
class worker
{
public:
worker();
friend class thread_pool;
void start(thread_pool& pool);
void stop();
void loop(thread_pool& pool);
private:
std::thread thread_;
};
thread_pool() = default;
thread_pool(const std::size_t num_workers);
void initialize(const std::size_t num_workers);
void start();
void update();
void stop();
template <typename F>
void push(F&& job)
{
if (this->stopped_)
{
return;
}
std::lock_guard lock(this->mutex_);
this->queue_.emplace_back(std::make_unique<thread_pool::job>(std::forward<F>(job)));
this->event_.notify_one();
}
private:
thread_pool::job_ptr pop_job();
void run_job();
std::mutex mutex_;
std::atomic_bool stopped_;
std::deque<job_ptr> queue_;
std::condition_variable event_;
std::deque<std::unique_ptr<worker>> workers_;
};
}