From 459ecf1695a30a4125eac65589dd8e9753a91d25 Mon Sep 17 00:00:00 2001 From: alice <58637860+alicealys@users.noreply.github.com> Date: Mon, 11 Aug 2025 17:52:46 +0200 Subject: [PATCH] add mysql + update build --- .github/workflows/build.yml | 38 +- .gitignore | 4 +- .gitmodules | 6 + deps/date | 1 + deps/premake/mysql.lua | 52 +++ deps/premake/sqlpp11.lua | 26 ++ deps/sqlpp11 | 1 + premake5.lua | 8 +- src/component/command.cpp | 4 +- src/component/exception.cpp | 6 +- src/component/gsc.cpp | 2 +- src/component/json.cpp | 2 +- src/component/mysql.cpp | 499 ++++++++++++++++++++++ src/component/mysql.hpp | 70 +++ src/component/string.cpp | 39 +- src/dllmain.cpp | 23 + src/game/scripting/array.cpp | 148 +++---- src/game/scripting/array.hpp | 102 +++-- src/game/scripting/container_iterator.hpp | 99 +++++ src/game/scripting/script_value.cpp | 77 +++- src/game/scripting/script_value.hpp | 88 +++- src/plugin.cpp | 6 +- src/resource.hpp | 3 + src/resource.rc | 3 + src/stdinc.hpp | 4 + src/utils/binary_resource.cpp | 76 ++++ src/utils/binary_resource.hpp | 20 + src/utils/hook.hpp | 6 + 28 files changed, 1242 insertions(+), 171 deletions(-) create mode 160000 deps/date create mode 100644 deps/premake/mysql.lua create mode 100644 deps/premake/sqlpp11.lua create mode 160000 deps/sqlpp11 create mode 100644 src/component/mysql.cpp create mode 100644 src/component/mysql.hpp create mode 100644 src/game/scripting/container_iterator.hpp create mode 100644 src/resource.hpp create mode 100644 src/resource.rc create mode 100644 src/utils/binary_resource.cpp create mode 100644 src/utils/binary_resource.hpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6b89eb9..7a768f9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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 @@ -44,3 +46,35 @@ jobs: 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/ssh-key-action@v2.7.0 + 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 }} diff --git a/.gitignore b/.gitignore index 832390e..938f737 100644 --- a/.gitignore +++ b/.gitignore @@ -147,4 +147,6 @@ UpgradeLog*.htm ### Custom user files # User scripts user*.bat -*.code-workspace \ No newline at end of file +*.code-workspace + +deps/mysql diff --git a/.gitmodules b/.gitmodules index 1112a30..4775a9a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -31,3 +31,9 @@ [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 diff --git a/deps/date b/deps/date new file mode 160000 index 0000000..e32a0d8 --- /dev/null +++ b/deps/date @@ -0,0 +1 @@ +Subproject commit e32a0d809c3d3c4797826825dc2d0b287281e3b2 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..1806fe4 --- /dev/null +++ b/deps/sqlpp11 @@ -0,0 +1 @@ +Subproject commit 1806fe4dd89c9fba3a21341c76e6947fb2b5d6df diff --git a/premake5.lua b/premake5.lua index 00bf2af..d602b7e 100644 --- a/premake5.lua +++ b/premake5.lua @@ -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" diff --git a/src/component/command.cpp b/src/component/command.cpp index 0c64fd7..d55ed16 100644 --- a/src/component/command.cpp +++ b/src/component/command.cpp @@ -288,7 +288,7 @@ namespace command for (auto i = 0; i < params.size(); i++) { - array.push(params[i]); + array.emplace_back(params[i]); } function({array}); @@ -309,7 +309,7 @@ namespace command for (auto i = 0; i < params.size(); i++) { - array.push(params[i]); + array.emplace_back(params[i]); } function(player, {array}); diff --git a/src/component/exception.cpp b/src/component/exception.cpp index ab69d07..3f9930e 100644 --- a/src/component/exception.cpp +++ b/src/component/exception.cpp @@ -136,12 +136,12 @@ namespace exception public: void on_startup([[maybe_unused]] plugin::plugin* plugin) override { -#ifdef DEBUG +//#ifdef DEBUG SetUnhandledExceptionFilter(exception_filter); utils::hook::jump(reinterpret_cast(&SetUnhandledExceptionFilter), set_unhandled_exception_filter_stub); -#endif +//#endif } }; } -//REGISTER_COMPONENT(exception::component) +REGISTER_COMPONENT(exception::component) diff --git a/src/component/gsc.cpp b/src/component/gsc.cpp index d344fbb..284e132 100644 --- a/src/component/gsc.cpp +++ b/src/component/gsc.cpp @@ -205,7 +205,7 @@ namespace gsc for (const auto& arg : va) { - array.push(arg); + array.emplace_back(arg); } return array; diff --git a/src/component/json.cpp b/src/component/json.cpp index 004bd5e..4aceafc 100644 --- a/src/component/json.cpp +++ b/src/component/json.cpp @@ -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(); diff --git a/src/component/mysql.cpp b/src/component/mysql.cpp new file mode 100644 index 0000000..94727fb --- /dev/null +++ b/src/component/mysql.cpp @@ -0,0 +1,499 @@ +#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 + { + std::thread thread; + bool done; + bool canceled; + std::unique_ptr handle; + mysql_result_t result; + }; + + uint64_t task_index{}; + std::unordered_map tasks; + + 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.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 + scripting::object create_mysql_query(F&& cb) + { + auto task = &tasks[task_index++]; + + task->done = false; + task->canceled = false; + task->handle = std::make_unique(); + + task->thread = std::thread([=]() + { + try + { + mysql::access([&](database_t& db) + { + task->result = cb(db); + task->done = true; + }); + } + catch (const std::exception& e) + { + printf("%s\n", e.what()); + task->done = true; + } + }); + + return *task->handle.get(); + } + } + + std::array connection_pool; + + 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 cleanup_connections() + { + for (auto& connection : connection_pool) + { + std::unique_lock lock(connection.mutex, std::try_to_lock); + if (!lock.owns_lock()) + { + continue; + } + + const auto now = std::chrono::high_resolution_clock::now(); + const auto diff = now - connection.last_access; + if (diff >= connection_timeout) + { + connection.db.reset(); + } + } + } + + class component final : public component_interface + { + public: + void on_shutdown([[maybe_unused]] plugin::plugin* plugin) override + { + for (auto i = tasks.begin(); i != tasks.end(); ++i) + { + i->second.canceled = true; + + if (i->second.thread.joinable()) + { + i->second.thread.join(); + } + } + } + + void on_startup([[maybe_unused]] plugin::plugin* plugin) override + { + scripting::on_shutdown([]() + { + for (auto i = tasks.begin(); i != tasks.end(); ++i) + { + i->second.canceled = true; + i->second.handle.reset(); + } + }); + + scheduler::loop([] + { + cleanup_connections(); + }, scheduler::async, 1s); + + scheduler::loop([] + { + for (auto i = tasks.begin(); i != tasks.end(); ) + { + if (!i->second.done) + { + ++i; + continue; + } + + if (i->second.thread.joinable()) + { + i->second.thread.join(); + } + + if (!i->second.canceled) + { + const auto result = i->second.result.result + ? generate_result(i->second.result.result) + : generate_result(i->second.result.stmt); + + const auto rows = static_cast(i->second.result.affected_rows); + + scripting::notify(i->second.handle->get_entity_id(), "done", {result, rows, i->second.result.error}); + + if (i->second.result.result) + { + mysql_free_result(i->second.result.result); + i->second.result.result = nullptr; + } + + if (i->second.result.stmt) + { + mysql_stmt_close(i->second.result.stmt); + i->second.result.stmt = nullptr; + } + } + + i = tasks.erase(i); + } + }, scheduler::server); + + gsc::function::add("mysql::set_config", [](const scripting::object& config) + { + 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(); + }); + }); + + gsc::function::add("mysql::query", [](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; + }); + }); + + gsc::function::add("mysql::prepared_statement", [](const std::string& query, const scripting::variadic_args& values) + { + MYSQL_BIND* binds = nullptr; + size_t bind_count = 0; + + const auto free_binds = [=] + { + if (binds == nullptr) + { + return; + } + + for (auto i = 0u; i < bind_count; i++) + { + utils::memory::free(binds[i].buffer); + } + + utils::memory::free(binds); + }; + + try + { + const auto bind_args = [&](const T& args) + { + bind_count = args.size(); + 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; + } + } + } + }; + + if (values.size() > 0 && values[0].is()) + { + bind_args(values[0].as()); + } + else + { + bind_args(values); + } + } + catch (const std::exception& e) + { + free_binds(); + throw e; + } + + return create_mysql_query([=](database_t& db) + { + const auto _0 = gsl::finally([&] + { + free_binds(); + }); + + 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 || + 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; + }); + }); + } + }; +} + +REGISTER_COMPONENT(mysql::component) diff --git a/src/component/mysql.hpp b/src/component/mysql.hpp new file mode 100644 index 0000000..49724eb --- /dev/null +++ b/src/component/mysql.hpp @@ -0,0 +1,70 @@ +#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 = 256; + constexpr auto connection_timeout = 200s; + + using database_mutex_t = std::recursive_mutex; + using database_t = std::unique_ptr; + + struct connection_t + { + database_t db; + database_mutex_t mutex; + std::chrono::high_resolution_clock::time_point start; + std::chrono::high_resolution_clock::time_point last_access; + }; + + extern std::array connection_pool; + + utils::concurrency::container& get_config(); + + template + T access(F&& accessor) + { + for (auto& connection : connection_pool) + { + std::unique_lock lock(connection.mutex, std::try_to_lock); + if (!lock.owns_lock()) + { + continue; + } + + const auto now = std::chrono::high_resolution_clock::now(); + const auto diff = now - connection.start; + + if (!connection.db.get() || !connection.db->ping_server() || diff >= 1h) + { + get_config().access([&](sql::connection_config& cfg) + { + connection.db = std::make_unique(cfg); + connection.start = now; + }); + } + + connection.last_access = now; + return accessor(connection.db); + } + + throw std::runtime_error("out of connections"); + } + + void cleanup_connections(); + + void run_tasks(); +} diff --git a/src/component/string.cpp b/src/component/string.cpp index 63cd927..16dc912 100644 --- a/src/component/string.cpp +++ b/src/component/string.cpp @@ -1,14 +1,8 @@ #include #include "loader/component_loader.hpp" -#include "game/structs.hpp" -#include "game/game.hpp" - #include "gsc.hpp" -#include -#include - // lua/lstrlib.c #define MAX_FORMAT 32 #define L_FMTFLAGSF "-+#0 " @@ -22,7 +16,7 @@ namespace string namespace { // lua/lstrlib.c - const char* getformat(const char* strfrmt, char* form) + const char* getformat(const char* strfrmt, char* form) { const auto len = std::strspn(strfrmt, L_FMTFLAGSF "123456789.") + 1; if (len >= MAX_FORMAT - 10) @@ -37,9 +31,9 @@ namespace string } // lua/lstrlib.c - const char* get_2_digits(const char* s) + const char* get_2_digits(const char* s) { - if (isdigit(static_cast(*s))) + if (isdigit(static_cast(*s))) { s++; if (isdigit(static_cast(*s))) @@ -52,14 +46,14 @@ namespace string } // lua/lstrlib.c - void check_format(const char* form, const char* flags, int precision) + void check_format(const char* form, const char* flags, int precision) { const char* spec = form + 1; spec += std::strspn(spec, flags); - if (*spec != '0') + if (*spec != '0') { spec = get_2_digits(spec); - if (*spec == '.' && precision) + if (*spec == '.' && precision) { spec++; spec = get_2_digits(spec); @@ -110,13 +104,13 @@ namespace string case 'X': flags = L_FMTFLAGSX; intcase: - { - check_format(form, flags, 1); - const auto value = va[va_index].as(); - buffer.append(utils::string::va(form, value)); - va_index++; - break; - } + { + check_format(form, flags, 1); + const auto value = va[va_index].as(); + buffer.append(utils::string::va(form, value)); + va_index++; + break; + } case 'f': case 'F': case 'e': @@ -157,12 +151,13 @@ namespace string } } + class component final : public component_interface { public: void on_startup([[maybe_unused]] plugin::plugin* plugin) override { - gsc::function::add_multiple(format_string, "va", "string::va", + gsc::function::add_multiple(format_string, "va", "string::va", "formatstring", "string::format", "sprintf"); gsc::function::add("printf", [](const std::string& fmt, const scripting::variadic_args& va) @@ -190,7 +185,7 @@ namespace string gsc::function::add("string::ends_with", utils::string::ends_with); gsc::function::add("string::replace", utils::string::replace); - gsc::function::add("string::regex_replace", [](const std::string& str, const std::regex& expr, + gsc::function::add("string::regex_replace", [](const std::string& str, const std::regex& expr, const std::string& with) { return std::regex_replace(str, expr, with); @@ -205,7 +200,7 @@ namespace string { for (const auto& s : match) { - array_match.push((s.str())); + array_match.emplace_back((s.str())); } } diff --git a/src/dllmain.cpp b/src/dllmain.cpp index 92c6b99..5940c3e 100644 --- a/src/dllmain.cpp +++ b/src/dllmain.cpp @@ -6,8 +6,30 @@ #include "game/game.hpp" #include +#include #include +namespace +{ + utils::hook::detour load_library_hook; + HMODULE __stdcall load_library_stub(LPCSTR lib_name, HANDLE file, DWORD flags) + { + if (lib_name == "libmysql.dll"s) + { + static auto dll = utils::binary_resource{LIBMYSQL_DLL, lib_name}; + const auto path = dll.get_extracted_file(); + const auto handle = load_library_hook.invoke_pascal(path.data(), file, flags); + + if (handle != nullptr) + { + return handle; + } + } + + return load_library_hook.invoke_pascal(lib_name, file, flags); + } +} + PLUTONIUM_API plutonium::sdk::plugin* PLUTONIUM_CALLBACK on_initialize() { return plugin::get(); @@ -18,6 +40,7 @@ BOOL APIENTRY DllMain(HMODULE module, DWORD ul_reason_for_call, LPVOID /*reserve 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) diff --git a/src/game/scripting/array.cpp b/src/game/scripting/array.cpp index bc26357..d076c6b 100644 --- a/src/game/scripting/array.cpp +++ b/src/game/scripting/array.cpp @@ -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; - } - - 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; + this->array_->set(this->key_, value); + this->script_value::operator=(value); } - 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(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()) + { + return this->erase(key.as()); + } + + if (key.is()) + { + return this->erase(key.as()); + } + } + + 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()) { - this->get(key.as()); + return this->get(key.as()); } if (key.is()) { - this->get(key.as()); + return this->get(key.as()); } - 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()) { - this->set(key.as(), _value); + return this->set(key.as(), value); } if (key.is()) { - this->set(key.as(), _value); + return this->set(key.as(), value); } + + throw std::runtime_error(std::format("invalid key type '{}'", key.type_name())); } - unsigned int array::get_entity_id() const + 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); + } } diff --git a/src/game/scripting/array.hpp b/src/game/scripting/array.hpp index a7ba48f..dab9522 100644 --- a/src/game/scripting/array.hpp +++ b/src/game/scripting/array.hpp @@ -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 + T as() const + { + try + { + return script_value::as(); + } + 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 + class array_iterator_base : public IteratorType + { + public: + array_iterator_base(const array* container) + : IteratorType(container) + { + } + + array_iterator_base(const array* container, const std::vector& keys, const std::int64_t key_index) + : IteratorType(container, keys, key_index) + { + } + }; + + using array_iterator = array_iterator_base>; + 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 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 + void emplace_back(Args&&... args) + { + this->push_back(std::forward(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 - array_value operator[](const script_value& key) const - { - if (key.is()) - { - return {this->id_, this->get_value_id(key.as())}; - } - - if (key.is()) - { - return {this->id_, this->get_value_id(key.as())}; - } - - 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_{}; }; } diff --git a/src/game/scripting/container_iterator.hpp b/src/game/scripting/container_iterator.hpp new file mode 100644 index 0000000..3278177 --- /dev/null +++ b/src/game/scripting/container_iterator.hpp @@ -0,0 +1,99 @@ +#pragma once + +#include "script_value.hpp" + +namespace scripting +{ + template + 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& keys, const std::int64_t key_index) + : container_(container) + , keys_(keys) + , key_index_(key_index) + { + this->update_pair(); + } + + std::pair& operator*() + { + return this->pair_.value(); + } + + std::pair* operator->() + { + return &this->pair_.value(); + } + + const std::pair& operator*() const + { + return this->pair_.value(); + } + + const std::pair* 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(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 keys_; + std::optional> pair_; + + }; +} diff --git a/src/game/scripting/script_value.cpp b/src/game/scripting/script_value.cpp index a8b3099..3f6e403 100644 --- a/src/game/scripting/script_value.cpp +++ b/src/game/scripting/script_value.cpp @@ -147,12 +147,30 @@ namespace scripting return this->is(); } + template <> + bool script_value::is() const + { + return this->is(); + } + template <> bool script_value::is() const { return this->is(); } + template <> + bool script_value::is() const + { + return this->is(); + } + + template <> + bool script_value::is() const + { + return this->is(); + } + 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(this->get_raw().u.uintValue); + } + + template <> + short script_value::get() const + { + return static_cast(this->get_raw().u.intValue); + } + + template <> + char script_value::get() const + { + return static_cast(this->get_raw().u.intValue); + } + /*********************************************** * Float **********************************************/ @@ -178,7 +214,8 @@ namespace scripting template <> bool script_value::is() 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(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(this->get_raw().u.intValue); + } + return static_cast(this->get_raw().u.floatValue); } @@ -320,6 +369,12 @@ namespace scripting return this->get_raw().type == game::SCRIPT_VECTOR; } + template <> + bool script_value::is() const + { + return this->is(); + } + template <> vector script_value::get() const { @@ -413,4 +468,24 @@ namespace scripting return std::vector::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(); + } } diff --git a/src/game/scripting/script_value.hpp b/src/game/scripting/script_value.hpp index 68fd79e..af6331d 100644 --- a/src/game/scripting/script_value.hpp +++ b/src/game/scripting/script_value.hpp @@ -137,9 +137,20 @@ namespace scripting template T get() const { - if (std::is_constructible::value && this->is()) \ + if constexpr (std::is_pointer::value) { - return T(this->as()); + if (this->is()) + { + return reinterpret_cast(this->as()); + } + } + + if constexpr (std::is_constructible::value) + { + if (this->is()) + { + return T(this->as()); + } } throw std::runtime_error("Invalid type"); @@ -157,7 +168,15 @@ public: \ template bool is() const { - if (std::is_constructible::value && this->is()) \ + if constexpr (std::is_pointer::value) + { + if (this->is()) + { + return true; + } + } + + if (std::is_constructible::value && this->is()) { 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(); } + template + T as_or(const T& default_value) const + { + if (!this->is()) + { + return default_value; + } + + return get(); + } + 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 + T* as_ptr() const + { + const auto value = script_value::as(); + + if (value == nullptr) + { + throw std::runtime_error("is null"); + } + + return reinterpret_cast(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>() const { const auto container_type = get_c_typename>>(); - if (!script_value::as()) + if (!script_value::is()) { const auto type = get_typename(this->get_raw()); @@ -297,15 +352,15 @@ public: \ C> container{}; const auto array = script_value::as(); - for (auto i = 0; i < array.size(); i++) + for (auto i = 0u; i < array.size(); i++) { try { - container.push_back(array.get(i).as()); + container.emplace_back(array.get(i).as()); } catch (const std::exception& e) { - throw std::runtime_error(utils::string::va("element %d of parameter %d of type '%s' %s", + throw std::runtime_error(utils::string::va("element %d of parameter %d of type '%s' %s", i, this->index_, container_type.data(), e.what())); } } @@ -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_{}; + }; } diff --git a/src/plugin.cpp b/src/plugin.cpp index 790977d..f962327 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -45,11 +45,11 @@ namespace plugin { this->interface_ = interface_ptr; this->game_ = game; - //utils::hook::jump(reinterpret_cast(&printf), printf_stub); + utils::hook::jump(reinterpret_cast(&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); + 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() 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.hpp b/src/stdinc.hpp index ba4f10f..7d898a4 100644 --- a/src/stdinc.hpp +++ b/src/stdinc.hpp @@ -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; \ No newline at end of file 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/hook.hpp b/src/utils/hook.hpp index 48db009..9e34126 100644 --- a/src/utils/hook.hpp +++ b/src/utils/hook.hpp @@ -71,6 +71,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: