diff --git a/.gitignore b/.gitignore index 130a33a..d05a223 100644 --- a/.gitignore +++ b/.gitignore @@ -147,4 +147,6 @@ UpgradeLog*.htm user*.bat # Premake binary -#premake5.exe \ No newline at end of file +#premake5.exe + +deps/mysql diff --git a/.gitmodules b/.gitmodules index 5bdf456..40393ce 100644 --- a/.gitmodules +++ b/.gitmodules @@ -17,3 +17,18 @@ [submodule "deps/plutonium-sdk"] path = deps/plutonium-sdk url = https://github.com/plutoniummod/plutonium-sdk.git +[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 +[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/curl"] + path = deps/curl + url = https://github.com/curl/curl.git diff --git a/deps/curl b/deps/curl new file mode 160000 index 0000000..cccd76f --- /dev/null +++ b/deps/curl @@ -0,0 +1 @@ +Subproject commit cccd76f2aa02b78af0b305a3f548edafb12966e9 diff --git a/deps/date b/deps/date new file mode 160000 index 0000000..c05cd34 --- /dev/null +++ b/deps/date @@ -0,0 +1 @@ +Subproject commit c05cd34119d62f96deed28c803d2fd0b59548d8f diff --git a/deps/libtomcrypt b/deps/libtomcrypt new file mode 160000 index 0000000..a8ed78c --- /dev/null +++ b/deps/libtomcrypt @@ -0,0 +1 @@ +Subproject commit a8ed78c2ca8a7dcb50ad789702ac0586125544f0 diff --git a/deps/libtommath b/deps/libtommath new file mode 160000 index 0000000..ae40a87 --- /dev/null +++ b/deps/libtommath @@ -0,0 +1 @@ +Subproject commit ae40a87a920099a7d9d00979570e0c8d917a1fd7 diff --git a/deps/premake/curl.lua b/deps/premake/curl.lua new file mode 100644 index 0000000..3d36749 --- /dev/null +++ b/deps/premake/curl.lua @@ -0,0 +1,73 @@ +curl = { + source = path.join(dependencies.basePath, "curl"), +} + +function curl.import() + links { "curl" } + + filter "toolset:msc*" + links { "Crypt32.lib" } + filter {} + + curl.includes() +end + +function curl.includes() +filter "toolset:msc*" + includedirs { + path.join(curl.source, "include"), + } + + defines { + "CURL_STRICTER", + "CURL_STATICLIB", + "CURL_DISABLE_LDAP", + } +filter {} +end + +function curl.project() + if not os.istarget("windows") then + return + end + + project "curl" + language "C" + + curl.includes() + + includedirs { + path.join(curl.source, "lib"), + } + + files { + path.join(curl.source, "lib/**.c"), + path.join(curl.source, "lib/**.h"), + } + + defines { + "BUILDING_LIBCURL", + } + + filter "toolset:msc*" + + defines { + "USE_SCHANNEL", + "USE_WINDOWS_SSPI", + "USE_THREADS_WIN32", + } + + filter "toolset:not msc*" + + defines { + "USE_GNUTLS", + "USE_THREADS_POSIX", + } + + filter {} + + warnings "Off" + kind "StaticLib" +end + +table.insert(dependencies, curl) \ No newline at end of file diff --git a/deps/premake/libtomcrypt.lua b/deps/premake/libtomcrypt.lua new file mode 100644 index 0000000..6c6f28d --- /dev/null +++ b/deps/premake/libtomcrypt.lua @@ -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) diff --git a/deps/premake/libtommath.lua b/deps/premake/libtommath.lua new file mode 100644 index 0000000..ab4cdde --- /dev/null +++ b/deps/premake/libtommath.lua @@ -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) diff --git a/deps/premake/mysql.lua b/deps/premake/mysql.lua new file mode 100644 index 0000000..a35778f --- /dev/null +++ b/deps/premake/mysql.lua @@ -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) diff --git a/deps/premake/sqlpp11.lua b/deps/premake/sqlpp11.lua new file mode 100644 index 0000000..1f96b16 --- /dev/null +++ b/deps/premake/sqlpp11.lua @@ -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) diff --git a/deps/sqlpp11 b/deps/sqlpp11 new file mode 160000 index 0000000..8c05983 --- /dev/null +++ b/deps/sqlpp11 @@ -0,0 +1 @@ +Subproject commit 8c05983a31272cf9740cb521500c66a032aad678 diff --git a/premake5.lua b/premake5.lua index 5d9a3e5..93e156f 100644 --- a/premake5.lua +++ b/premake5.lua @@ -75,18 +75,30 @@ workspace "iw5-gsc-utils" pchheader "stdinc.hpp" pchsource "src/stdinc.cpp" - includedirs - { - "src" - } - - files - { - "src/**.h", - "src/**.hpp", - "src/**.cpp" + files + { + "./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"} + dependencies.imports() group "Dependencies" diff --git a/src/component/gsc.cpp b/src/component/gsc.cpp index e51ba4f..2f2f997 100644 --- a/src/component/gsc.cpp +++ b/src/component/gsc.cpp @@ -200,6 +200,11 @@ namespace gsc return this->values_; } + std::vector& function_args::get_raw() + { + return this->values_; + } + scripting::value_wrap function_args::get(const std::uint32_t index) const { if (index >= this->values_.size()) diff --git a/src/component/gsc.hpp b/src/component/gsc.hpp index 2a1c0be..23ac593 100644 --- a/src/component/gsc.hpp +++ b/src/component/gsc.hpp @@ -31,6 +31,7 @@ namespace gsc std::uint32_t size() const; const std::vector& get_raw() const; + std::vector& get_raw(); scripting::value_wrap get(const std::uint32_t index) const; scripting::value_wrap operator[](const std::uint32_t index) const diff --git a/src/component/http.cpp b/src/component/http.cpp new file mode 100644 index 0000000..5958bad --- /dev/null +++ b/src/component/http.cpp @@ -0,0 +1,218 @@ +#include +#include "loader/component_loader.hpp" + +#include "game/structs.hpp" +#include "game/game.hpp" + +#include "gsc.hpp" +#include "scheduler.hpp" +#include "scripting.hpp" + +#include +#include +#include + +namespace http +{ + namespace + { + constexpr const auto max_result_size = 0x5000u; + + struct http_request_params_t + { + std::string method; + std::string url; + std::string fields; + utils::http::headers headers; + }; + + struct http_request_t + { + http_request_params_t params; + scripting::object handle; + std::optional result; + std::atomic_bool completed; + }; + + std::vector> requests; + + void notify_request_result(std::shared_ptr& request) + { + const auto handle_id = request->handle.get_entity_id(); + + if (!request->result.has_value()) + { + scripting::notify(handle_id, "done", {{}, false, "unknown error"}); + return; + } + + auto& result = request->result.value(); + const auto error = curl_easy_strerror(result.code); + + if (result.code != CURLE_OK) + { + scripting::notify(handle_id, "done", {{}, false, error}); + return; + } + + if (result.buffer.size() >= max_result_size) + { + printf("^3WARNING: http result size bigger than %i bytes (%i), truncating!", max_result_size, + static_cast(result.buffer.size())); + result.buffer.resize(max_result_size); + } + + scripting::notify(handle_id, "done", {result.buffer, true}); + } + + void check_requests() + { + for (auto i = requests.begin(); i != requests.end(); ) + { + 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(); + 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 + void push_request(T&& r) + { + const auto request = std::make_shared(std::forward(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()) + { + params.method = method.as(); + } + + if (fields.is()) + { + const auto fields_ = fields.as(); + const auto keys = fields_.get_keys(); + + for (const auto& key : keys) + { + if (!key.is()) + { + continue; + } + + const auto key_ = key.as(); + const auto value = fields_[key].to_string(); + params.fields += key_ + "=" + value + "&"; + } + + } + else if (body.is()) + { + params.fields = body.as(); + } + + if (headers.is()) + { + const auto headers_arr = headers.as(); + const auto keys = headers_arr.get_keys(); + + for (const auto& key : keys) + { + if (!key.is()) + { + continue; + } + + const auto key_str = key.as(); + const auto value = headers_arr[key].to_string(); + + params.headers[key_str] = value; + } + } + } + } + + class component final : public component_interface + { + public: + void on_shutdown([[maybe_unused]] plugin::plugin* plugin) override + { + scripting::on_shutdown(wait_and_clear_requests); + } + + void on_startup([[maybe_unused]] plugin::plugin* plugin) override + { + scheduler::loop(check_requests, scheduler::server); + scripting::on_shutdown(wait_and_clear_requests); + + gsc::function::add("httpget", [](const gsc::function_args& args) + { + http_request_params_t params{}; + params.url = args[0].as(); + return create_request(params); + }); + + gsc::function::add("httprequest", [](const gsc::function_args& args) + { + http_request_params_t params{}; + params.url = args[0].as(); + + if (args.size() > 1) + { + const auto options = args[1].as(); + parse_request_options(params, options); + } + + return create_request(params); + }); + } + }; +} + +REGISTER_COMPONENT(http::component) diff --git a/src/component/io.cpp b/src/component/io.cpp index a2dd539..e792ac5 100644 --- a/src/component/io.cpp +++ b/src/component/io.cpp @@ -8,72 +8,6 @@ namespace io { - namespace - { - struct http_request_t - { - std::string url; - bool completed; - std::string result; - scripting::entity handle; - }; - - utils::concurrency::container> requests; - - void run_requests() - { - requests.access([&](std::vector& r) - { - for (auto& request : r) - { - const auto data = utils::http::get_data(request.url.data()); - if (data.has_value()) - { - request.result = data->substr(0, 0x5000); - } - else - { - request.result = ""; - } - - request.completed = true; - } - }); - } - - void check_requests() - { - requests.access([&](std::vector& r) - { - for (auto i = r.begin(); i != r.end(); ) - { - if (!i->completed) - { - ++i; - continue; - } - else - { - scripting::notify(i->handle, "done", {i->result}); - i = r.erase(i); - } - } - }); - } - - void add_request(const std::string& url, const scripting::entity& handle) - { - requests.access([&](std::vector& r) - { - http_request_t request{}; - request.handle = handle; - request.url = url; - request.completed = false; - r.emplace_back(request); - }); - } - } - class component final : public component_interface { public: @@ -91,17 +25,6 @@ namespace io void on_startup([[maybe_unused]] plugin::plugin* plugin) override { - scheduler::loop(run_requests, scheduler::async); - scheduler::loop(check_requests, scheduler::server); - - scripting::on_shutdown([]() - { - requests.access([&](std::vector& r) - { - r.clear(); - }); - }); - gsc::function::add("jsonprint", [](const gsc::function_args& args) -> scripting::script_value { @@ -189,17 +112,6 @@ namespace io return scripting::script_value{}; }); - - gsc::function::add("httpget", [](const gsc::function_args& args) - -> scripting::script_value - { - const auto url = args[0].as(); - const auto object = scripting::entity(scripting::make_object()); - - add_request(url, object); - - return object; - }); } }; } diff --git a/src/component/mysql.cpp b/src/component/mysql.cpp new file mode 100644 index 0000000..a074452 --- /dev/null +++ b/src/component/mysql.cpp @@ -0,0 +1,556 @@ +#include +#include "loader/component_loader.hpp" + +#include "mysql.hpp" +#include "component/gsc.hpp" +#include "component/scheduler.hpp" +#include "component/scripting.hpp" + +#include + +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> tasks; + std::array 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(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(bind->buffer); + case enum_field_types::MYSQL_TYPE_LONGLONG: + return std::to_string(*reinterpret_cast(bind->buffer)); + case enum_field_types::MYSQL_TYPE_FLOAT: + return *reinterpret_cast(bind->buffer); + case enum_field_types::MYSQL_TYPE_DOUBLE: + return static_cast(*reinterpret_cast(bind->buffer)); + case enum_field_types::MYSQL_TYPE_STRING: + return std::string{reinterpret_cast(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(column_count); + const auto errors = allocator.allocate_array(column_count); + const auto real_lengths = allocator.allocate_array(column_count); + const auto binds = allocator.allocate_array(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(); + 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(); + 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(); + 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(); + binds[i].buffer_length = sizeof(double); + break; + } + default: + binds[i].buffer_type = MYSQL_TYPE_STRING; + binds[i].buffer = allocator.allocate_array(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.push(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.push(row_arr); + } + + return result_arr; + } + + template + scripting::object create_mysql_query(F&& cb) + { + auto task = std::make_shared(); + 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) + { + const auto result = task->result.result + ? generate_result(task->result.result) + : generate_result(task->result.stmt); + + const auto rows = static_cast(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 + MYSQL_BIND* bind_statement_args(const T& args, std::size_t& bind_count) + { + bind_count = args.size(); + const auto binds = utils::memory::allocate_array(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(); + binds[i].buffer_type = MYSQL_TYPE_FLOAT; + *reinterpret_cast(binds[i].buffer) = raw_value.u.floatValue; + break; + } + case game::SCRIPT_INTEGER: + { + binds[i].buffer = utils::memory::allocate(); + binds[i].buffer_type = MYSQL_TYPE_LONG; + *reinterpret_cast(binds[i].buffer) = raw_value.u.intValue; + break; + } + case game::SCRIPT_STRING: + { + const auto str = arg.as(); + 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& get_config() + { + static utils::concurrency::container 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(cfg); + this->start_ = now; + }); + } + + this->last_access_ = now; + } + + void connection::cleanup() + { + std::unique_lock 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& 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 gsc::function_args& args) + -> scripting::script_value + { + const auto config = args[0].as(); + get_config().access([&](sql::connection_config& cfg) + { + cfg.host = config["host"].as(); + cfg.user = config["user"].as(); + cfg.password = config["password"].as(); + cfg.port = config["port"].as(); + cfg.database = config["database"].as(); + }); + + return {}; + }); + + gsc::function::add("mysql_query", [](const gsc::function_args& args) + -> scripting::script_value + { + const auto query = args[0].as(); + 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; + }); + }); + + gsc::function::add("mysql_prepared_statement", [](const gsc::function_args& args) + -> scripting::script_value + { + MYSQL_BIND* binds = nullptr; + auto bind_count = 0u; + + const auto query = args[0].as(); + auto args_vec = args.get_raw(); + args_vec.erase(args_vec.begin()); + + try + { + if (args_vec.size() > 0 && args_vec[0].is()) + { + binds = bind_statement_args(args_vec[0].as(), bind_count); + } + else + { + binds = bind_statement_args(args_vec, 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) diff --git a/src/component/mysql.hpp b/src/component/mysql.hpp new file mode 100644 index 0000000..bd88662 --- /dev/null +++ b/src/component/mysql.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include + +#pragma warning(push) +#pragma warning(disable: 4127) +#pragma warning(disable: 4267) +#pragma warning(disable: 4018) +#pragma warning(disable: 4996) +#pragma warning(disable: 4244) +#include +#include +#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; + + utils::concurrency::container& 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& lock); + + template + T access(F&& accessor) + { + std::unique_lock lock; + auto conn = get_connection(lock); + if (conn == nullptr) + { + throw std::runtime_error("out of connections"); + } + + conn->check(); + return accessor(conn->db); + } +} diff --git a/src/component/scheduler.cpp b/src/component/scheduler.cpp index 8c4461c..0f11a05 100644 --- a/src/component/scheduler.cpp +++ b/src/component/scheduler.cpp @@ -5,6 +5,8 @@ namespace scheduler { + utils::thread_pool thread_pool; + namespace { struct task @@ -131,6 +133,9 @@ namespace scheduler public: void on_startup([[maybe_unused]] plugin::plugin* plugin) override { + thread_pool.initialize(8); + thread_pool.start(); + thread = std::thread([]() { while (!killed) @@ -145,6 +150,7 @@ namespace scheduler void on_shutdown([[maybe_unused]] plugin::plugin* plugin) override { + thread_pool.stop(); killed = true; if (thread.joinable()) diff --git a/src/component/scheduler.hpp b/src/component/scheduler.hpp index cabb37e..46ccef1 100644 --- a/src/component/scheduler.hpp +++ b/src/component/scheduler.hpp @@ -1,5 +1,7 @@ #pragma once +#include + namespace scheduler { enum pipeline @@ -18,4 +20,6 @@ namespace scheduler std::chrono::milliseconds delay = 0ms); void once(const std::function& callback, pipeline type = pipeline::server, std::chrono::milliseconds delay = 0ms); + + extern utils::thread_pool thread_pool; } diff --git a/src/dllmain.cpp b/src/dllmain.cpp index baf1fd5..6906b91 100644 --- a/src/dllmain.cpp +++ b/src/dllmain.cpp @@ -3,22 +3,76 @@ #include "plugin.hpp" +#include "game/game.hpp" + +#include +#include +#include +#include +#include + +namespace +{ + utils::hook::detour load_library_hook; + + std::string extract_resource(const std::string& name, const int resource) + { + const auto data = utils::nt::load_resource(resource); + const auto path = std::filesystem::current_path() / "tmp" / name; + const auto path_str = path.generic_string(); + + 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!"); + } + } + + 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(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(lib_name, file, flags); + } +} + PLUTONIUM_API plutonium::sdk::plugin* PLUTONIUM_CALLBACK on_initialize() { - return plugin::get(); + return plugin::get(); } -BOOL APIENTRY DllMain(HMODULE /*module*/, DWORD reason, LPVOID /*reserved*/) +BOOL APIENTRY DllMain(HMODULE module, DWORD ul_reason_for_call, LPVOID /*reserved*/) { - if (reason == DLL_PROCESS_ATTACH) - { + if (ul_reason_for_call == DLL_PROCESS_ATTACH) + { + 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::on_shutdown(); + } - if (reason == DLL_PROCESS_DETACH) - { - component_loader::on_shutdown(); - } - - return TRUE; + return TRUE; } diff --git a/src/game/scripting/array.hpp b/src/game/scripting/array.hpp index 482ff0d..e44a0e6 100644 --- a/src/game/scripting/array.hpp +++ b/src/game/scripting/array.hpp @@ -76,6 +76,8 @@ namespace scripting { return {this->id_, this->get_value_id(key.as())}; } + + throw std::runtime_error("invalid key type"); } private: diff --git a/src/game/scripting/execution.hpp b/src/game/scripting/execution.hpp index 14c6cf6..94dd3e4 100644 --- a/src/game/scripting/execution.hpp +++ b/src/game/scripting/execution.hpp @@ -2,6 +2,7 @@ #include "game/game.hpp" #include "entity.hpp" #include "array.hpp" +#include "object.hpp" #include "function.hpp" #include "script_value.hpp" diff --git a/src/game/scripting/object.cpp b/src/game/scripting/object.cpp new file mode 100644 index 0000000..b62410a --- /dev/null +++ b/src/game/scripting/object.cpp @@ -0,0 +1,89 @@ +#include +#include "object.hpp" +#include "script_value.hpp" +#include "execution.hpp" + +namespace scripting +{ + object::object(const unsigned int id) + : id_(id) + { + this->add(); + } + + object::object(const object& other) + : object(other.id_) + { + } + + object::object(object&& other) noexcept + { + this->id_ = other.id_; + other.id_ = 0; + } + + object::object() + { + this->id_ = make_object(); + } + + object::~object() + { + this->release(); + } + + object& object::operator=(const object& other) + { + if (&other != this) + { + this->release(); + this->id_ = other.id_; + this->add(); + } + + return *this; + } + + object& object::operator=(object&& other) noexcept + { + if (&other != this) + { + this->release(); + this->id_ = other.id_; + other.id_ = 0; + } + + return *this; + } + + void object::add() const + { + if (this->id_) + { + game::AddRefToValue(game::SCRIPT_OBJECT, {static_cast(this->id_)}); + } + } + + void object::release() const + { + if (this->id_) + { + game::RemoveRefToValue(game::SCRIPT_OBJECT, {static_cast(this->id_)}); + } + } + + unsigned int object::size() const + { + return game::Scr_GetSelf(this->id_); + } + + unsigned int object::get_entity_id() const + { + return this->id_; + } + + entity object::get_raw() const + { + return entity(this->id_); + } +} diff --git a/src/game/scripting/object.hpp b/src/game/scripting/object.hpp new file mode 100644 index 0000000..34a7782 --- /dev/null +++ b/src/game/scripting/object.hpp @@ -0,0 +1,32 @@ +#pragma once +#include "game/game.hpp" +#include "script_value.hpp" + +namespace scripting +{ + class object final + { + public: + object(); + object(const unsigned int); + + object(const object& other); + object(object&& other) noexcept; + + ~object(); + + object& operator=(const object& other); + object& operator=(object&& other) noexcept; + + unsigned int size() const; + unsigned int get_entity_id() const; + + entity get_raw() const; + + private: + void add() const; + void release() const; + + unsigned int id_; + }; +} diff --git a/src/game/scripting/script_value.cpp b/src/game/scripting/script_value.cpp index 5ca275f..2a2e871 100644 --- a/src/game/scripting/script_value.cpp +++ b/src/game/scripting/script_value.cpp @@ -2,6 +2,7 @@ #include "script_value.hpp" #include "entity.hpp" #include "array.hpp" +#include "object.hpp" #include "function.hpp" namespace scripting @@ -117,6 +118,15 @@ namespace scripting this->value_ = variable; } + script_value::script_value(const object& value) + { + game::VariableValue variable{}; + variable.type = game::SCRIPT_OBJECT; + variable.u.pointerValue = value.get_entity_id(); + + this->value_ = variable; + } + script_value::script_value(const function& value) { game::VariableValue variable{}; @@ -142,6 +152,12 @@ namespace scripting return this->is(); } + template <> + bool script_value::is() const + { + return this->is(); + } + template <> bool script_value::is() const { @@ -160,6 +176,12 @@ namespace scripting return this->get_raw().u.uintValue; } + template <> + unsigned short script_value::get() const + { + return static_cast(this->get_raw().u.uintValue); + } + template <> bool script_value::get() const { @@ -262,6 +284,32 @@ namespace scripting return array(this->get_raw().u.uintValue); } + + /*************************************************************** + * Array + **************************************************************/ + + template <> + bool script_value::is() const + { + if (this->get_raw().type != game::SCRIPT_OBJECT) + { + return false; + } + + const auto id = this->get_raw().u.uintValue; + const auto type = game::scr_VarGlob->objectVariableValue[id].w.type; + + return type == game::SCRIPT_STRUCT; + } + + template <> + object script_value::get() const + { + return object(this->get_raw().u.uintValue); + } + + /*************************************************************** * Struct **************************************************************/ @@ -321,6 +369,44 @@ namespace scripting return this->value_.get(); } + std::string script_value::to_string() const + { + if (this->is()) + { + return utils::string::va("%i", this->as()); + } + + if (this->is()) + { + return utils::string::va("%f", this->as()); + } + + if (this->is()) + { + return this->as(); + } + + if (this->is()) + { + const auto vec = this->as(); + return utils::string::va("(%g, %g, %g)", + vec.get_x(), + vec.get_y(), + vec.get_z() + ); + } + + if (this->is()) + { + const auto func = this->as(); + const auto pos = func.get_pos(); + return utils::string::va("[[ function: %p ]]", pos); + } + + return this->type_name(); + } + + value_wrap::value_wrap(const scripting::script_value& value, const std::uint32_t argument_index) : value_(value) , argument_index_(argument_index) diff --git a/src/game/scripting/script_value.hpp b/src/game/scripting/script_value.hpp index 75f3d53..0514f46 100644 --- a/src/game/scripting/script_value.hpp +++ b/src/game/scripting/script_value.hpp @@ -7,6 +7,7 @@ namespace scripting { class entity; class array; + class object; class function; class value_wrap; @@ -82,6 +83,11 @@ namespace scripting return "array"; } + if (info == typeid(object)) + { + return "object"; + } + if (info == typeid(function)) { return "function"; @@ -121,6 +127,8 @@ namespace scripting script_value(const array& value); + script_value(const object& value); + script_value(const function& value); template @@ -153,8 +161,15 @@ namespace scripting return reinterpret_cast(value); } + std::string type_name() const + { + return get_typename(this->get_raw()); + } + const game::VariableValue& get_raw() const; + std::string to_string() const; + variable_value value_{}; private: template diff --git a/src/resource.hpp b/src/resource.hpp new file mode 100644 index 0000000..beabe45 --- /dev/null +++ b/src/resource.hpp @@ -0,0 +1,3 @@ +#pragma once + +#define LIBMYSQL_DLL 100 diff --git a/src/resource.rc b/src/resource.rc new file mode 100644 index 0000000..335da49 --- /dev/null +++ b/src/resource.rc @@ -0,0 +1,3 @@ +#include "resource.hpp" + +LIBMYSQL_DLL RCDATA "../../deps/mysql/lib/libmysql.dll" diff --git a/src/stdinc.cpp b/src/stdinc.cpp index 25163e4..49e54fa 100644 --- a/src/stdinc.cpp +++ b/src/stdinc.cpp @@ -1 +1,24 @@ -#include \ No newline at end of file +#include + +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; + } +} diff --git a/src/stdinc.hpp b/src/stdinc.hpp index 81acaec..4cfb2d9 100644 --- a/src/stdinc.hpp +++ b/src/stdinc.hpp @@ -12,6 +12,9 @@ #define WIN32_LEAN_AND_MEAN #include +#undef min +#undef max + #include #include #include @@ -31,6 +34,11 @@ #include #pragma comment(lib, "urlmon.lib") +#pragma comment(lib, "libmysql.lib") +#pragma comment(lib, "delayimp.lib") +#pragma comment(lib, "secur32.lib") + +#include "resource.hpp" using namespace std::literals; diff --git a/src/utils/binary_resource.cpp b/src/utils/binary_resource.cpp new file mode 100644 index 0000000..4eb5e6c --- /dev/null +++ b/src/utils/binary_resource.cpp @@ -0,0 +1,76 @@ +#include +#include "binary_resource.hpp" + +#include +#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, ¤t_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_; + } +} diff --git a/src/utils/binary_resource.hpp b/src/utils/binary_resource.hpp new file mode 100644 index 0000000..da19af1 --- /dev/null +++ b/src/utils/binary_resource.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include + +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_; + }; +} diff --git a/src/utils/cryptography.cpp b/src/utils/cryptography.cpp new file mode 100644 index 0000000..3d32ca2 --- /dev/null +++ b/src/utils/cryptography.cpp @@ -0,0 +1,691 @@ +#include + +#include "cryptography.hpp" +#include "nt.hpp" +#include + +#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(data); + } + + [[maybe_unused]] char* cs(uint8_t* data) + { + return reinterpret_cast(data); + } + + [[maybe_unused]] const uint8_t* cs(const char* data) + { + return reinterpret_cast(data); + } + + [[maybe_unused]] uint8_t* cs(char* data) + { + return reinterpret_cast(data); + } + + [[maybe_unused]] unsigned long ul(const size_t value) + { + return static_cast(value); + } + + class prng + { + public: + prng(const ltc_prng_descriptor& descriptor, const bool autoseed = true) + : state_(std::make_unique()) + , 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(data), ul(length), this->state_.get()); + } + + void read(void* data, const size_t length) const + { + this->descriptor_.read(static_cast(data), ul(length), this->get_state()); + } + + private: + int id_; + std::unique_ptr 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(&i), sizeof(i)); + this->add_entropy(reinterpret_cast(&i_ptr), sizeof(i_ptr)); + + auto t = time(nullptr); + this->add_entropy(reinterpret_cast(&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(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(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(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(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(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(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(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) diff --git a/src/utils/cryptography.hpp b/src/utils/cryptography.hpp new file mode 100644 index 0000000..01409a7 --- /dev/null +++ b/src/utils/cryptography.hpp @@ -0,0 +1,151 @@ +#pragma once + +#include +#include +#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 + 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(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); + } +} diff --git a/src/utils/hook.hpp b/src/utils/hook.hpp index 1ca94ab..f4301c6 100644 --- a/src/utils/hook.hpp +++ b/src/utils/hook.hpp @@ -55,6 +55,12 @@ namespace utils::hook return static_cast(this->get_original())(args...); } + template + T invoke_pascal(Args... args) + { + return static_cast(this->get_original())(args...); + } + [[nodiscard]] void* get_original() const; private: diff --git a/src/utils/http.cpp b/src/utils/http.cpp index 55725a2..9caa373 100644 --- a/src/utils/http.cpp +++ b/src/utils/http.cpp @@ -1,47 +1,79 @@ #include #include "http.hpp" +#include + +#pragma comment(lib, "ws2_32.lib") + namespace utils::http { - std::optional get_data(const std::string& url) + namespace { - CComPtr stream; - - if (FAILED(URLOpenBlockingStreamA(nullptr, url.data(), &stream, 0, nullptr))) + size_t write_callback(void* contents, const size_t size, const size_t nmemb, void* userp) { - return {}; + const auto buffer = static_cast(userp); + const auto total_size = size * nmemb; + buffer->append(static_cast(contents), total_size); + return total_size; } - - char buffer[0x1000]; - std::string result; - - HRESULT status{}; - - do - { - DWORD bytes_read = 0; - status = stream->Read(buffer, sizeof(buffer), &bytes_read); - - if (bytes_read > 0) - { - result.append(buffer, bytes_read); - } - } - while (SUCCEEDED(status) && status != S_FALSE); - - if (FAILED(status)) - { - return {}; - } - - return {result}; } - std::future> get_data_async(const std::string& url) + std::optional get_data(const std::string& url, const std::string& fields, + const headers& headers, const std::string& method) { - return std::async(std::launch::async, [url]() + curl_slist* header_list = nullptr; + const auto curl = curl_easy_init(); + if (!curl) { - return get_data(url); + return {}; + } + + auto _ = gsl::finally([&]() + { + curl_slist_free_all(header_list); + curl_easy_cleanup(curl); }); + + for (const auto& header : headers) + { + auto data = header.first + ": " + header.second; + header_list = curl_slist_append(header_list, data.data()); + } + + std::string buffer{}; + + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_list); + curl_easy_setopt(curl, CURLOPT_URL, url.data()); + + 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) + { + result result; + result.code = code; + result.buffer = std::move(buffer); + return result; + } + else + { + result result; + result.code = code; + return result; + } } } diff --git a/src/utils/http.hpp b/src/utils/http.hpp index 65628a9..8a481ad 100644 --- a/src/utils/http.hpp +++ b/src/utils/http.hpp @@ -2,10 +2,19 @@ #include #include -#include + +#include namespace utils::http { - std::optional get_data(const std::string& url); - std::future> get_data_async(const std::string& url); + struct result + { + CURLcode code; + std::string buffer; + }; + + using headers = std::unordered_map; + + std::optional get_data(const std::string& url, const std::string& fields = {}, + const headers& headers = {}, const std::string& method = {}); } diff --git a/src/utils/nt.cpp b/src/utils/nt.cpp new file mode 100644 index 0000000..ebcd6b8 --- /dev/null +++ b/src/utils/nt.cpp @@ -0,0 +1,250 @@ +#include +#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(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(this->get_ptr() + this->get_dos_header()->e_lfanew); + } + + PIMAGE_DOS_HEADER library::get_dos_header() const + { + return reinterpret_cast(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 library::get_section_headers() const + { + std::vector 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(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(proc_name); + if (!target_function) return nullptr; + + auto* header = this->get_optional_header(); + if (!header) return nullptr; + + auto* import_descriptor = reinterpret_cast(this->get_ptr() + header->DataDirectory + [IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); + + while (import_descriptor->Name) + { + if (!_stricmp(reinterpret_cast(this->get_ptr() + import_descriptor->Name), module_name.data())) + { + auto* original_thunk_data = reinterpret_cast(import_descriptor-> + OriginalFirstThunk + this->get_ptr()); + auto* thunk_data = reinterpret_cast(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(ordinal_number)) == + target_function) + { + return reinterpret_cast(&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("RtlAdjustPrivilege", 19, true, false, &data); + ntdll.invoke_pascal("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}; + } +} diff --git a/src/utils/nt.hpp b/src/utils/nt.hpp new file mode 100644 index 0000000..4ba32c7 --- /dev/null +++ b/src/utils/nt.hpp @@ -0,0 +1,112 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN +#include + +// min and max is required by gdi, therefore NOMINMAX won't work +#ifdef max +#undef max +#endif + +#ifdef min +#undef min +#endif + +#include +#include +#include + +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 + T get_proc(const std::string& process) const + { + if (!this->is_valid()) T{}; + return reinterpret_cast(GetProcAddress(this->module_, process.data())); + } + + template + std::function get(const std::string& process) const + { + if (!this->is_valid()) return std::function(); + return static_cast(this->get_proc(process)); + } + + template + T invoke(const std::string& process, Args ... args) const + { + auto method = this->get(process); + if (method) return method(args...); + return T(); + } + + template + T invoke_pascal(const std::string& process, Args ... args) const + { + auto method = this->get(process); + if (method) return method(args...); + return T(); + } + + template + T invoke_this(const std::string& process, void* this_ptr, Args ... args) const + { + auto method = this->get(this_ptr, process); + if (method) return method(args...); + return T(); + } + + std::vector 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); +} diff --git a/src/utils/thread_pool.cpp b/src/utils/thread_pool.cpp new file mode 100644 index 0000000..092c89b --- /dev/null +++ b/src/utils/thread_pool.cpp @@ -0,0 +1,96 @@ +#include + +#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 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()); + } + } + + 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(); + } + } +} diff --git a/src/utils/thread_pool.hpp b/src/utils/thread_pool.hpp new file mode 100644 index 0000000..9b9bcb2 --- /dev/null +++ b/src/utils/thread_pool.hpp @@ -0,0 +1,60 @@ +#pragma once + +namespace utils +{ + class thread_pool + { + public: + using job = std::function; + using job_ptr = std::unique_ptr; + + 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 + void push(F&& job) + { + if (this->stopped_) + { + return; + } + + std::lock_guard lock(this->mutex_); + this->queue_.emplace_back(std::make_unique(std::forward(job))); + this->event_.notify_one(); + } + + private: + thread_pool::job_ptr pop_job(); + void run_job(); + + std::mutex mutex_; + std::atomic_bool stopped_; + std::deque queue_; + std::condition_variable event_; + std::deque> workers_; + + }; +}