diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..832390e --- /dev/null +++ b/.gitignore @@ -0,0 +1,150 @@ +### Windows + +version.hpp + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Shortcuts +*.lnk + +### OSX + +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear on external disk +.Spotlight-V100 +.Trashes + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Visual Studio + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +build + +# Visual Studio 2015 cache/options directory +.vs/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +### IDA +*.id0 +*.id1 +*.id2 +*.nam +*.til + +### Custom user files +# User scripts +user*.bat +*.code-workspace \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..466a220 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,30 @@ +[submodule "minhook"] + path = minhook + url = https://github.com/TsudaKageyu/minhook.git +[submodule "GSL"] + path = GSL + url = https://github.com/microsoft/GSL.git +[submodule "zlib"] + path = zlib + url = https://github.com/madler/zlib.git +[submodule "asmjit"] + path = asmjit + url = https://github.com/asmjit/asmjit.git +[submodule "deps/asmjit"] + path = deps/asmjit + url = https://github.com/asmjit/asmjit.git +[submodule "deps/json"] + path = deps/json + url = https://github.com/nlohmann/json.git +[submodule "deps/zlib"] + path = deps/zlib + url = https://github.com/madler/zlib.git +[submodule "deps/GSL"] + path = deps/GSL + url = https://github.com/microsoft/GSL.git +[submodule "deps/minhook"] + path = deps/minhook + url = https://github.com/TsudaKageyu/minhook.git +[submodule "deps/curl"] + path = deps/curl + url = https://github.com/curl/curl.git diff --git a/deps/GSL b/deps/GSL new file mode 160000 index 0000000..d9fc52e --- /dev/null +++ b/deps/GSL @@ -0,0 +1 @@ +Subproject commit d9fc52e20e32fcc804502480a781120b210afd41 diff --git a/deps/asmjit b/deps/asmjit new file mode 160000 index 0000000..33a31f0 --- /dev/null +++ b/deps/asmjit @@ -0,0 +1 @@ +Subproject commit 33a31f04e83845d15effbb9ca27a8f117205baf8 diff --git a/deps/curl b/deps/curl new file mode 160000 index 0000000..134963a --- /dev/null +++ b/deps/curl @@ -0,0 +1 @@ +Subproject commit 134963a5efdc3906257c88ce62dba8d46c292908 diff --git a/deps/json b/deps/json new file mode 160000 index 0000000..b6d00d1 --- /dev/null +++ b/deps/json @@ -0,0 +1 @@ +Subproject commit b6d00d18974358ab1d55dcc3379d0ee3052deb4e diff --git a/deps/minhook b/deps/minhook new file mode 160000 index 0000000..4a45552 --- /dev/null +++ b/deps/minhook @@ -0,0 +1 @@ +Subproject commit 4a455528f61b5a375b1f9d44e7d296d47f18bb18 diff --git a/deps/premake/asmjit.lua b/deps/premake/asmjit.lua new file mode 100644 index 0000000..ee93259 --- /dev/null +++ b/deps/premake/asmjit.lua @@ -0,0 +1,34 @@ +asmjit = { + source = path.join(dependencies.basePath, "asmjit"), +} + +function asmjit.import() + links { "asmjit" } + asmjit.includes() +end + +function asmjit.includes() + includedirs { + path.join(asmjit.source, "src") + } + + defines { + "ASMJIT_STATIC" + } +end + +function asmjit.project() + project "asmjit" + language "C++" + + asmjit.includes() + + files { + path.join(asmjit.source, "src/**.cpp"), + } + + warnings "Off" + kind "StaticLib" +end + +table.insert(dependencies, asmjit) 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/gsl.lua b/deps/premake/gsl.lua new file mode 100644 index 0000000..7a2daf6 --- /dev/null +++ b/deps/premake/gsl.lua @@ -0,0 +1,19 @@ +gsl = { + source = path.join(dependencies.basePath, "GSL"), +} + +function gsl.import() + gsl.includes() +end + +function gsl.includes() + includedirs { + path.join(gsl.source, "include") + } +end + +function gsl.project() + +end + +table.insert(dependencies, gsl) diff --git a/deps/premake/json.lua b/deps/premake/json.lua new file mode 100644 index 0000000..e5eb493 --- /dev/null +++ b/deps/premake/json.lua @@ -0,0 +1,19 @@ +json = { + source = path.join(dependencies.basePath, "json"), +} + +function json.import() + json.includes() +end + +function json.includes() + includedirs { + path.join(json.source, "single_include/*") + } +end + +function json.project() + +end + +table.insert(dependencies, json) diff --git a/deps/premake/minhook.lua b/deps/premake/minhook.lua new file mode 100644 index 0000000..396d4d3 --- /dev/null +++ b/deps/premake/minhook.lua @@ -0,0 +1,31 @@ +minhook = { + source = path.join(dependencies.basePath, "minhook"), +} + +function minhook.import() + links { "minhook" } + minhook.includes() +end + +function minhook.includes() + includedirs { + path.join(minhook.source, "include") + } +end + +function minhook.project() + project "minhook" + language "C" + + minhook.includes() + + files { + path.join(minhook.source, "src/**.h"), + path.join(minhook.source, "src/**.c"), + } + + warnings "Off" + kind "StaticLib" +end + +table.insert(dependencies, minhook) diff --git a/deps/premake/minizip.lua b/deps/premake/minizip.lua new file mode 100644 index 0000000..4a5754b --- /dev/null +++ b/deps/premake/minizip.lua @@ -0,0 +1,43 @@ +minizip = { + source = path.join(dependencies.basePath, "zlib/contrib/minizip"), +} + +function minizip.import() + links { "minizip" } + zlib.import() + minizip.includes() +end + +function minizip.includes() + includedirs { + minizip.source + } + + zlib.includes() +end + +function minizip.project() + project "minizip" + language "C" + + minizip.includes() + + files { + path.join(minizip.source, "*.h"), + path.join(minizip.source, "*.c"), + } + + removefiles { + path.join(minizip.source, "miniunz.c"), + path.join(minizip.source, "minizip.c"), + } + + defines { + "_CRT_SECURE_NO_DEPRECATE", + } + + warnings "Off" + kind "StaticLib" +end + +table.insert(dependencies, minizip) diff --git a/deps/premake/zlib.lua b/deps/premake/zlib.lua new file mode 100644 index 0000000..566a707 --- /dev/null +++ b/deps/premake/zlib.lua @@ -0,0 +1,39 @@ +zlib = { + source = path.join(dependencies.basePath, "zlib"), +} + +function zlib.import() + links { "zlib" } + zlib.includes() +end + +function zlib.includes() + includedirs { + zlib.source + } + + defines { + "ZLIB_CONST", + } +end + +function zlib.project() + project "zlib" + language "C" + + zlib.includes() + + files { + path.join(zlib.source, "*.h"), + path.join(zlib.source, "*.c"), + } + + defines { + "_CRT_SECURE_NO_DEPRECATE", + } + + warnings "Off" + kind "StaticLib" +end + +table.insert(dependencies, zlib) diff --git a/deps/zlib b/deps/zlib new file mode 160000 index 0000000..21767c6 --- /dev/null +++ b/deps/zlib @@ -0,0 +1 @@ +Subproject commit 21767c654d31d2dccdde4330529775c6c5fd5389 diff --git a/generate.bat b/generate.bat new file mode 100644 index 0000000..f3f1a6b --- /dev/null +++ b/generate.bat @@ -0,0 +1,3 @@ +@echo off +call git submodule update --init --recursive +tools\windows\premake5.exe vs2022 \ No newline at end of file diff --git a/premake5.lua b/premake5.lua new file mode 100644 index 0000000..50600a3 --- /dev/null +++ b/premake5.lua @@ -0,0 +1,100 @@ +dependencies = { + basePath = "./deps" +} + +function dependencies.load() + dir = path.join(dependencies.basePath, "premake/*.lua") + deps = os.matchfiles(dir) + + for i, dep in pairs(deps) do + dep = dep:gsub(".lua", "") + require(dep) + end +end + +function dependencies.imports() + for i, proj in pairs(dependencies) do + if type(i) == 'number' then + proj.import() + end + end +end + +function dependencies.projects() + for i, proj in pairs(dependencies) do + if type(i) == 'number' then + proj.project() + end + end +end + +dependencies.load() + +workspace "t5-gsc-utils" + location "./build" + objdir "%{wks.location}/obj/%{cfg.buildcfg}" + targetdir "%{wks.location}/bin/%{cfg.buildcfg}" + targetname "%{prj.name}" + + configurations { "Debug", "Release", } + + language "C++" + cppdialect "C++20" + + architecture "x86" + + systemversion "latest" + symbols "On" + staticruntime "On" + editandcontinue "Off" + warnings "Extra" + characterset "ASCII" + + flags + { + "NoIncrementalLink", + "MultiProcessorCompile", + } + + filter "configurations:Release" + optimize "Full" + defines { "NDEBUG" } + filter {} + + filter "configurations:Debug" + optimize "Debug" + defines { "DEBUG", "_DEBUG" } + filter {} + + startproject "t5-gsc-utils" + + project "t5-gsc-utils" + kind "SharedLib" + language "C++" + + files + { + "./src/**.h", + "./src/**.hpp", + "./src/**.cpp", + } + + includedirs + { + "%{prj.location}/src", + "./src", + } + + resincludedirs + { + "$(ProjectDir)src" + } + + pchheader "stdinc.hpp" + pchsource "src/stdinc.cpp" + buildoptions { "/Zm100 -Zm100" } + + dependencies.imports() + + group "Dependencies" + dependencies.projects() \ No newline at end of file diff --git a/src/component/exception.cpp b/src/component/exception.cpp new file mode 100644 index 0000000..353babb --- /dev/null +++ b/src/component/exception.cpp @@ -0,0 +1,145 @@ +#include +#include "loader/component_loader.hpp" +#include "game/game.hpp" + +#include "scheduler.hpp" + +#include "gsc.hpp" +#include "json.hpp" +#include "scripting.hpp" +#include "game/scripting/execution.hpp" + +#include +#include +#include +#include +#include + +#include + +namespace exception +{ + namespace + { + thread_local struct + { + DWORD code = 0; + PVOID address = nullptr; + } exception_data; + + void show_mouse_cursor() + { + while (ShowCursor(TRUE) < 0); + } + + void display_error_dialog() + { + std::string error_str = utils::string::va("Fatal error (0x%08X) at 0x%p.\n" + "A minidump has been written.\n\n", + exception_data.code, exception_data.address); + + error_str += "Make sure to update your graphics card drivers and install operating system updates!"; + + utils::thread::suspend_other_threads(); + show_mouse_cursor(); + MessageBoxA(nullptr, error_str.data(), "Plutonium T6 ERROR", MB_ICONERROR); + TerminateProcess(GetCurrentProcess(), exception_data.code); + } + + void reset_state() + { + display_error_dialog(); + } + + size_t get_reset_state_stub() + { + static auto* stub = utils::hook::assemble([](utils::hook::assembler& a) + { + a.sub(esp, 0x10); + a.or_(esp, 0x8); + a.jmp(reset_state); + }); + + return reinterpret_cast(stub); + } + + std::string generate_crash_info(const LPEXCEPTION_POINTERS exceptioninfo) + { + std::string info{}; + const auto line = [&info](const std::string& text) + { + info.append(text); + info.append("\r\n"); + }; + + line("Plutonium T6 Crash Dump"); + line(""); + line("Timestamp: "s + utils::string::get_timestamp()); + line(utils::string::va("Exception: 0x%08X", exceptioninfo->ExceptionRecord->ExceptionCode)); + line(utils::string::va("Address: 0x%lX", exceptioninfo->ExceptionRecord->ExceptionAddress)); + +#pragma warning(push) +#pragma warning(disable: 4996) + OSVERSIONINFOEXA version_info; + ZeroMemory(&version_info, sizeof(version_info)); + version_info.dwOSVersionInfoSize = sizeof(version_info); + GetVersionExA(reinterpret_cast(&version_info)); +#pragma warning(pop) + + line(utils::string::va("OS Version: %u.%u", version_info.dwMajorVersion, version_info.dwMinorVersion)); + + return info; + } + + void write_minidump(const LPEXCEPTION_POINTERS exceptioninfo) + { + const std::string crash_name = utils::string::va("minidumps/plutonium-t6-crash-%s.zip", + utils::string::get_timestamp().data()); + + utils::compression::zip::archive zip_file{}; + zip_file.add("crash.dmp", create_minidump(exceptioninfo)); + zip_file.add("info.txt", generate_crash_info(exceptioninfo)); + zip_file.write(crash_name, "Plutonium T6 Crash Dump"); + } + + bool is_harmless_error(const LPEXCEPTION_POINTERS exceptioninfo) + { + const auto code = exceptioninfo->ExceptionRecord->ExceptionCode; + return code == STATUS_INTEGER_OVERFLOW || code == STATUS_FLOAT_OVERFLOW || code == STATUS_SINGLE_STEP; + } + + LONG WINAPI exception_filter(const LPEXCEPTION_POINTERS exceptioninfo) + { + if (is_harmless_error(exceptioninfo)) + { + return EXCEPTION_CONTINUE_EXECUTION; + } + + write_minidump(exceptioninfo); + + exception_data.code = exceptioninfo->ExceptionRecord->ExceptionCode; + exception_data.address = exceptioninfo->ExceptionRecord->ExceptionAddress; + exceptioninfo->ContextRecord->Eip = get_reset_state_stub(); + + return EXCEPTION_CONTINUE_EXECUTION; + } + + LPTOP_LEVEL_EXCEPTION_FILTER WINAPI set_unhandled_exception_filter_stub(LPTOP_LEVEL_EXCEPTION_FILTER) + { + // Don't register anything here... + return &exception_filter; + } + } + + class component final : public component_interface + { + public: + void post_unpack() override + { + SetUnhandledExceptionFilter(exception_filter); + utils::hook::jump(reinterpret_cast(&SetUnhandledExceptionFilter), set_unhandled_exception_filter_stub); + } + }; +} + +REGISTER_COMPONENT(exception::component) diff --git a/src/component/gsc.cpp b/src/component/gsc.cpp new file mode 100644 index 0000000..cc43af5 --- /dev/null +++ b/src/component/gsc.cpp @@ -0,0 +1,230 @@ +#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 scripting +{ + namespace + { + utils::hook::detour get_function_hook; + utils::hook::detour get_method_hook; + + std::unordered_map> functions; + std::unordered_map> methods; + + std::unordered_map function_wraps; + std::unordered_map method_wraps; + + std::vector get_arguments() + { + std::vector args; + + for (auto i = 0; static_cast(i) < game::scr_VmPub->outparamcount; i++) + { + const auto value = game::scr_VmPub->top[-i]; + args.push_back(value); + } + + return args; + } + + void return_value(const scripting::script_value& value) + { + if (game::scr_VmPub->outparamcount) + { + game::Scr_ClearOutParams(game::SCRIPTINSTANCE_SERVER); + } + + push_value(value); + } + + void call_function(const char* name) + { + if (functions.find(name) == functions.end()) + { + return; + } + + const auto args = get_arguments(); + const auto& function = functions[name]; + + try + { + const auto value = function(args); + return_value(value); + } + catch (const std::exception& e) + { + game::Scr_Error(game::SCRIPTINSTANCE_SERVER, e.what(), false); + } + } + + void call_method(const char* name, const game::scr_entref_t entref) + { + if (methods.find(name) == methods.end()) + { + return; + } + + const auto args = get_arguments(); + const auto& method = methods[name]; + + try + { + const entity entity = game::Scr_GetEntityId( + game::SCRIPTINSTANCE_SERVER, entref.entnum, entref.classnum, 0); + + std::vector args_{}; + args_.push_back(entity); + for (const auto& arg : args) + { + args_.push_back(arg); + } + + const auto value = method(args_); + return_value(value); + } + catch (const std::exception& e) + { + game::Scr_Error(game::SCRIPTINSTANCE_SERVER, e.what(), false); + } + } + + void* wrap_function_call(const std::string& name) + { + const auto name_ = utils::memory::get_allocator()->duplicate_string(name); + return utils::hook::assemble([name_](utils::hook::assembler& a) + { + a.pushad(); + a.push(name_); + a.call(call_function); + a.add(esp, 0x4); + a.popad(); + + a.ret(); + }); + } + + void* wrap_method_call(const std::string& name) + { + const auto name_ = utils::memory::get_allocator()->duplicate_string(name); + return utils::hook::assemble([name_](utils::hook::assembler& a) + { + a.pushad(); + a.push(dword_ptr(esp, 0x24)); + a.push(name_); + a.call(call_method); + a.add(esp, 0x8); + a.popad(); + + a.ret(); + }); + } + + script_function get_function_stub(const char** name, int* type) + { + if (function_wraps.find(*name) != function_wraps.end()) + { + return reinterpret_cast(function_wraps[*name]); + } + + return get_function_hook.invoke(name, type); + } + + script_function get_method_stub(const char** name, int* type) + { + if (method_wraps.find(*name) != method_wraps.end()) + { + return reinterpret_cast(method_wraps[*name]); + } + + return get_method_hook.invoke(name, type); + } + + void print(int, const char* fmt, ...) + { + char buffer[2048]; + + va_list ap; + va_start(ap, fmt); + + vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, fmt, ap); + + va_end(ap); + + printf("%s", buffer); + } + + utils::hook::detour scr_settings_hook; + void scr_settings_stub(int /*developer*/, int developer_script, int /*abort_on_error*/, int inst) + { + scr_settings_hook.invoke(0x55D010, developer_script, developer_script, 0, inst); + } + } + + template + void add_function(const std::string& name, F f) + { + const auto wrap = wrap_function(f); + functions[name] = wrap; + const auto call_wrap = wrap_function_call(name); + function_wraps[name] = call_wrap; + } + + template + void add_method(const std::string& name, F f) + { + const auto wrap = wrap_function(f); + methods[name] = wrap; + const auto call_wrap = wrap_method_call(name); + method_wraps[name] = call_wrap; + } + + class component final : public component_interface + { + public: + void post_unpack() override + { + // Don't com_error on gsc errors + utils::hook::nop(SELECT_VALUE(0, 0x4D9BB1), 5); + utils::hook::jump(0x568B90, print); + + scr_settings_hook.create(SELECT_VALUE(0x0, 0x55D010), scr_settings_stub); + get_function_hook.create(SELECT_VALUE(0x0, 0x465E20), get_function_stub); + get_method_hook.create(SELECT_VALUE(0x0, 0x555580), get_method_stub); + + add_function("print_", [](const variadic_args& args) + { + for (const auto& arg : args) + { + printf("%s\t", arg.to_string().data()); + } + printf("\n"); + }); + + add_function("fileexists", utils::io::file_exists); + add_function("writefile", utils::io::write_file); + add_function("movefile", utils::io::move_file); + add_function("filesize", utils::io::file_size); + add_function("createdirectory", utils::io::create_directory); + add_function("directoryexists", utils::io::directory_exists); + add_function("directoryisempty", utils::io::directory_is_empty); + add_function("listfiles", utils::io::list_files); + add_function("removefile", utils::io::remove_file); + add_function("readfile", static_cast(utils::io::read_file)); + } + }; +} + +REGISTER_COMPONENT(scripting::component) \ No newline at end of file diff --git a/src/component/gsc.hpp b/src/component/gsc.hpp new file mode 100644 index 0000000..49421e3 --- /dev/null +++ b/src/component/gsc.hpp @@ -0,0 +1,54 @@ +#pragma once +#include "game/scripting/array.hpp" +#include "game/scripting/execution.hpp" + +namespace scripting +{ + using script_function = void(*)(game::scr_entref_t); + + template + auto wrap_function(const std::function& f, std::index_sequence) + { + return [f]([[maybe_unused]] const function_arguments& args) + { + f(args[I]...); + return script_value{}; + }; + } + + template + auto wrap_function(const std::function& f, std::index_sequence) + { + return [f]([[maybe_unused]] const function_arguments& args) + { + return f(args[I]...); + }; + } + + template + auto wrap_function(const std::function& f, std::index_sequence) + { + return [f]([[maybe_unused]] const function_arguments& args) + { + return script_value{f(args[I]...)}; + }; + } + + template + auto wrap_function(const std::function& f) + { + return wrap_function(f, std::index_sequence_for{}); + } + + template + auto wrap_function(F f) + { + return wrap_function(std::function(f)); + } + + template + void add_function(const std::string& name, F f); + + template + void add_method(const std::string& name, F f); +} diff --git a/src/component/scheduler.cpp b/src/component/scheduler.cpp new file mode 100644 index 0000000..00ddf87 --- /dev/null +++ b/src/component/scheduler.cpp @@ -0,0 +1,159 @@ +#include +#include "loader/component_loader.hpp" + +#include "game/game.hpp" +#include "scheduler.hpp" + +#include +#include + +namespace scheduler +{ + namespace + { + struct task + { + std::function handler{}; + std::chrono::milliseconds interval{}; + std::chrono::high_resolution_clock::time_point last_call{}; + }; + + using task_list = std::vector; + + class task_pipeline + { + public: + void add(task&& task) + { + new_callbacks_.access([&task](task_list& tasks) + { + tasks.emplace_back(std::move(task)); + }); + } + + void execute() + { + callbacks_.access([&](task_list& tasks) + { + this->merge_callbacks(); + + for (auto i = tasks.begin(); i != tasks.end();) + { + const auto now = std::chrono::high_resolution_clock::now(); + const auto diff = now - i->last_call; + + if (diff < i->interval) + { + ++i; + continue; + } + + i->last_call = now; + + const auto res = i->handler(); + if (res == cond_end) + { + i = tasks.erase(i); + } + else + { + ++i; + } + } + }); + } + + private: + utils::concurrency::container new_callbacks_; + utils::concurrency::container callbacks_; + + void merge_callbacks() + { + callbacks_.access([&](task_list& tasks) + { + new_callbacks_.access([&](task_list& new_tasks) + { + tasks.insert(tasks.end(), std::move_iterator(new_tasks.begin()), std::move_iterator(new_tasks.end())); + new_tasks = {}; + }); + }); + } + }; + + std::thread thread; + task_pipeline pipelines[pipeline::count]; + + void execute(const pipeline type) + { + assert(type >= 0 && type < pipeline::count); + pipelines[type].execute(); + } + + void execute_server() + { + execute(pipeline::server); + } + + void server_frame_stub(utils::hook::assembler& a) + { + a.pushad(); + a.call(execute_server); + a.popad(); + + a.jmp(SELECT_VALUE(0x0, 0x0)); + } + } + + void schedule(const std::function& callback, const pipeline type, + const std::chrono::milliseconds delay) + { + assert(type >= 0 && type < pipeline::count); + + task task; + task.handler = callback; + task.interval = delay; + task.last_call = std::chrono::high_resolution_clock::now(); + + pipelines[type].add(std::move(task)); + } + + void loop(const std::function& callback, const pipeline type, + const std::chrono::milliseconds delay) + { + schedule([callback]() + { + callback(); + return cond_continue; + }, type, delay); + } + + void once(const std::function& callback, const pipeline type, + const std::chrono::milliseconds delay) + { + schedule([callback]() + { + callback(); + return cond_end; + }, type, delay); + } + + class component final : public component_interface + { + public: + void post_unpack() override + { + thread = std::thread([]() + { + while (true) + { + execute(pipeline::async); + std::this_thread::sleep_for(10ms); + } + }); + + + } + }; +} + +//REGISTER_COMPONENT(scheduler::component) diff --git a/src/component/scheduler.hpp b/src/component/scheduler.hpp new file mode 100644 index 0000000..cabb37e --- /dev/null +++ b/src/component/scheduler.hpp @@ -0,0 +1,21 @@ +#pragma once + +namespace scheduler +{ + enum pipeline + { + server, + async, + count, + }; + + static const bool cond_continue = false; + static const bool cond_end = true; + + void schedule(const std::function& callback, pipeline type = pipeline::server, + std::chrono::milliseconds delay = 0ms); + void loop(const std::function& callback, pipeline type = pipeline::server, + std::chrono::milliseconds delay = 0ms); + void once(const std::function& callback, pipeline type = pipeline::server, + std::chrono::milliseconds delay = 0ms); +} diff --git a/src/component/scripting.cpp b/src/component/scripting.cpp new file mode 100644 index 0000000..1050a5b --- /dev/null +++ b/src/component/scripting.cpp @@ -0,0 +1,98 @@ +#include +#include "loader/component_loader.hpp" + +#include "game/game.hpp" + +#include "game/scripting/event.hpp" +#include "game/scripting/execution.hpp" + +#include "scripting.hpp" +#include "scheduler.hpp" + +#include +#include + +namespace scripting +{ + std::unordered_map> fields_table; + + std::unordered_map method_map; + std::unordered_map function_map; + + std::unordered_map> script_function_table; + std::unordered_map>> script_function_table_sort; + std::unordered_map> script_function_table_rev; + + namespace + { + utils::hook::detour scr_add_class_field_hook; + utils::hook::detour g_shutdown_game_hook; + + void scr_add_class_field_stub(game::scriptInstance_t inst, unsigned int classnum, char const* name, unsigned int offset) + { + if (fields_table[classnum].find(name) == fields_table[classnum].end()) + { + fields_table[classnum][name] = offset; + } + + scr_add_class_field_hook.invoke(inst, classnum, name, offset); + } + + game::BuiltinMethodDef get_method(const std::string& name) + { + game::BuiltinMethodDef method{}; + + auto pName = name.data(); + int arg = 0; + + const auto func = game::Scr_GetMethod(&pName, &arg, &arg, &arg); + + if (func) + { + method.actionFunc = reinterpret_cast(func); + method.actionString = pName; + } + + return method; + } + + void load_functions() + { + + } + + utils::hook::detour scr_load_script_hook; + + std::vector> shutdown_callbacks; + void g_shutdown_game_stub(const int free_scripts) + { + for (const auto& callback : shutdown_callbacks) + { + callback(); + } + + return g_shutdown_game_hook.invoke(free_scripts); + } + } + + void on_shutdown(const std::function& callback) + { + shutdown_callbacks.push_back(callback); + } + + class component final : public component_interface + { + public: + void post_unpack() override + { + //g_shutdown_game_hook.create(SELECT(0x60DCF0, 0x688A40), g_shutdown_game_stub); + + //scr_add_class_field_hook.create(SELECT(0x6B7620, 0x438AD0), scr_add_class_field_stub); + //scr_post_load_scripts_hook.create(SELECT(0x642EB0, 0x425F80), post_load_scripts_stub); + + //load_functions(); + } + }; +} + +//REGISTER_COMPONENT(scripting::component) diff --git a/src/component/scripting.hpp b/src/component/scripting.hpp new file mode 100644 index 0000000..02df595 --- /dev/null +++ b/src/component/scripting.hpp @@ -0,0 +1,22 @@ +#pragma once + +namespace scripting +{ + extern std::unordered_map> fields_table; + + extern std::unordered_map> script_function_table; + extern std::unordered_map>> script_function_table_sort; + extern std::unordered_map> script_function_table_rev; + + extern std::unordered_map method_map; + extern std::unordered_map function_map; + + using script_function = void(*)(game::scr_entref_t); + + script_function find_function_ptr(const std::string& name); + std::string find_function(const char* pos); + const char* find_function_start(const char* pos); + std::optional> find_function_pair(const char* pos); + + void on_shutdown(const std::function& callback); +} \ No newline at end of file diff --git a/src/dllmain.cpp b/src/dllmain.cpp new file mode 100644 index 0000000..0d10007 --- /dev/null +++ b/src/dllmain.cpp @@ -0,0 +1,21 @@ +#include +#include "loader/component_loader.hpp" + +#include "game/game.hpp" + +#include + +BOOL APIENTRY DllMain(HMODULE /*module_*/, DWORD ul_reason_for_call, LPVOID /*reserved_*/) +{ + if (ul_reason_for_call == DLL_PROCESS_ATTACH) + { + if (game::plutonium::is_up_to_date()) + { + utils::hook::jump(reinterpret_cast(&printf), game::plutonium::printf); + } + + component_loader::post_unpack(); + } + + return TRUE; +} \ No newline at end of file diff --git a/src/exception/minidump.cpp b/src/exception/minidump.cpp new file mode 100644 index 0000000..9160d96 --- /dev/null +++ b/src/exception/minidump.cpp @@ -0,0 +1,94 @@ +#include +#include "minidump.hpp" + +#include +#pragma comment(lib, "dbghelp.lib") + +#include + +namespace exception +{ + namespace + { + constexpr MINIDUMP_TYPE get_minidump_type() + { + const auto type = MiniDumpIgnoreInaccessibleMemory // + | MiniDumpWithHandleData // + | MiniDumpScanMemory // + | MiniDumpWithProcessThreadData // + | MiniDumpWithFullMemoryInfo // + | MiniDumpWithThreadInfo // + | MiniDumpWithUnloadedModules; + + return static_cast(type); + } + + std::string get_temp_filename() + { + char filename[MAX_PATH] = {0}; + char pathname[MAX_PATH] = {0}; + + GetTempPathA(sizeof(pathname), pathname); + GetTempFileNameA(pathname, "Plutonium T6 -", 0, filename); + return filename; + } + + HANDLE write_dump_to_temp_file(const LPEXCEPTION_POINTERS exceptioninfo) + { + MINIDUMP_EXCEPTION_INFORMATION minidump_exception_info = {GetCurrentThreadId(), exceptioninfo, FALSE}; + + auto* const file_handle = CreateFileA(get_temp_filename().data(), GENERIC_WRITE | GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_ALWAYS, + FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, + nullptr); + + if (!MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file_handle, get_minidump_type(), + &minidump_exception_info, + nullptr, + nullptr)) + { + MessageBoxA(nullptr, "There was an error creating the minidump! Hit OK to close the program.", + "Minidump Error", MB_OK | MB_ICONERROR); + TerminateProcess(GetCurrentProcess(), 123); + } + + return file_handle; + } + + std::string read_file(HANDLE file_handle) + { + FlushFileBuffers(file_handle); + SetFilePointer(file_handle, 0, nullptr, FILE_BEGIN); + + std::string buffer{}; + + DWORD bytes_read = 0; + char temp_bytes[0x2000]; + + do + { + if (!ReadFile(file_handle, temp_bytes, sizeof(temp_bytes), &bytes_read, nullptr)) + { + return {}; + } + + buffer.append(temp_bytes, bytes_read); + } + while (bytes_read == sizeof(temp_bytes)); + + return buffer; + } + } + + std::string create_minidump(const LPEXCEPTION_POINTERS exceptioninfo) + { + auto* const file_handle = write_dump_to_temp_file(exceptioninfo); + + const auto _ = gsl::finally([file_handle]() + { + CloseHandle(file_handle); + }); + + return read_file(file_handle); + } +} diff --git a/src/exception/minidump.hpp b/src/exception/minidump.hpp new file mode 100644 index 0000000..9378639 --- /dev/null +++ b/src/exception/minidump.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace exception +{ + std::string create_minidump(LPEXCEPTION_POINTERS exceptioninfo); +} diff --git a/src/game/game.cpp b/src/game/game.cpp new file mode 100644 index 0000000..5e6d19a --- /dev/null +++ b/src/game/game.cpp @@ -0,0 +1,82 @@ +#include +#include "game.hpp" + +#include + +namespace game +{ + gamemode current = *reinterpret_cast(0x401337) == 0x281488C0 + ? gamemode::multiplayer + : gamemode::singleplayer; + + namespace environment + { + bool is_mp() + { + return current == gamemode::multiplayer; + } + + bool is_sp() + { + return current == gamemode::singleplayer; + } + } + + void AddRefToValue(scriptInstance_t inst, const VariableValue* value) + { + AddRefToValue_(inst, value->type, value->u); + } + + unsigned int AllocVariable(scriptInstance_t inst) + { + static const auto func = utils::hook::assemble([](utils::hook::assembler& a) + { + a.mov(eax, 0); + a.call(SELECT_VALUE(0, 0x8E49A0)); + a.ret(); + }); + + return utils::hook::invoke(func, inst); + } + + VariableValue Scr_GetArrayIndexValue(scriptInstance_t inst, unsigned int name) + { + VariableValue value{}; + + if (name >= 0x10000) + { + if (name >= 0x17FFE) + { + value.type = 6; + value.u.intValue = name - 0x800000; + } + else + { + value.type = 1; + value.u.intValue = name - 0x10000; + } + } + else + { + value.type = 2; + value.u.intValue = name; + } + + return value; + } + + unsigned int Scr_GetSelf(scriptInstance_t inst, unsigned int threadId) + { + return game::scr_VarGlob->variableList[threadId + 1].u.o.u.self; + } + + namespace plutonium + { + bool is_up_to_date() + { + //const auto value = *reinterpret_cast(0); + //return value == 0; + return false; + } + } +} diff --git a/src/game/game.hpp b/src/game/game.hpp new file mode 100644 index 0000000..4289cb8 --- /dev/null +++ b/src/game/game.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include "structs.hpp" + +#define SELECT_VALUE(sp, mp) (game::environment::is_sp() ? (sp) : (mp)) + +namespace game +{ + enum gamemode + { + none, + multiplayer, + singleplayer + }; + + extern gamemode current; + + namespace environment + { + bool is_mp(); + bool is_sp(); + } + + template + class symbol + { + public: + symbol(const size_t sp_address, const size_t mp_address) + : sp_object_(reinterpret_cast(sp_address)) + , mp_object_(reinterpret_cast(mp_address)) + { + } + + T* get() const + { + if (environment::is_mp()) + { +#ifdef DEBUG + if (mp_object_ == nullptr) + { + MessageBoxA(nullptr, "nullptr symbol", "", 0); + } +#endif + + return mp_object_; + } + +#ifdef DEBUG + if (sp_object_ == nullptr) + { + MessageBoxA(nullptr, "nullptr symbol", "", 0); + } +#endif + + return sp_object_; + } + + operator T* () const + { + return this->get(); + } + + T* operator->() const + { + return this->get(); + } + + private: + T* sp_object_; + T* mp_object_; + }; + + void AddRefToValue(scriptInstance_t inst, const VariableValue* value); + unsigned int AllocVariable(scriptInstance_t inst); + + VariableValue Scr_GetArrayIndexValue(scriptInstance_t inst, unsigned int name); + unsigned int Scr_GetSelf(scriptInstance_t inst, unsigned int threadId); + + namespace plutonium + { + bool is_up_to_date(); + } +} + +#include "symbols.hpp" diff --git a/src/game/scripting/array.cpp b/src/game/scripting/array.cpp new file mode 100644 index 0000000..fb0f430 --- /dev/null +++ b/src/game/scripting/array.cpp @@ -0,0 +1,324 @@ +#include +#include "array.hpp" +#include "execution.hpp" + +namespace scripting +{ + array_value::array_value(unsigned int parent_id, unsigned int id) + : id_(id) + , parent_id_(parent_id) + { + if (!this->id_) + { + return; + } + + const auto value = game::scr_VarGlob->variableList[this->id_]; + game::VariableValue variable{}; + variable.u = value.u.u; + variable.type = value.w.type & 0x1F; + + this->value_ = variable; + } + + void array_value::operator=(const script_value& value) + { + if (!this->id_) + { + return; + } + + const auto& value_0 = value.get_raw(); + + const auto variable = &game::scr_VarGlob->variableList[this->id_]; + game::VariableValue variable_{}; + variable_.type = variable->w.type & 0x1F; + variable_.u = variable->u.u; + + game::AddRefToValue(game::SCRIPTINSTANCE_SERVER, &value_0); + game::RemoveRefToValue(game::SCRIPTINSTANCE_SERVER, variable_.type, variable_.u); + + variable->w.type = value_0.type & 0x1F; + variable->u.u = value_0.u; + + this->value_ = value_0; + } + + array::array(const unsigned int id) + : id_(id) + { + this->add(); + } + + array::array(const array& other) + { + this->operator=(other); + } + + array::array(array&& other) noexcept + { + this->id_ = other.id_; + other.id_ = 0; + } + + array::array() + { + this->id_ = game::Scr_AllocArray(game::SCRIPTINSTANCE_SERVER); + } + + array::~array() + { + this->release(); + } + + array& array::operator=(const array& other) + { + if (&other != this) + { + this->release(); + this->id_ = other.id_; + this->add(); + } + + return *this; + } + + array& array::operator=(array&& other) noexcept + { + if (&other != this) + { + this->release(); + this->id_ = other.id_; + other.id_ = 0; + } + + return *this; + } + + void array::add() const + { + if (this->id_) + { + game::VariableValue value{}; + value.u.uintValue = this->id_; + value.type = game::SCRIPT_OBJECT; + + game::AddRefToValue(game::SCRIPTINSTANCE_SERVER, &value); + } + } + + void array::release() const + { + if (this->id_) + { + game::VariableValue value{}; + value.u.uintValue = this->id_; + value.type = game::SCRIPT_OBJECT; + + game::RemoveRefToValue(game::SCRIPTINSTANCE_SERVER, value.type, value.u); + } + } + + std::vector array::get_keys() const + { + std::vector result; + + auto current = game::scr_VarGlob->variableList[this->id_ + 1].nextSibling; + + while (current) + { + const auto var = &game::scr_VarGlob->variableList[current + 0x8000]; + const auto key_value = game::Scr_GetArrayIndexValue(game::SCRIPTINSTANCE_SERVER, var->w.status >> 8); + result.push_back(key_value); + + const auto next_sibling = game::scr_VarGlob->variableList[current + 0x8000].nextSibling; + if (!next_sibling) + { + break; + } + + current = game::scr_VarGlob->variableList[next_sibling + 0x8000].hash.id; + } + + return result; + } + + unsigned int array::size() const + { + return game::Scr_GetSelf(game::SCRIPTINSTANCE_SERVER, this->id_); + } + + unsigned int array::push(const script_value& value) const + { + this->set(this->size(), value); + return this->size(); + } + + void array::erase(const unsigned int index) const + { + const auto variable_id = game::FindArrayVariable(game::SCRIPTINSTANCE_SERVER, this->id_, index); + if (variable_id) + { + game::RemoveVariableValue(game::SCRIPTINSTANCE_SERVER, this->id_, variable_id); + } + } + + void array::erase(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); + if (variable_id) + { + game::RemoveVariableValue(game::SCRIPTINSTANCE_SERVER, this->id_, variable_id); + } + } + + 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); + const auto variable_id = game::FindVariable(game::SCRIPTINSTANCE_SERVER, this->id_, string_value); + + if (!variable_id) + { + return {}; + } + + const auto value = game::scr_VarGlob->variableList[variable_id + 0x8000]; + game::VariableValue variable{}; + variable.u = value.u.u; + variable.type = value.w.type & 0x1F; + + return variable; + } + + script_value array::get(const unsigned int index) const + { + const auto variable_id = game::FindArrayVariable(game::SCRIPTINSTANCE_SERVER, this->id_, index); + + if (!variable_id) + { + return {}; + } + + const auto value = game::scr_VarGlob->variableList[variable_id + 0x8000]; + game::VariableValue variable{}; + variable.u = value.u.u; + variable.type = value.w.type & 0x1F; + + return variable; + } + + script_value array::get(const script_value& key) const + { + if (key.is()) + { + this->get(key.as()); + } + + if (key.is()) + { + this->get(key.as()); + } + + return {}; + } + + void array::set(const std::string& key, const script_value& value) const + { + const auto& value_ = value.get_raw(); + const auto variable_id = this->get_value_id(key); + + if (!variable_id) + { + return; + } + + const auto variable = &game::scr_VarGlob->variableList[variable_id + 0x8000]; + game::VariableValue variable_{}; + variable_.type = variable->w.type & 0x1F; + variable_.u = variable->u.u; + + game::AddRefToValue(game::SCRIPTINSTANCE_SERVER, &value_); + game::RemoveRefToValue(game::SCRIPTINSTANCE_SERVER, variable_.type, variable_.u); + + variable->w.type |= value_.type; + variable->u.u = value_.u; + } + + void array::set(const unsigned int index, const script_value& value) const + { + const auto& value_ = value.get_raw(); + const auto variable_id = this->get_value_id(index); + + if (!variable_id) + { + return; + } + + auto variable_list = *game::scr_VarGlob; + const auto variable = &game::scr_VarGlob->variableList[variable_id + 0x8000]; + game::VariableValue variable_{}; + variable_.type = variable->w.type & 0x1F; + variable_.u = variable->u.u; + + game::AddRefToValue(game::SCRIPTINSTANCE_SERVER, &value_); + game::RemoveRefToValue(game::SCRIPTINSTANCE_SERVER, variable_.type, variable_.u); + + variable->w.type |= value_.type; + variable->u.u = value_.u; + } + + void array::set(const script_value& key, const script_value& _value) const + { + if (key.is()) + { + this->set(key.as(), _value); + } + + if (key.is()) + { + this->set(key.as(), _value); + } + } + + unsigned int array::get_entity_id() const + { + return this->id_; + } + + unsigned int 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); + + if (!variable_id) + { + return game::GetNewVariable(game::SCRIPTINSTANCE_SERVER, this->id_, string_value); + } + + return variable_id; + } + + unsigned int array::get_value_id(const unsigned int index) const + { + const auto variable_id = game::FindArrayVariable(game::SCRIPTINSTANCE_SERVER, this->id_, index); + if (!variable_id) + { + return game::GetNewArrayVariable(game::SCRIPTINSTANCE_SERVER, this->id_, index); + } + + return variable_id; + } + + entity array::get_raw() const + { + return entity(this->id_); + } +} diff --git a/src/game/scripting/array.hpp b/src/game/scripting/array.hpp new file mode 100644 index 0000000..9edc697 --- /dev/null +++ b/src/game/scripting/array.hpp @@ -0,0 +1,83 @@ +#pragma once +#include "script_value.hpp" + +namespace scripting +{ + class array_value : public script_value + { + public: + array_value(unsigned int, unsigned int); + void operator=(const script_value&); + private: + unsigned int id_; + unsigned int parent_id_; + }; + + class array final + { + public: + array(); + array(const unsigned int); + + array(const array& other); + array(array&& other) noexcept; + + ~array(); + + array& operator=(const array& other); + array& operator=(array&& other) noexcept; + + std::vector get_keys() const; + unsigned int 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; + + script_value get(const script_value&) const; + script_value get(const std::string&) const; + script_value get(const unsigned int) 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; + + unsigned int get_entity_id() const; + + unsigned int get_value_id(const std::string&) const; + unsigned int get_value_id(const unsigned int) const; + + entity get_raw() const; + + array_value operator[](const int index) const + { + return {this->id_, this->get_value_id(index)}; + } + + 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())}; + } + } + + private: + void add() const; + void release() const; + + unsigned int id_{}; + }; +} diff --git a/src/game/scripting/entity.cpp b/src/game/scripting/entity.cpp new file mode 100644 index 0000000..d7ebd6d --- /dev/null +++ b/src/game/scripting/entity.cpp @@ -0,0 +1,120 @@ +#include +#include "entity.hpp" +#include "script_value.hpp" +#include "execution.hpp" + +namespace scripting +{ + entity::entity() + : entity(0) + { + } + + entity::entity(const entity& other) : entity(other.entity_id_) + { + } + + entity::entity(entity&& other) noexcept + { + this->entity_id_ = other.entity_id_; + other.entity_id_ = 0; + } + + entity::entity(const unsigned int entity_id) + : entity_id_(entity_id) + { + this->add(); + } + + entity::~entity() + { + this->release(); + } + + entity& entity::operator=(const entity& other) + { + if (&other != this) + { + this->release(); + this->entity_id_ = other.entity_id_; + this->add(); + } + + return *this; + } + + entity& entity::operator=(entity&& other) noexcept + { + if (&other != this) + { + this->release(); + this->entity_id_ = other.entity_id_; + other.entity_id_ = 0; + } + + return *this; + } + + unsigned int entity::get_entity_id() const + { + return this->entity_id_; + } + + game::scr_entref_t entity::get_entity_reference() const + { + if (!this->entity_id_) + { + const auto not_null = static_cast(~0ui16); + return game::scr_entref_t{not_null, not_null}; + } + + game::scr_entref_t entref{}; + game::Scr_GetEntityIdRef(&entref, game::SCRIPTINSTANCE_SERVER, this->get_entity_id()); + return entref; + } + + bool entity::operator==(const entity& other) const noexcept + { + return this->get_entity_id() == other.get_entity_id(); + } + + bool entity::operator!=(const entity& other) const noexcept + { + return !this->operator==(other); + } + + void entity::add() const + { + if (this->entity_id_) + { + game::VariableValue value; + value.type = game::SCRIPT_OBJECT; + value.u.uintValue = this->entity_id_; + + game::AddRefToValue(game::SCRIPTINSTANCE_SERVER, &value); + } + } + + void entity::release() const + { + if (this->entity_id_) + { + game::VariableValue value; + value.type = game::SCRIPT_OBJECT; + value.u.uintValue = this->entity_id_; + + game::RemoveRefToValue(game::SCRIPTINSTANCE_SERVER, value.type, value.u); + } + } + + void entity::set(const std::string& field, const script_value& value) const + { + set_entity_field(*this, field, value); + } + + template <> + script_value entity::get(const std::string& field) const + { + return get_entity_field(*this, field); + } +} diff --git a/src/game/scripting/entity.hpp b/src/game/scripting/entity.hpp new file mode 100644 index 0000000..22382fa --- /dev/null +++ b/src/game/scripting/entity.hpp @@ -0,0 +1,47 @@ +#pragma once +#include "game/game.hpp" +#include "script_value.hpp" + +namespace scripting +{ + class entity final + { + public: + entity(); + entity(unsigned int entity_id); + + entity(const entity& other); + entity(entity&& other) noexcept; + + ~entity(); + + entity& operator=(const entity& other); + entity& operator=(entity&& other) noexcept; + + void set(const std::string& field, const script_value& value) const; + + template + T get(const std::string& field) const; + + unsigned int get_entity_id() const; + game::scr_entref_t get_entity_reference() const; + + bool operator ==(const entity& other) const noexcept; + bool operator !=(const entity& other) const noexcept; + + private: + unsigned int entity_id_; + + void add() const; + void release() const; + }; + + template <> + script_value entity::get(const std::string& field) const; + + template + T entity::get(const std::string& field) const + { + return this->get(field).as(); + } +} diff --git a/src/game/scripting/event.hpp b/src/game/scripting/event.hpp new file mode 100644 index 0000000..bc1d53e --- /dev/null +++ b/src/game/scripting/event.hpp @@ -0,0 +1,13 @@ +#pragma once +#include "script_value.hpp" +#include "entity.hpp" + +namespace scripting +{ + struct event + { + std::string name; + entity entity{}; + std::vector arguments; + }; +} diff --git a/src/game/scripting/execution.cpp b/src/game/scripting/execution.cpp new file mode 100644 index 0000000..3a17f94 --- /dev/null +++ b/src/game/scripting/execution.cpp @@ -0,0 +1,160 @@ +#include +#include "execution.hpp" +#include "safe_execution.hpp" +#include "stack_isolation.hpp" + +#include "component/scripting.hpp" + +namespace scripting +{ + namespace + { + game::VariableValue* allocate_argument() + { + game::VariableValue* value_ptr = ++game::scr_VmPub->top; + ++game::scr_VmPub->inparamcount; + return value_ptr; + } + + script_value get_return_value() + { + if (game::scr_VmPub->inparamcount == 0) + { + return {}; + } + + game::Scr_ClearOutParams(game::SCRIPTINSTANCE_SERVER); + game::scr_VmPub->outparamcount = game::scr_VmPub->inparamcount; + game::scr_VmPub->inparamcount = 0; + + return script_value(game::scr_VmPub->top[1 - game::scr_VmPub->outparamcount]); + } + + int get_field_id(const int classnum, const std::string& field) + { + if (scripting::fields_table[classnum].find(field) != scripting::fields_table[classnum].end()) + { + return scripting::fields_table[classnum][field]; + } + + return -1; + } + } + + void push_value(const script_value& value) + { + auto* value_ptr = allocate_argument(); + *value_ptr = value.get_raw(); + + game::AddRefToValue(game::SCRIPTINSTANCE_SERVER, value_ptr); + } + + void notify(const entity& entity, const std::string& event, const std::vector& arguments) + { + stack_isolation _; + for (auto i = arguments.rbegin(); i != arguments.rend(); ++i) + { + push_value(*i); + } + + const auto event_id = game::SL_GetString(event.data(), 0, game::SCRIPTINSTANCE_SERVER); + game::Scr_NotifyId(game::SCRIPTINSTANCE_SERVER, 0, entity.get_entity_id(), event_id, game::scr_VmPub->inparamcount); + } + + script_value exec_ent_thread(const entity& entity, const char* pos, const std::vector& arguments) + { + const auto id = entity.get_entity_id(); + + stack_isolation _; + for (auto i = arguments.rbegin(); i != arguments.rend(); ++i) + { + push_value(*i); + } + + game::VariableValue value{}; + value.type = game::SCRIPT_OBJECT; + value.u.uintValue = id; + game::AddRefToValue(game::SCRIPTINSTANCE_SERVER, &value); + + const auto local_id = game::AllocThread(game::SCRIPTINSTANCE_SERVER, id); + const auto result = game::VM_Execute(game::SCRIPTINSTANCE_SERVER, local_id, pos, arguments.size()); + game::RemoveRefToObject(game::SCRIPTINSTANCE_SERVER, result); + + return get_return_value(); + } + + script_value get_custom_field(const entity& entity, const std::string& field) + { + const object object = entity.get_entity_id(); + return object.get(field); + } + + void set_custom_field(const entity& entity, const std::string& field, const script_value& value) + { + const object object = entity.get_entity_id(); + object.set(field, value); + } + + void set_entity_field(const entity& entity, const std::string& field, const script_value& value) + { + const auto entref = entity.get_entity_reference(); + const int id = get_field_id(entref.classnum, field); + + if (id != -1) + { + stack_isolation _; + push_value(value); + + game::scr_VmPub->outparamcount = game::scr_VmPub->inparamcount; + game::scr_VmPub->inparamcount = 0; + + if (!safe_execution::set_entity_field(entref, id)) + { + throw std::runtime_error("Failed to set value for field '" + field + "'"); + } + } + else + { + set_custom_field(entity, field, value); + } + } + + script_value get_entity_field(const entity& entity, const std::string& field) + { + const auto entref = entity.get_entity_reference(); + const auto id = get_field_id(entref.classnum, field); + + if (id != -1) + { + stack_isolation _; + + game::VariableValue value{}; + if (!safe_execution::get_entity_field(entref, id, &value)) + { + throw std::runtime_error("Failed to get value for field '" + field + "'"); + } + + const auto __ = gsl::finally([&value]() + { + game::RemoveRefToValue(game::SCRIPTINSTANCE_SERVER, value.type, value.u); + }); + + return value; + } + else + { + return get_custom_field(entity, field); + } + } + + unsigned int make_object() + { + unsigned int index = 0; + const auto id = game::AllocVariable(game::SCRIPTINSTANCE_SERVER); + const auto variable = &game::scr_VarGlob->variableList[id + 1]; + variable->w.type = game::SCRIPT_STRUCT; + variable->u.o.refCount = 0; + + return index; + } +} \ No newline at end of file diff --git a/src/game/scripting/execution.hpp b/src/game/scripting/execution.hpp new file mode 100644 index 0000000..c4b2c3c --- /dev/null +++ b/src/game/scripting/execution.hpp @@ -0,0 +1,22 @@ +#pragma once +#include "game/game.hpp" +#include "entity.hpp" +#include "array.hpp" +#include "object.hpp" +#include "function.hpp" +#include "thread.hpp" +#include "script_value.hpp" + +namespace scripting +{ + void push_value(const script_value& value); + + script_value exec_ent_thread(const entity& entity, const char* pos, const std::vector& arguments); + + void set_entity_field(const entity& entity, const std::string& field, const script_value& value); + script_value get_entity_field(const entity& entity, const std::string& field); + + void notify(const entity& entity, const std::string& event, const std::vector& arguments); + + unsigned int make_object(); +} diff --git a/src/game/scripting/function.cpp b/src/game/scripting/function.cpp new file mode 100644 index 0000000..5f511ac --- /dev/null +++ b/src/game/scripting/function.cpp @@ -0,0 +1,42 @@ +#include +#include "function.hpp" +#include "execution.hpp" +#include "../../component/scripting.hpp" + +namespace scripting +{ + function::function(const char* pos) + : pos_(pos) + { + } + + script_value function::get_raw() const + { + game::VariableValue value; + value.type = game::SCRIPT_FUNCTION; + value.u.codePosValue = this->pos_; + + return value; + } + + const char* function::get_pos() const + { + return this->pos_; + } + + std::string function::get_name() const + { + if (scripting::script_function_table_rev.find(this->pos_) != scripting::script_function_table_rev.end()) + { + const auto& func = scripting::script_function_table_rev[this->pos_]; + return utils::string::va("%s::%s", func.first.data(), func.second.data()); + } + + return "unknown function"; + } + + script_value function::call(const entity& self, const arguments& arguments) const + { + return exec_ent_thread(self, this->pos_, arguments); + } +} diff --git a/src/game/scripting/function.hpp b/src/game/scripting/function.hpp new file mode 100644 index 0000000..b0eb4f8 --- /dev/null +++ b/src/game/scripting/function.hpp @@ -0,0 +1,48 @@ +#pragma once +#include "entity.hpp" +#include "script_value.hpp" + +namespace scripting +{ + class function + { + public: + function(const char*); + + script_value get_raw() const; + const char* get_pos() const; + std::string get_name() const; + + script_value call(const entity& self, const arguments& arguments) const; + + script_value operator()(const entity& self, const arguments& arguments) const + { + return this->call(self, arguments); + } + + script_value operator()(const arguments& arguments) const + { + return this->call(*game::levelEntityId, arguments); + } + + script_value operator()() const + { + return this->call(*game::levelEntityId, {}); + } + + template + arguments operator()(T... arguments) const + { + return this->call(*game::levelEntityId, {arguments...}); + } + + template + arguments operator()(const entity& self, T... arguments) const + { + return this->call(self, {arguments...}); + } + + private: + const char* pos_; + }; +} diff --git a/src/game/scripting/object.cpp b/src/game/scripting/object.cpp new file mode 100644 index 0000000..5bf24ca --- /dev/null +++ b/src/game/scripting/object.cpp @@ -0,0 +1,233 @@ +#include +#include "object.hpp" +#include "execution.hpp" + +namespace scripting +{ + object_value::object_value(unsigned int parent_id, unsigned int id) + : id_(id) + , parent_id_(parent_id) + { + /*if (!this->id_) + { + return; + } + + const auto value = game::scr_VarGlob->childVariableValue[this->id_]; + game::VariableValue variable; + variable.u = value.u.u; + variable.type = (game::scriptType_e)value.type; + + this->value_ = variable;*/ + } + + void object_value::operator=(const script_value& /*value*/) + { + /*if (!this->id_) + { + return; + } + + const auto value = _value.get_raw(); + + const auto variable = &game::scr_VarGlob->childVariableValue[this->id_]; + game::VariableValue variable_{}; + variable_.type = variable->type; + variable_.u = variable->u.u; + + game::AddRefToValue(game::SCRIPTINSTANCE_SERVER, &value); + game::RemoveRefToValue(game::SCRIPTINSTANCE_SERVER, variable->type, variable->u.u); + + variable->type = gsl::narrow_cast(value.type); + variable->u.u = value.u; + + this->value_ = value;*/ + } + + object::object(const unsigned int id) + : id_(id) + { + this->add(); + } + + object::object(const object& other) + { + this->operator=(other); + } + + object::object(object&& other) noexcept + { + this->id_ = other.id_; + other.id_ = 0; + } + + object::object() + { + this->id_ = make_object(); + } + + object::object(std::unordered_map values) + { + this->id_ = make_object(); + + for (const auto& value : values) + { + this->set(value.first, value.second); + } + } + + 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::VariableValue value{}; + value.u.uintValue = this->id_; + value.type = game::SCRIPT_OBJECT; + + game::AddRefToValue(game::SCRIPTINSTANCE_SERVER, &value); + } + } + + void object::release() const + { + if (this->id_) + { + game::VariableValue value{}; + value.u.uintValue = this->id_; + value.type = game::SCRIPT_OBJECT; + + game::RemoveRefToValue(game::SCRIPTINSTANCE_SERVER, value.type, value.u); + } + } + + std::vector object::get_keys() const + { + std::vector result; + + /*auto current = game::scr_VarGlob->objectVariableChildren[this->id_].firstChild; + + while (current) + { + const auto var = game::scr_VarGlob->childVariableValue[current]; + const auto string_id = (unsigned __int8)var.name_lo + (var.k.keys.name_hi << 8); + + if (string_id < 0x34BC) + { + const auto string = reinterpret_cast(SELECT_VALUE(0x2DACC28, 0x2D7CF28))[string_id]; + result.push_back(string); + } + + current = var.nextSibling; + }*/ + + return result; + } + + unsigned int object::size() const + { + return game::Scr_GetSelf(game::SCRIPTINSTANCE_SERVER, this->id_); + } + + void object::erase(const std::string& key) const + { + const auto string_value = game::SL_GetCanonicalString(key.data(), 0); + const auto variable_id = game::FindVariable(game::SCRIPTINSTANCE_SERVER, this->id_, string_value); + if (variable_id) + { + game::RemoveVariableValue(game::SCRIPTINSTANCE_SERVER, this->id_, variable_id); + } + } + + script_value object::get(const std::string& /*key*/) const + { + /*const auto string_value = game::SL_GetCanonicalString(key.data(), 0); + const auto variable_id = game::FindVariable(game::SCRIPTINSTANCE_SERVER, this->id_, string_value); + + if (!variable_id) + { + return {}; + } + + const auto value = game::scr_VarGlob->childVariableValue[variable_id]; + game::VariableValue variable; + variable.u = value.u.u; + variable.type = (game::scriptType_e)value.type; + + return variable;*/ + return {}; + } + + void object::set(const std::string& /*key*/, const script_value& /*value*/) const + { + /*const auto value = value_.get_raw(); + const auto variable_id = this->get_value_id(key); + + if (!variable_id) + { + return; + } + + const auto variable = &game::scr_VarGlob->childVariableValue[variable_id]; + game::VariableValue variable_{}; + variable_.type = variable->type; + variable_.u = variable->u.u; + + game::AddRefToValue(game::SCRIPTINSTANCE_SERVER, &value); + game::RemoveRefToValue(game::SCRIPTINSTANCE_SERVER, variable_.type, variable_.u); + + variable->type = gsl::narrow_cast(value.type); + variable->u.u = value.u;*/ + } + + unsigned int object::get_entity_id() const + { + return this->id_; + } + + unsigned int object::get_value_id(const std::string& key) const + { + const auto string_value = game::SL_GetCanonicalString(key.data(), 0); + const auto variable_id = game::FindVariable(game::SCRIPTINSTANCE_SERVER, this->id_, string_value); + + if (!variable_id) + { + return game::GetNewVariable(game::SCRIPTINSTANCE_SERVER, this->id_, string_value); + } + + return variable_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..d6732ea --- /dev/null +++ b/src/game/scripting/object.hpp @@ -0,0 +1,55 @@ +#pragma once +#include "script_value.hpp" + +namespace scripting +{ + class object_value : public script_value + { + public: + object_value(unsigned int, unsigned int); + void operator=(const script_value&); + private: + unsigned int id_; + unsigned int parent_id_; + }; + + class object final + { + public: + object(); + object(const unsigned int); + + object(std::unordered_map); + + object(const object& other); + object(object&& other) noexcept; + + ~object(); + + object& operator=(const object& other); + object& operator=(object&& other) noexcept; + + std::vector get_keys() const; + unsigned int size() const; + void erase(const std::string&) const; + + script_value get(const std::string&) const; + void set(const std::string&, const script_value&) const; + + unsigned int get_entity_id() const; + unsigned int get_value_id(const std::string&) const; + + entity get_raw() const; + + object_value operator[](const std::string& key) const + { + return {this->id_, this->get_value_id(key)}; + } + + private: + void add() const; + void release() const; + + unsigned int id_{}; + }; +} diff --git a/src/game/scripting/safe_execution.cpp b/src/game/scripting/safe_execution.cpp new file mode 100644 index 0000000..c7805b4 --- /dev/null +++ b/src/game/scripting/safe_execution.cpp @@ -0,0 +1,67 @@ +#include +#include "safe_execution.hpp" + +#pragma warning(push) +#pragma warning(disable: 4611) + +namespace scripting::safe_execution +{ + bool execute_with_seh(const script_function function, const game::scr_entref_t& entref) + { + __try + { + function(entref); + return true; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return false; + } + } + + bool call(const script_function function, const game::scr_entref_t& entref) + { + *game::g_script_error_level += 1; + if (game::_setjmp(&game::g_script_error[*game::g_script_error_level], 0)) + { + *game::g_script_error_level -= 1; + return false; + } + + const auto result = execute_with_seh(function, entref); + *game::g_script_error_level -= 1; + return result; + } + + bool set_entity_field(const game::scr_entref_t& entref, const int offset) + { + *game::g_script_error_level += 1; + if (game::_setjmp(&game::g_script_error[*game::g_script_error_level], 0)) + { + *game::g_script_error_level -= 1; + return false; + } + + game::Scr_SetObjectField(entref.classnum, entref.entnum, offset); + + *game::g_script_error_level -= 1; + return true; + } + + bool get_entity_field(const game::scr_entref_t& entref, const int offset, game::VariableValue* value) + { + *game::g_script_error_level += 1; + if (game::_setjmp(&game::g_script_error[*game::g_script_error_level], 0)) + { + value->type = game::SCRIPT_NONE; + value->u.intValue = 0; + *game::g_script_error_level -= 1; + return false; + } + + *value = game::GetEntityFieldValue(game::SCRIPTINSTANCE_SERVER, entref.classnum, entref.entnum, 0, offset); + + *game::g_script_error_level -= 1; + return true; + } +} diff --git a/src/game/scripting/safe_execution.hpp b/src/game/scripting/safe_execution.hpp new file mode 100644 index 0000000..7091973 --- /dev/null +++ b/src/game/scripting/safe_execution.hpp @@ -0,0 +1,14 @@ +#pragma once +#include "game/game.hpp" + +namespace scripting::safe_execution +{ + using script_function = void(*)(game::scr_entref_t); + + bool execute_with_seh(const script_function function, const game::scr_entref_t& entref); + + bool call(script_function function, const game::scr_entref_t& entref); + + bool set_entity_field(const game::scr_entref_t& entref, int offset); + bool get_entity_field(const game::scr_entref_t& entref, int offset, game::VariableValue* value); +} diff --git a/src/game/scripting/script_value.cpp b/src/game/scripting/script_value.cpp new file mode 100644 index 0000000..bd4ff5c --- /dev/null +++ b/src/game/scripting/script_value.cpp @@ -0,0 +1,386 @@ +#include +#include "script_value.hpp" +#include "entity.hpp" +#include "array.hpp" +#include "function.hpp" +#include "object.hpp" + +namespace scripting +{ + /*********************************************** + * Constructors + **********************************************/ + + script_value::script_value(const game::VariableValue& value) + : value_(value) + { + } + + script_value::script_value(void* value) + { + game::VariableValue variable{}; + variable.type = game::SCRIPT_INTEGER; + variable.u.intValue = reinterpret_cast(value); + + this->value_ = variable; + } + + script_value::script_value(const int value) + { + game::VariableValue variable{}; + variable.type = game::SCRIPT_INTEGER; + variable.u.intValue = value; + + this->value_ = variable; + } + + script_value::script_value(const unsigned int value) + { + game::VariableValue variable{}; + variable.type = game::SCRIPT_INTEGER; + variable.u.uintValue = value; + + this->value_ = variable; + } + + script_value::script_value(const bool value) + : script_value(static_cast(value)) + { + } + + script_value::script_value(const float value) + { + game::VariableValue variable{}; + variable.type = game::SCRIPT_FLOAT; + variable.u.floatValue = value; + + this->value_ = variable; + } + + script_value::script_value(const double value) + : script_value(static_cast(value)) + { + } + + script_value::script_value(const char* value) + { + game::VariableValue variable{}; + variable.type = game::SCRIPT_STRING; + variable.u.stringValue = game::SL_GetString(value, 0, game::SCRIPTINSTANCE_SERVER); + + const auto _ = gsl::finally([&variable]() + { + game::RemoveRefToValue(game::SCRIPTINSTANCE_SERVER, variable.type, variable.u); + }); + + this->value_ = variable; + } + + script_value::script_value(const std::string& value) + : script_value(value.data()) + { + } + + script_value::script_value(const entity& value) + { + game::VariableValue variable{}; + variable.type = game::SCRIPT_OBJECT; + variable.u.pointerValue = value.get_entity_id(); + + this->value_ = variable; + } + + script_value::script_value(const array& value) + { + game::VariableValue variable{}; + variable.type = game::SCRIPT_OBJECT; + variable.u.pointerValue = value.get_entity_id(); + + 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{}; + variable.type = game::SCRIPT_FUNCTION; + variable.u.codePosValue = value.get_pos(); + + this->value_ = variable; + } + + script_value::script_value(const vector& value) + { + game::VariableValue variable{}; + variable.type = game::SCRIPT_VECTOR; + variable.u.vectorValue = game::Scr_AllocVector(game::SCRIPTINSTANCE_SERVER, value); + + const auto _ = gsl::finally([&variable]() + { + game::RemoveRefToValue(game::SCRIPTINSTANCE_SERVER, variable.type, variable.u); + }); + + this->value_ = variable; + } + + /*********************************************** + * Integer + **********************************************/ + + template <> + bool script_value::is() const + { + return this->get_raw().type == game::SCRIPT_INTEGER; + } + + template <> + bool script_value::is() const + { + return this->is(); + } + + template <> + bool script_value::is() const + { + return this->is(); + } + + template <> + int script_value::get() const + { + return this->get_raw().u.intValue; + } + + template <> + unsigned int script_value::get() const + { + return this->get_raw().u.uintValue; + } + + template <> + bool script_value::get() const + { + return this->get_raw().u.uintValue != 0; + } + + /*********************************************** + * Float + **********************************************/ + + template <> + bool script_value::is() const + { + return this->get_raw().type == game::SCRIPT_FLOAT; + } + + template <> + bool script_value::is() const + { + return this->is(); + } + + template <> + float script_value::get() const + { + return this->get_raw().u.floatValue; + } + + template <> + double script_value::get() const + { + return static_cast(this->get_raw().u.floatValue); + } + + /*********************************************** + * String + **********************************************/ + + template <> + bool script_value::is() const + { + return this->get_raw().type == game::SCRIPT_STRING + || this->get_raw().type == game::SCRIPT_ISTRING; + } + + template <> + bool script_value::is() const + { + return this->is(); + } + + template <> + const char* script_value::get() const + { + return game::SL_ConvertToString(static_cast(this->get_raw().u.stringValue), + game::SCRIPTINSTANCE_SERVER); + } + + template <> + std::string script_value::get() const + { + return this->get(); + } + + /*********************************************** + * Entity + **********************************************/ + + template <> + bool script_value::is() const + { + return this->get_raw().type == game::SCRIPT_OBJECT; + } + + template <> + entity script_value::get() const + { + return entity(this->get_raw().u.pointerValue); + } + + /*********************************************** + * 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->variableList[id + 1].w.type & 0x1F; + + return type == game::SCRIPT_ARRAY; + } + + template <> + array script_value::get() const + { + return array(this->get_raw().u.uintValue); + } + + /*********************************************** + * Struct + **********************************************/ + + 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->variableList[id + 1].w.type & 0x1F; + + return type == game::SCRIPT_STRUCT || type == game::SCRIPT_ENTITY; + } + + template <> + object script_value::get() const + { + return object(this->get_raw().u.uintValue); + } + + /*********************************************** + * Function + **********************************************/ + + template <> + bool script_value::is() const + { + return this->get_raw().type == game::SCRIPT_FUNCTION; + } + + template <> + function script_value::get() const + { + return function(this->get_raw().u.codePosValue); + } + + + /*********************************************** + * Vector + **********************************************/ + + template <> + bool script_value::is() const + { + return this->get_raw().type == game::SCRIPT_VECTOR; + } + + template <> + vector script_value::get() const + { + return this->get_raw().u.vectorValue; + } + + /*********************************************** + * + **********************************************/ + + const game::VariableValue& script_value::get_raw() const + { + 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& name = func.get_name(); + return utils::string::va("[[ %s ]]", name.data()); + } + + return this->type_name(); + } + + function_argument::function_argument(const arguments& args, const script_value& value, const int index) + : values_(args) + , value_(value) + , index_(index) + { + } + + function_arguments::function_arguments(const arguments& values) + : values_(values) + { + } +} diff --git a/src/game/scripting/script_value.hpp b/src/game/scripting/script_value.hpp new file mode 100644 index 0000000..d58c7c1 --- /dev/null +++ b/src/game/scripting/script_value.hpp @@ -0,0 +1,262 @@ +#pragma once +#include "game/game.hpp" +#include "variable_value.hpp" +#include "vector.hpp" + +#include + +namespace scripting +{ + class entity; + class array; + class object; + class function; + class script_value; + + namespace + { + std::unordered_map typenames = + { + {0, "undefined"}, + {1, "object"}, + {2, "string"}, + {3, "localized string"}, + {4, "vector"}, + {5, "float"}, + {6, "int"}, + {7, "codepos"}, + {8, "precodepos"}, + {9, "function"}, + {10, "stack"}, + {11, "animation"}, + {12, "developer codepos"}, + {13, "thread"}, + {14, "thread"}, + {15, "thread"}, + {16, "thread"}, + {17, "struct"}, + {18, "removed entity"}, + {19, "entity"}, + {20, "array"}, + {21, "removed thread"}, + }; + + std::string get_typename(const game::VariableValue& value) + { + auto type_ = 0; + if (value.type == game::SCRIPT_OBJECT) + { + type_ = game::scr_VarGlob->variableList[value.u.uintValue].w.type & 0x1F; + } + else + { + type_ = value.type; + } + + if (typenames.find(type_) != typenames.end()) + { + return typenames[type_]; + } + + printf("UNKNOWN TYPE %i\n", type_); + return "unknown"; + } + + template + std::string get_c_typename() + { + auto& info = typeid(T); + + if (info == typeid(std::string)) + { + return "string"; + } + + if (info == typeid(const char*)) + { + return "string"; + } + + if (info == typeid(entity)) + { + return "entity"; + } + + if (info == typeid(array)) + { + return "array"; + } + + if (info == typeid(object)) + { + return "struct"; + } + + if (info == typeid(function)) + { + return "function"; + } + + if (info == typeid(vector)) + { + return "vector"; + } + + return info.name(); + } + } + + using arguments = std::vector; + + class script_value + { + public: + script_value() = default; + script_value(const game::VariableValue& value); + + script_value(void* value); + + script_value(int value); + script_value(unsigned int value); + script_value(bool value); + + script_value(float value); + script_value(double value); + + script_value(const char* value); + script_value(const std::string& value); + + script_value(const entity& value); + script_value(const array& value); + script_value(const object& value); + + script_value(const function& value); + + script_value(const vector& value); + + template + bool is() const; + + template + T as() const + { + if (!this->is()) + { + const auto type = get_typename(this->get_raw()); + const auto c_type = get_c_typename(); + throw std::runtime_error(std::string("has type '" + type + "' but should be '" + c_type + "'")); + } + + return get(); + } + + std::string type_name() const + { + return get_typename(this->get_raw()); + } + + template class C, class T, typename ArrayType = array> + script_value(const C>& container) + { + ArrayType array_{}; + + for (const auto& value : container) + { + array_.push(value); + } + + game::VariableValue value{}; + value.type = game::SCRIPT_OBJECT; + value.u.pointerValue = array_.get_entity_id(); + + this->value_ = value; + } + + template + arguments operator()(T... arguments) const + { + return this->as().call({arguments...}); + } + + std::string to_string() const; + + const game::VariableValue& get_raw() const; + + variable_value value_{}; + + private: + template + T get() const; + + }; + + class variadic_args : public arguments + { + }; + + class function_argument + { + public: + function_argument(const arguments& args, const script_value& value, const int index); + + template + T as() const + { + try + { + return this->value_.as(); + } + catch (const std::exception& e) + { + throw std::runtime_error(utils::string::va("parameter %d %s", + this->index_ + 1, e.what())); + } + } + + template <> + variadic_args as() const + { + variadic_args args{}; + for (auto i = this->index_; i < static_cast(this->values_.size()); i++) + { + args.push_back(this->values_[i]); + } + return args; + } + + template <> + script_value as() const + { + return this->value_; + } + + template + operator T() const + { + return this->as(); + } + + private: + arguments values_{}; + script_value value_{}; + int index_{}; + }; + + class function_arguments + { + public: + function_arguments(const arguments& values); + + function_argument operator[](const int index) const + { + if (index >= static_cast(values_.size())) + { + throw std::runtime_error(utils::string::va("parameter %d does not exist", index)); + } + + return {values_, values_[index], index}; + } + private: + arguments values_{}; + }; +} diff --git a/src/game/scripting/stack_isolation.cpp b/src/game/scripting/stack_isolation.cpp new file mode 100644 index 0000000..ee72c5c --- /dev/null +++ b/src/game/scripting/stack_isolation.cpp @@ -0,0 +1,27 @@ +#include +#include "stack_isolation.hpp" + +namespace scripting +{ + stack_isolation::stack_isolation() + { + this->in_param_count_ = game::scr_VmPub->inparamcount; + this->out_param_count_ = game::scr_VmPub->outparamcount; + this->top_ = game::scr_VmPub->top; + this->max_stack_ = game::scr_VmPub->maxstack; + + game::scr_VmPub->top = this->stack_; + game::scr_VmPub->maxstack = &this->stack_[ARRAYSIZE(this->stack_) - 1]; + game::scr_VmPub->inparamcount = 0; + game::scr_VmPub->outparamcount = 0; + } + + stack_isolation::~stack_isolation() + { + game::Scr_ClearOutParams(game::SCRIPTINSTANCE_SERVER); + game::scr_VmPub->inparamcount = this->in_param_count_; + game::scr_VmPub->outparamcount = this->out_param_count_; + game::scr_VmPub->top = this->top_; + game::scr_VmPub->maxstack = this->max_stack_; + } +} diff --git a/src/game/scripting/stack_isolation.hpp b/src/game/scripting/stack_isolation.hpp new file mode 100644 index 0000000..8dffd4b --- /dev/null +++ b/src/game/scripting/stack_isolation.hpp @@ -0,0 +1,25 @@ +#pragma once +#include "game/game.hpp" + +namespace scripting +{ + class stack_isolation final + { + public: + stack_isolation(); + ~stack_isolation(); + + stack_isolation(stack_isolation&&) = delete; + stack_isolation(const stack_isolation&) = delete; + stack_isolation& operator=(stack_isolation&&) = delete; + stack_isolation& operator=(const stack_isolation&) = delete; + + private: + game::VariableValue stack_[512]{}; + + game::VariableValue* max_stack_; + game::VariableValue* top_; + unsigned int in_param_count_; + unsigned int out_param_count_; + }; +} diff --git a/src/game/scripting/thread.cpp b/src/game/scripting/thread.cpp new file mode 100644 index 0000000..2b1247f --- /dev/null +++ b/src/game/scripting/thread.cpp @@ -0,0 +1,67 @@ +#include +#include "thread.hpp" +#include "execution.hpp" +#include "../../component/scripting.hpp" + +namespace scripting +{ + thread::thread(unsigned int id) + : id_(id) + , type_(game::scr_VarGlob->variableList[id].w.type & 0x7F) + { + } + + script_value thread::get_raw() const + { + game::VariableValue value; + value.type = game::SCRIPT_OBJECT; + value.u.uintValue = this->id_; + + return value; + } + + unsigned int thread::get_entity_id() const + { + return this->id_; + } + + unsigned int thread::get_type() const + { + return this->type_; + } + + unsigned int thread::get_wait_time() const + { + return game::scr_VarGlob->variableList[this->id_].w.waitTime >> 8; + } + + unsigned int thread::get_notify_name_id() const + { + return game::scr_VarGlob->variableList[this->id_].w.notifyName >> 8; + } + + unsigned int thread::get_self() const + { + return game::Scr_GetSelf(game::SCRIPTINSTANCE_SERVER, this->id_); + } + + std::string thread::get_notify_name() const + { + return game::SL_ConvertToString(this->get_notify_name_id(), game::SCRIPTINSTANCE_SERVER); + } + + const char* thread::get_pos() const + { + return 0; + } + + const char* thread::get_start_pos() const + { + return 0; + } + + void thread::kill() const + { + + } +} diff --git a/src/game/scripting/thread.hpp b/src/game/scripting/thread.hpp new file mode 100644 index 0000000..29d8476 --- /dev/null +++ b/src/game/scripting/thread.hpp @@ -0,0 +1,32 @@ +#pragma once +#include "entity.hpp" +#include "script_value.hpp" + +namespace scripting +{ + class thread + { + public: + thread(unsigned int); + + script_value get_raw() const; + + unsigned int get_entity_id() const; + unsigned int get_type() const; + + unsigned int get_self() const; + unsigned int get_wait_time() const; + unsigned int get_notify_name_id() const; + std::string get_notify_name() const; + + const char* get_pos() const; + const char* get_start_pos() const; + + void kill() const; + + private: + unsigned int id_{}; + unsigned int type_{}; + + }; +} diff --git a/src/game/scripting/variable_value.cpp b/src/game/scripting/variable_value.cpp new file mode 100644 index 0000000..cdd5e9b --- /dev/null +++ b/src/game/scripting/variable_value.cpp @@ -0,0 +1,68 @@ +#include +#include "variable_value.hpp" + +namespace scripting +{ + variable_value::variable_value(const game::VariableValue& value) + { + this->assign(value); + } + + variable_value::variable_value(const variable_value& other) noexcept + { + this->operator=(other); + } + + variable_value::variable_value(variable_value&& other) noexcept + { + this->operator=(std::move(other)); + } + + variable_value& variable_value::operator=(const variable_value& other) noexcept + { + if (this != &other) + { + this->release(); + this->assign(other.value_); + } + + return *this; + } + + variable_value& variable_value::operator=(variable_value&& other) noexcept + { + if (this != &other) + { + this->release(); + this->value_ = other.value_; + other.value_.type = game::SCRIPT_NONE; + } + + return *this; + } + + variable_value::~variable_value() + { + this->release(); + } + + const game::VariableValue& variable_value::get() const + { + return this->value_; + } + + void variable_value::assign(const game::VariableValue& value) + { + this->value_ = value; + game::AddRefToValue(game::SCRIPTINSTANCE_SERVER, &this->value_); + } + + void variable_value::release() + { + if (this->value_.type != game::SCRIPT_NONE) + { + game::RemoveRefToValue(game::SCRIPTINSTANCE_SERVER, this->value_.type, this->value_.u); + this->value_.type = game::SCRIPT_NONE; + } + } +} diff --git a/src/game/scripting/variable_value.hpp b/src/game/scripting/variable_value.hpp new file mode 100644 index 0000000..7a96261 --- /dev/null +++ b/src/game/scripting/variable_value.hpp @@ -0,0 +1,27 @@ +#pragma once +#include "game/game.hpp" + +namespace scripting +{ + class variable_value + { + public: + variable_value() = default; + variable_value(const game::VariableValue& value); + variable_value(const variable_value& other) noexcept; + variable_value(variable_value&& other) noexcept; + + variable_value& operator=(const variable_value& other) noexcept; + variable_value& operator=(variable_value&& other) noexcept; + + ~variable_value(); + + const game::VariableValue& get() const; + + private: + void assign(const game::VariableValue& value); + void release(); + + game::VariableValue value_{{0}, game::SCRIPT_NONE}; + }; +} diff --git a/src/game/scripting/vector.cpp b/src/game/scripting/vector.cpp new file mode 100644 index 0000000..143e29a --- /dev/null +++ b/src/game/scripting/vector.cpp @@ -0,0 +1,85 @@ +#include +#include "vector.hpp" + +namespace scripting +{ + vector::vector(const float* value) + { + for (auto i = 0; i < 3; ++i) + { + this->value_[i] = value[i]; + } + } + + vector::vector(const game::vec3_t& value) + : vector(&value[0]) + { + } + + vector::vector(const float x, const float y, const float z) + { + this->value_[0] = x; + this->value_[1] = y; + this->value_[2] = z; + } + + vector::operator game::vec3_t& () + { + return this->value_; + } + + vector::operator const game::vec3_t& () const + { + return this->value_; + } + + game::vec_t& vector::operator[](const size_t i) + { + if (i >= 3) + { + throw std::runtime_error("Out of bounds."); + } + + return this->value_[i]; + } + + const game::vec_t& vector::operator[](const size_t i) const + { + if (i >= 3) + { + throw std::runtime_error("Out of bounds."); + } + + return this->value_[i]; + } + + float vector::get_x() const + { + return this->operator[](0); + } + + float vector::get_y() const + { + return this->operator[](1); + } + + float vector::get_z() const + { + return this->operator[](2); + } + + void vector::set_x(const float value) + { + this->operator[](0) = value; + } + + void vector::set_y(const float value) + { + this->operator[](1) = value; + } + + void vector::set_z(const float value) + { + this->operator[](2) = value; + } +} diff --git a/src/game/scripting/vector.hpp b/src/game/scripting/vector.hpp new file mode 100644 index 0000000..0bb1595 --- /dev/null +++ b/src/game/scripting/vector.hpp @@ -0,0 +1,31 @@ +#pragma once +#include "game/game.hpp" + +namespace scripting +{ + class vector final + { + public: + vector() = default; + vector(const float* value); + vector(const game::vec3_t& value); + vector(float x, float y, float z); + + operator game::vec3_t& (); + operator const game::vec3_t& () const; + + game::vec_t& operator[](size_t i); + const game::vec_t& operator[](size_t i) const; + + float get_x() const; + float get_y() const; + float get_z() const; + + void set_x(float value); + void set_y(float value); + void set_z(float value); + + private: + game::vec3_t value_{ 0 }; + }; +} \ No newline at end of file diff --git a/src/game/structs.hpp b/src/game/structs.hpp new file mode 100644 index 0000000..462fbe6 --- /dev/null +++ b/src/game/structs.hpp @@ -0,0 +1,742 @@ +#pragma once + +namespace game +{ + typedef float vec_t; + typedef vec_t vec2_t[2]; + typedef vec_t vec3_t[3]; + typedef vec_t vec4_t[4]; + + enum scriptInstance_t + { + SCRIPTINSTANCE_SERVER, + SCRIPTINSTANCE_CLIENT, + }; + + struct scr_entref_t + { + unsigned short entnum; + unsigned short classnum; + int client; + }; + + struct BuiltinMethodDef + { + const char* actionString; + unsigned int constId; + int min_args; + int max_args; + void(__cdecl* actionFunc)(scr_entref_t); + int type; + }; + + struct BuiltinFunctionDef + { + const char* actionString; + unsigned int constId; + int min_args; + int max_args; + void(__cdecl* actionFunc)(); + int type; + }; + + enum scriptType_e + { + SCRIPT_NONE = 0, + SCRIPT_OBJECT = 1, + SCRIPT_STRING = 2, + SCRIPT_ISTRING = 3, + SCRIPT_VECTOR = 4, + SCRIPT_FLOAT = 5, + SCRIPT_INTEGER = 6, + SCRIPT_CODEPOS = 7, + SCRIPT_END = 8, + SCRIPT_FUNCTION = 9, + SCRIPT_THREAD = 13, + SCRIPT_NOTIFY_THREAD = 14, + SCRIPT_TIME_THREAD = 15, + SCRIPT_STRUCT = 17, + SCRIPT_ENTITY = 19, + SCRIPT_ARRAY = 20, + SCRIPT_FREE = 0x17, + }; + + struct VariableStackBuffer + { + const char* pos; + unsigned __int16 size; + unsigned __int16 bufLen; + unsigned __int16 localId; + char time; + char buf[1]; + }; + + union VariableUnion + { + int intValue; + unsigned int uintValue; + float floatValue; + unsigned int stringValue; + const float* vectorValue; + const char* codePosValue; + unsigned int pointerValue; + VariableStackBuffer* stackValue; + unsigned int entityOffset; + }; + + struct VariableValue + { + VariableUnion u; + int type; + }; + + struct function_stack_t + { + char* pos; + VariableValue* top; + unsigned int localId; + unsigned int localVarCount; + VariableValue* startTop; + }; + + struct function_frame_t + { + function_stack_t fs; + }; + + struct scrVmPub_t + { + unsigned int* localVars; + VariableValue* maxstack; + int function_count; + function_frame_t* function_frame; + VariableValue* top; + bool abort_on_error; + bool terminal_error; + bool block_execution; + unsigned int inparamcount; + unsigned int outparamcount; + function_frame_t function_frame_start[32]; + VariableValue stack[2048]; + void(__cdecl* notifyListeners[1])(unsigned int, unsigned int); + }; + + static_assert(offsetof(scrVmPub_t, top) == 16); + static_assert(offsetof(scrVmPub_t, inparamcount) == 24); + + struct scrVarPub_t + { + const char* fieldBuffer; + unsigned __int16 canonicalStrCount; + bool developer; + bool developer_script; + bool evaluate; + const char* error_message; + int error_index; + unsigned int time; + unsigned int timeArrayId; + unsigned int pauseArrayId; + unsigned int levelId; + unsigned int gameId; + unsigned int animId; + unsigned int freeEntList; + unsigned int tempVariable; + bool bInited; + unsigned __int16 savecount; + unsigned int checksum; + unsigned int entId; + unsigned int entFieldName; + void* programHunkUser; + const char* programBuffer; + const char* endScriptBuffer; + unsigned __int16* saveIdMap; + unsigned __int16* saveIdMapRev; + unsigned int numScriptThreads; + unsigned int numScriptValues; + unsigned int numScriptObjects; + const char* varUsagePos; + int ext_threadcount; + int totalObjectRefCount; + volatile int totalVectorRefCount; + }; + + union Variable_u + { + unsigned int prev; + unsigned int prevSibling; + }; + + struct Variable + { + unsigned int id; + Variable_u u; + }; + + union ObjectInfo_u + { + unsigned __int16 entnum; + unsigned __int16 size; + unsigned int nextEntId; + unsigned int self; + }; + + struct ObjectInfo + { + unsigned __int16 refCount; + ObjectInfo_u u; + }; + + union VariableValueInternal_u + { + unsigned int next; + VariableUnion u; + ObjectInfo o; + }; + + union VariableValueInternal_w + { + unsigned int status; + unsigned int type; + unsigned int name; + unsigned int classnum; + unsigned int notifyName; + unsigned int waitTime; + unsigned int parentLocalId; + }; + + union VariableValueInternal_v + { + unsigned int next; + unsigned int index; + }; + + struct VariableValueInternal + { + Variable hash; + VariableValueInternal_u u; + VariableValueInternal_w w; + VariableValueInternal_v v; + unsigned int nextSibling; + }; + + static_assert(sizeof(VariableValueInternal) == 28); + static_assert(offsetof(VariableValueInternal, hash) == 0); + static_assert(offsetof(VariableValueInternal, u) == 8); + static_assert(offsetof(VariableValueInternal, w) == 16); + + struct scrVarGlob_t + { + VariableValueInternal* variableList; + }; + + struct scr_classStruct_t + { + unsigned __int16 id; + unsigned __int16 entArrayId; + char charId; + const char* name; + }; + + enum gclientFlag + { + NOCLIP = 1 << 0, + UFO = 1 << 1, + }; + + enum entityFlag + { + FL_GODMODE = 1 << 0, + FL_DEMI_GODMODE = 1 << 1, + FL_NOTARGET = 1 << 2, + FL_SUPPORTS_LINKTO = 1 << 12, + }; // TODO: Finish + + struct gclient_s + { + char __pad0[0x18]; + int eflags; + char __pad1[0x5668]; + int flags; + }; + + struct gentity_s + { + int number; + char __pad0[0x150]; + gclient_s* client; // 340 + char __pad1[0x30]; + int flags; // 392 + char __pad2[0x190]; + }; + + static_assert(sizeof(gentity_s) == 0x31C); + + enum clientState_t + { + CS_FREE, + CS_ZOMBIE, + CS_RECONNECTING, + CS_CONNECTED, + CS_CLIENTLOADING, + CS_ACTIVE, + }; + + enum netsrc_t + { + NS_CLIENT1 = 0x0, + NS_CLIENT2 = 0x1, + NS_CLIENT3 = 0x2, + NS_CLIENT4 = 0x3, + NS_SERVER = 0x4, + NS_PACKET = 0x5, + NS_NULL = -1, + }; + + enum netadrtype_t + { + NA_BOT = 0x0, + NA_BAD = 0x1, + NA_LOOPBACK = 0x2, + NA_BROADCAST = 0x3, + NA_IP = 0x4, + }; + + struct netadr_t + { + union + { + unsigned char ip[4]; + unsigned int inaddr; + }; + unsigned __int16 port; + netadrtype_t type; + netsrc_t localNetID; + unsigned __int16 serverID; + }; + + static_assert(sizeof(netadr_t) == 0x14); + + struct netProfileInfo_t + { + unsigned char __pad0[0x5E0]; + }; + + struct netchan_t + { + int outgoingSequence; + netsrc_t sock; + int dropped; + int incomingSequence; + netadr_t remoteAddress; + int qport; + int fragmentSequence; + int fragmentLength; + unsigned char* fragmentBuffer; + int fragmentBufferSize; + int unsentFragments; + int unsentOnLoan; + int unsentFragmentStart; + int unsentLength; + unsigned char* unsentBuffer; + int unsentBufferSize; + int reliable_fragments; + unsigned char fragment_send_count[128]; + unsigned int fragment_ack[4]; + int lowest_send_count; + netProfileInfo_t prof; + }; + + static_assert(sizeof(netchan_t) == 0x6C8); + + struct PredictedVehicleDef + { + bool fullPhysics; + vec3_t origin; + vec3_t angles; + vec3_t tVel; + vec3_t aVel; + int serverTime; + }; + + static_assert(sizeof(PredictedVehicleDef) == 0x38); + + struct clientHeader_t + { + clientState_t state; + int sendAsActive; + int deltaMessage; + int rateDelayed; + int hasAckedBaselineData; + int hugeSnapshotSent; + netchan_t netchan; + vec3_t predictedOrigin; + int predictedOriginServerTime; + int migrationState; + PredictedVehicleDef predictedVehicle; + }; + + static_assert(sizeof(clientHeader_t) == 0x72C); + + struct client_s + { + clientHeader_t header; + const char* dropReason; + char userinfo[1024]; + unsigned char __pad0[0x3F75C]; + int bIsTestClient; + unsigned char __pad1[0xDEF0]; + }; + + static_assert(sizeof(client_s) == 0x4E180); + + struct cmd_function_t + { + cmd_function_t* next; + const char* name; + const char* autoCompleteDir; + const char* autoCompleteExt; + void(__cdecl* function)(); + int flags; + }; + + struct CmdArgs + { + int nesting; + int localClientNum[8]; + int controllerIndex[8]; + void* itemDef[8]; + int argshift[8]; + int argc[8]; + const char** argv[8]; + char textPool[8192]; + const char* argvPool[512]; + int usedTextPool[8]; + int totalUsedArgvPool; + int totalUsedTextPool; + }; + + enum dvarType_t + { + DVAR_TYPE_INVALID = 0x0, + DVAR_TYPE_BOOL = 0x1, + DVAR_TYPE_FLOAT = 0x2, + DVAR_TYPE_FLOAT_2 = 0x3, + DVAR_TYPE_FLOAT_3 = 0x4, + DVAR_TYPE_FLOAT_4 = 0x5, + DVAR_TYPE_INT = 0x6, + DVAR_TYPE_ENUM = 0x7, + DVAR_TYPE_STRING = 0x8, + DVAR_TYPE_COLOR = 0x9, + DVAR_TYPE_INT64 = 0xA, + DVAR_TYPE_LINEAR_COLOR_RGB = 0xB, + DVAR_TYPE_COLOR_XYZ = 0xC, + DVAR_TYPE_COUNT = 0xD, + }; + + union DvarValue + { + bool enabled; + int integer; + unsigned int unsignedInt; + __int64 integer64; + unsigned __int64 unsignedInt64; + float value; + vec4_t vector; + const char* string; + char color[4]; + }; + + struct $A37BA207B3DDD6345C554D4661813EDD + { + int stringCount; + const char* const* strings; + }; + + struct $9CA192F9DB66A3CB7E01DE78A0DEA53D + { + int min; + int max; + }; + + struct $251C2428A496074035CACA7AAF3D55BD + { + float min; + float max; + }; + + union DvarLimits + { + $A37BA207B3DDD6345C554D4661813EDD enumeration; + $9CA192F9DB66A3CB7E01DE78A0DEA53D integer; + $251C2428A496074035CACA7AAF3D55BD value; + $251C2428A496074035CACA7AAF3D55BD vector; + }; + + struct dvar_t + { + const char* name; + const char* description; + int hash; + unsigned int flags; + dvarType_t type; + bool modified; + DvarValue current; + DvarValue latched; + DvarValue reset; + DvarLimits domain; + dvar_t* hashNext; + }; + + struct GSC_OBJ + { + char magic[8]; + unsigned int source_crc; + unsigned int include_offset; + unsigned int animtree_offset; + unsigned int cseg_offset; + unsigned int stringtablefixup_offset; + unsigned int exports_offset; + unsigned int imports_offset; + unsigned int fixup_offset; + unsigned int profile_offset; + unsigned int cseg_size; + unsigned __int16 name; + unsigned __int16 stringtablefixup_count; + unsigned __int16 exports_count; + unsigned __int16 imports_count; + unsigned __int16 fixup_count; + unsigned __int16 profile_count; + char include_count; + char animtree_count; + char flags; + }; + + struct GSC_EXPORT_ITEM + { + unsigned int checksum; + unsigned int address; + unsigned __int16 name; + char param_count; + char flags; + }; + + // gsc-tool + enum class opcode : std::uint8_t + { + OP_End = 0x0, + OP_Return = 0x1, + OP_GetUndefined = 0x2, + OP_GetZero = 0x3, + OP_GetByte = 0x4, + OP_GetNegByte = 0x5, + OP_GetUnsignedShort = 0x6, + OP_GetNegUnsignedShort = 0x7, + OP_GetInteger = 0x8, + OP_GetFloat = 0x9, + OP_GetString = 0xA, + OP_GetIString = 0xB, + OP_GetVector = 0xC, + OP_GetLevelObject = 0xD, + OP_GetAnimObject = 0xE, + OP_GetSelf = 0xF, + OP_GetLevel = 0x10, + OP_GetGame = 0x11, + OP_GetAnim = 0x12, + OP_GetAnimation = 0x13, + OP_GetGameRef = 0x14, + OP_GetFunction = 0x15, + OP_CreateLocalVariable = 0x16, + OP_SafeCreateLocalVariables = 0x17, + OP_RemoveLocalVariables = 0x18, + OP_EvalLocalVariableCached = 0x19, + OP_EvalArray = 0x1A, + OP_EvalLocalArrayRefCached = 0x1B, + OP_EvalArrayRef = 0x1C, + OP_ClearArray = 0x1D, + OP_EmptyArray = 0x1E, + OP_GetSelfObject = 0x1F, + OP_EvalFieldVariable = 0x20, + OP_EvalFieldVariableRef = 0x21, + OP_ClearFieldVariable = 0x22, + OP_SafeSetVariableFieldCached = 0x23, + OP_SafeSetWaittillVariableFieldCached = 0x24, + OP_ClearParams = 0x25, + OP_CheckClearParams = 0x26, + OP_EvalLocalVariableRefCached = 0x27, + OP_SetVariableField = 0x28, + OP_CallBuiltin = 0x29, + OP_CallBuiltinMethod = 0x2A, + OP_Wait = 0x2B, + OP_WaitTillFrameEnd = 0x2C, + OP_PreScriptCall = 0x2D, + OP_ScriptFunctionCall = 0x2E, + OP_ScriptFunctionCallPointer = 0x2F, + OP_ScriptMethodCall = 0x30, + OP_ScriptMethodCallPointer = 0x31, + OP_ScriptThreadCall = 0x32, + OP_ScriptThreadCallPointer = 0x33, + OP_ScriptMethodThreadCall = 0x34, + OP_ScriptMethodThreadCallPointer = 0x35, + OP_DecTop = 0x36, + OP_CastFieldObject = 0x37, + OP_CastBool = 0x38, + OP_BoolNot = 0x39, + OP_BoolComplement = 0x3A, + OP_JumpOnFalse = 0x3B, + OP_JumpOnTrue = 0x3C, + OP_JumpOnFalseExpr = 0x3D, + OP_JumpOnTrueExpr = 0x3E, + OP_Jump = 0x3F, + OP_JumpBack = 0x40, + OP_Inc = 0x41, + OP_Dec = 0x42, + OP_Bit_Or = 0x43, + OP_Bit_Xor = 0x44, + OP_Bit_And = 0x45, + OP_Equal = 0x46, + OP_NotEqual = 0x47, + OP_LessThan = 0x48, + OP_GreaterThan = 0x49, + OP_LessThanOrEqualTo = 0x4A, + OP_GreaterThanOrEqualTo = 0x4B, + OP_ShiftLeft = 0x4C, + OP_ShiftRight = 0x4D, + OP_Plus = 0x4E, + OP_Minus = 0x4F, + OP_Multiply = 0x50, + OP_Divide = 0x51, + OP_Modulus = 0x52, + OP_SizeOf = 0x53, + OP_WaitTillMatch = 0x54, + OP_WaitTill = 0x55, + OP_Notify = 0x56, + OP_EndOn = 0x57, + OP_VoidCodePos = 0x58, + OP_Switch = 0x59, + OP_EndSwitch = 0x5A, + OP_Vector = 0x5B, + OP_GetHash = 0x5C, + OP_RealWait = 0x5D, + OP_VectorConstant = 0x5E, + OP_IsDefined = 0x5F, + OP_VectorScale = 0x60, + OP_AnglesToUp = 0x61, + OP_AnglesToRight = 0x62, + OP_AnglesToForward = 0x63, + OP_AngleClamp180 = 0x64, + OP_VectorToAngles = 0x65, + OP_Abs = 0x66, + OP_GetTime = 0x67, + OP_GetDvar = 0x68, + OP_GetDvarInt = 0x69, + OP_GetDvarFloat = 0x6A, + OP_GetDvarVector = 0x6B, + OP_GetDvarColorRed = 0x6C, + OP_GetDvarColorGreen = 0x6D, + OP_GetDvarColorBlue = 0x6E, + OP_GetDvarColorAlpha = 0x6F, + OP_FirstArrayKey = 0x70, + OP_NextArrayKey = 0x71, + OP_ProfileStart = 0x72, + OP_ProfileStop = 0x73, + OP_SafeDecTop = 0x74, + OP_Nop = 0x75, + OP_Abort = 0x76, + OP_Object = 0x77, + OP_ThreadObject = 0x78, + OP_EvalLocalVariable = 0x79, + OP_EvalLocalVariableRef = 0x7A, + OP_DevblockBegin = 0x7B, + OP_DevblockEnd = 0x7C, + OP_Breakpoint = 0x7D, + OP_Count = 0x7E, + }; + + enum XAssetType + { + ASSET_TYPE_XMODELPIECES = 0x0, + ASSET_TYPE_PHYSPRESET = 0x1, + ASSET_TYPE_PHYSCONSTRAINTS = 0x2, + ASSET_TYPE_DESTRUCTIBLEDEF = 0x3, + ASSET_TYPE_XANIMPARTS = 0x4, + ASSET_TYPE_XMODEL = 0x5, + ASSET_TYPE_MATERIAL = 0x6, + ASSET_TYPE_TECHNIQUE_SET = 0x7, + ASSET_TYPE_IMAGE = 0x8, + ASSET_TYPE_SOUND = 0x9, + ASSET_TYPE_SOUND_PATCH = 0xA, + ASSET_TYPE_CLIPMAP = 0xB, + ASSET_TYPE_CLIPMAP_PVS = 0xC, + ASSET_TYPE_COMWORLD = 0xD, + ASSET_TYPE_GAMEWORLD_SP = 0xE, + ASSET_TYPE_GAMEWORLD_MP = 0xF, + ASSET_TYPE_MAP_ENTS = 0x10, + ASSET_TYPE_GFXWORLD = 0x11, + ASSET_TYPE_LIGHT_DEF = 0x12, + ASSET_TYPE_UI_MAP = 0x13, + ASSET_TYPE_FONT = 0x14, + ASSET_TYPE_FONTICON = 0x15, + ASSET_TYPE_MENULIST = 0x16, + ASSET_TYPE_MENU = 0x17, + ASSET_TYPE_LOCALIZE_ENTRY = 0x18, + ASSET_TYPE_WEAPON = 0x19, + ASSET_TYPE_WEAPONDEF = 0x1A, + ASSET_TYPE_WEAPON_VARIANT = 0x1B, + ASSET_TYPE_WEAPON_FULL = 0x1C, + ASSET_TYPE_ATTACHMENT = 0x1D, + ASSET_TYPE_ATTACHMENT_UNIQUE = 0x1E, + ASSET_TYPE_WEAPON_CAMO = 0x1F, + ASSET_TYPE_SNDDRIVER_GLOBALS = 0x20, + ASSET_TYPE_FX = 0x21, + ASSET_TYPE_IMPACT_FX = 0x22, + ASSET_TYPE_AITYPE = 0x23, + ASSET_TYPE_MPTYPE = 0x24, + ASSET_TYPE_MPBODY = 0x25, + ASSET_TYPE_MPHEAD = 0x26, + ASSET_TYPE_CHARACTER = 0x27, + ASSET_TYPE_XMODELALIAS = 0x28, + ASSET_TYPE_RAWFILE = 0x29, + ASSET_TYPE_STRINGTABLE = 0x2A, + ASSET_TYPE_LEADERBOARD = 0x2B, + ASSET_TYPE_XGLOBALS = 0x2C, + ASSET_TYPE_DDL = 0x2D, + ASSET_TYPE_GLASSES = 0x2E, + ASSET_TYPE_EMBLEMSET = 0x2F, + ASSET_TYPE_SCRIPTPARSETREE = 0x30, + ASSET_TYPE_KEYVALUEPAIRS = 0x31, + ASSET_TYPE_VEHICLEDEF = 0x32, + ASSET_TYPE_MEMORYBLOCK = 0x33, + ASSET_TYPE_ADDON_MAP_ENTS = 0x34, + ASSET_TYPE_TRACER = 0x35, + ASSET_TYPE_SKINNEDVERTS = 0x36, + ASSET_TYPE_QDB = 0x37, + ASSET_TYPE_SLUG = 0x38, + ASSET_TYPE_FOOTSTEP_TABLE = 0x39, + ASSET_TYPE_FOOTSTEPFX_TABLE = 0x3A, + ASSET_TYPE_ZBARRIER = 0x3B, + ASSET_TYPE_COUNT = 0x3C, + ASSET_TYPE_STRING = 0x3C, + ASSET_TYPE_ASSETLIST = 0x3D, + ASSET_TYPE_REPORT = 0x3E, + ASSET_TYPE_DEPEND = 0x3F, + ASSET_TYPE_FULL_COUNT = 0x40, + }; + + struct ScriptParseTree + { + const char* name; + int len; + GSC_OBJ* obj; + }; + + struct objFileInfo_t + { + GSC_OBJ* activeVersion; + char __pad[0x24]; + }; + + union XAssetHeader + { + ScriptParseTree* scriptParseTree; + }; +} \ No newline at end of file diff --git a/src/game/symbols.hpp b/src/game/symbols.hpp new file mode 100644 index 0000000..6d89aea --- /dev/null +++ b/src/game/symbols.hpp @@ -0,0 +1,126 @@ +#pragma once + +#define WEAK __declspec(selectany) + +namespace game +{ + // Functions + + WEAK symbol BG_StringHashValue{0x0, 0x0}; + + WEAK symbol Cbuf_InsertText{0x0, 0x0}; + WEAK symbol Cbuf_AddText{0x0, 0x0}; + WEAK symbol Cmd_ExecuteSingleCommand{0x0, 0x0}; + WEAK symbol Cmd_AddCommandInternal{0x0, 0x0}; + WEAK symbol Cmd_Argv{0x0, 0x0}; + WEAK symbol Cmd_RemoveCommand{0x0, 0x0}; + + WEAK symbol ClientUserInfoChanged{0x0, 0x0}; + + WEAK symbol Dvar_FindVar{0x0, 0x0}; + WEAK symbol Dvar_GetInt{0x0, 0x0}; + WEAK symbol Dvar_RegisterInt{0x0, 0x0}; + + WEAK symbol DB_FindXAssetHeader{0x0, 0x0}; + + WEAK symbol I_CleanStr{0x0, 0x0}; + + WEAK symbol Player_GetMethod{0x0, 0x0}; + WEAK symbol Scr_GetCommonFunction{0x0, 0x0}; + WEAK symbol Scr_GetMethod{0x0, 0x0}; + + WEAK symbol Scr_AddEntity{0x0, 0x0}; + WEAK symbol Scr_AddFloat{0x0, 0x0}; + WEAK symbol Scr_AddInt{0x0, 0x0}; + WEAK symbol Scr_AddString{0x0, 0x0}; + WEAK symbol Scr_AddVector{0x0, 0x0}; + WEAK symbol Scr_AddObject{0x0, 0x0}; + + WEAK symbol Scr_ClearOutParams{0x0, 0x588680}; + + WEAK symbol AllocObject{0x0, 0x0}; + WEAK symbol AllocThread{0x0, 0x0}; + WEAK symbol RemoveRefToObject{0x0, 0x0}; + WEAK symbol RemoveRefToVector{0x0, 0x0}; + WEAK symbol AddRefToValue_{0x0, 0x6706B0}; + WEAK symbol RemoveRefToValue{0x0, 0x4249C0}; + WEAK symbol RemoveVariableValue{0x0, 0x0}; + + WEAK symbol FindVariable{0x0, 0x5DF7E0}; + WEAK symbol FindArrayVariable{0x0, 0x509230}; + WEAK symbol GetVariable{0x0, 0x0}; + WEAK symbol GetNewVariable{0x0, 0x5B3750}; + WEAK symbol GetNewArrayVariable{0x0, 0x468BC0}; + WEAK symbol GetVariableValueAddress{0x0, 0x651390}; + WEAK symbol SetNewVariableValue{0x0, 0x4DCA30}; + + WEAK symbol Scr_SetObjectField{0x0, 0x0}; + WEAK symbol GetEntityFieldValue{0x0, 0x0}; + + WEAK symbol Scr_Notify{0x0, 0x0}; + WEAK symbol Scr_NotifyId{0x0, 0x0}; + WEAK symbol Scr_NotifyNum{0x0, 0x0}; + + WEAK symbol SL_GetString{0x0, 0x4B1770}; + WEAK symbol SL_ConvertToString{0x0, 0x624C70}; + WEAK symbol SL_GetCanonicalString{0x0, 0x0}; + + WEAK symbol Scr_GetNumParam{0x0, 0x0}; + WEAK symbol Scr_GetEntity{0x0, 0x0}; + WEAK symbol Scr_GetFloat{0x0, 0x0}; + WEAK symbol Scr_GetInt{0x0, 0x0}; + WEAK symbol Scr_GetString{0x0, 0x0}; + WEAK symbol Scr_GetVector{0x0, 0x0}; + WEAK symbol Scr_AllocVector{0x0, 0x4C4440}; + WEAK symbol Scr_GetFunctionHandle{0x0, 0x0}; + WEAK symbol Scr_AddClassField{0x0, 0x0}; + WEAK symbol Scr_GetEntityId{0x0, 0x6A07D0}; + WEAK symbol GetStartLocalId{0x0, 0x0}; + WEAK symbol Scr_TerminateRunningThread{0x0, 0x0}; + WEAK symbol Scr_Error{0x0, 0x6245E0}; + WEAK symbol Scr_ParamError{0x0, 0x0}; + WEAK symbol Scr_ObjectError{0x0, 0x0}; + WEAK symbol Scr_GetEntityIdRef{0x0, 0x6A53C0}; + WEAK symbol Scr_AllocArray{0x0, 0x5B8400}; + + WEAK symbol GetPlayerEntity{0x0, 0x0}; + + WEAK symbol VM_Execute{0x0, 0x0}; + + WEAK symbol SV_GameDropClient{0x0, 0x0}; + WEAK symbol SV_IsTestClient{0x0, 0x0}; + WEAK symbol SV_GameSendServerCommand{0x0, 0x0}; + + WEAK symbol Sys_GetValue{0x0, 0x0}; + WEAK symbol Sys_Milliseconds{0x0, 0x0}; + + WEAK symbol longjmp{0x0, 0x9D05C4}; + WEAK symbol _setjmp{0x0, 0x9CED5C}; + + // Variables + + WEAK symbol g_script_error_level{0x0, 0x3DD4A18}; + WEAK symbol g_script_error{0x0, 0x3DD3998}; + + WEAK symbol scr_VmPub{0x0, 0x3DCB338}; + WEAK symbol scr_VarGlob{0x0, 0x3DCB180}; + WEAK symbol scr_VarPub{0x0, 0x3DCB280}; + WEAK symbol scr_starttime{0x0, 0x0}; + WEAK symbol fs{0x0, 0x0}; + + WEAK symbol sv_configstrings{0x0, 0x0}; + + WEAK symbol g_classMap{0x0, 0x0}; + + WEAK symbol g_entities{0x0, 0x0}; + WEAK symbol levelEntityId{0x0, 0x0}; + + WEAK symbol svs_clients{0x0, 0x0}; + + namespace plutonium + { + WEAK symbol printf{0x0, 0x0}; + } +} diff --git a/src/loader/component_interface.hpp b/src/loader/component_interface.hpp new file mode 100644 index 0000000..e1ee433 --- /dev/null +++ b/src/loader/component_interface.hpp @@ -0,0 +1,35 @@ +#pragma once + +class component_interface +{ +public: + virtual ~component_interface() + { + } + + virtual void post_start() + { + } + + virtual void post_load() + { + } + + virtual void pre_destroy() + { + } + + virtual void post_unpack() + { + } + + virtual void* load_import([[maybe_unused]] const std::string& library, [[maybe_unused]] const std::string& function) + { + return nullptr; + } + + virtual bool is_supported() + { + return true; + } +}; diff --git a/src/loader/component_loader.cpp b/src/loader/component_loader.cpp new file mode 100644 index 0000000..d6a0090 --- /dev/null +++ b/src/loader/component_loader.cpp @@ -0,0 +1,127 @@ +#include +#include "component_loader.hpp" + +void component_loader::register_component(std::unique_ptr&& component_) +{ + get_components().push_back(std::move(component_)); +} + +bool component_loader::post_start() +{ + static auto handled = false; + if (handled) return true; + handled = true; + + try + { + for (const auto& component_ : get_components()) + { + component_->post_start(); + } + } + catch (premature_shutdown_trigger&) + { + return false; + } + + return true; +} + +bool component_loader::post_load() +{ + static auto handled = false; + if (handled) return true; + handled = true; + + clean(); + + try + { + for (const auto& component_ : get_components()) + { + component_->post_load(); + } + } + catch (premature_shutdown_trigger&) + { + return false; + } + + return true; +} + +void component_loader::post_unpack() +{ + static auto handled = false; + if (handled) return; + handled = true; + + for (const auto& component_ : get_components()) + { + component_->post_unpack(); + } +} + +void component_loader::pre_destroy() +{ + static auto handled = false; + if (handled) return; + handled = true; + + for (const auto& component_ : get_components()) + { + component_->pre_destroy(); + } +} + +void component_loader::clean() +{ + auto& components = get_components(); + for (auto i = components.begin(); i != components.end();) + { + if (!(*i)->is_supported()) + { + (*i)->pre_destroy(); + i = components.erase(i); + } + else + { + ++i; + } + } +} + +void* component_loader::load_import(const std::string& library, const std::string& function) +{ + void* function_ptr = nullptr; + + for (const auto& component_ : get_components()) + { + auto* const component_function_ptr = component_->load_import(library, function); + if (component_function_ptr) + { + function_ptr = component_function_ptr; + } + } + + return function_ptr; +} + +void component_loader::trigger_premature_shutdown() +{ + throw premature_shutdown_trigger(); +} + +std::vector>& component_loader::get_components() +{ + using component_vector = std::vector>; + using component_vector_container = std::unique_ptr>; + + static component_vector_container components(new component_vector, [](component_vector* component_vector) + { + pre_destroy(); + delete component_vector; + }); + + return *components; +} diff --git a/src/loader/component_loader.hpp b/src/loader/component_loader.hpp new file mode 100644 index 0000000..1f6b4d1 --- /dev/null +++ b/src/loader/component_loader.hpp @@ -0,0 +1,61 @@ +#pragma once +#include "component_interface.hpp" + +class component_loader final +{ +public: + class premature_shutdown_trigger final : public std::exception + { + [[nodiscard]] const char* what() const noexcept override + { + return "Premature shutdown requested"; + } + }; + + template + class installer final + { + static_assert(std::is_base_of::value, "component has invalid base class"); + + public: + installer() + { + register_component(std::make_unique()); + } + }; + + template + static T* get() + { + for (const auto& component_ : get_components()) + { + if (typeid(*component_.get()) == typeid(T)) + { + return reinterpret_cast(component_.get()); + } + } + + return nullptr; + } + + static void register_component(std::unique_ptr&& component); + + static bool post_start(); + static bool post_load(); + static void post_unpack(); + static void pre_destroy(); + static void clean(); + + static void* load_import(const std::string& library, const std::string& function); + + static void trigger_premature_shutdown(); + +private: + static std::vector>& get_components(); +}; + +#define REGISTER_COMPONENT(name) \ +namespace \ +{ \ + static component_loader::installer __component; \ +} diff --git a/src/stdinc.cpp b/src/stdinc.cpp new file mode 100644 index 0000000..25163e4 --- /dev/null +++ b/src/stdinc.cpp @@ -0,0 +1 @@ +#include \ No newline at end of file diff --git a/src/stdinc.hpp b/src/stdinc.hpp new file mode 100644 index 0000000..3e4464f --- /dev/null +++ b/src/stdinc.hpp @@ -0,0 +1,55 @@ +#pragma once + +#pragma warning(disable: 6011) +#pragma warning(disable: 6054) +#pragma warning(disable: 6387) +#pragma warning(disable: 26451) +#pragma warning(disable: 26812) +#pragma warning(disable: 28182) + +#define DLL_EXPORT extern "C" __declspec(dllexport) +#define WIN32_LEAN_AND_MEAN +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef max +#undef max +#endif + +#ifdef min +#undef min +#endif + +#include +#include + +#include + +#include +#include + +#pragma comment(lib, "ntdll.lib") +#pragma comment(lib, "ws2_32.lib") +#pragma comment(lib, "urlmon.lib" ) +#pragma comment(lib, "iphlpapi.lib") +#pragma comment(lib, "Crypt32.lib") + +using namespace std::literals; \ No newline at end of file diff --git a/src/utils/compression.cpp b/src/utils/compression.cpp new file mode 100644 index 0000000..3a6b9e2 --- /dev/null +++ b/src/utils/compression.cpp @@ -0,0 +1,170 @@ +#include +#include "memory.hpp" +#include "compression.hpp" + +#include +#include + +#include + +#include "io.hpp" + +namespace utils::compression +{ + namespace zlib + { + namespace + { + class zlib_stream + { + public: + zlib_stream() + { + memset(&stream_, 0, sizeof(stream_)); + valid_ = inflateInit(&stream_) == Z_OK; + } + + zlib_stream(zlib_stream&&) = delete; + zlib_stream(const zlib_stream&) = delete; + zlib_stream& operator=(zlib_stream&&) = delete; + zlib_stream& operator=(const zlib_stream&) = delete; + + ~zlib_stream() + { + if (valid_) + { + inflateEnd(&stream_); + } + } + + z_stream& get() + { + return stream_; // + } + + bool is_valid() const + { + return valid_; + } + + private: + bool valid_{false}; + z_stream stream_{}; + }; + } + + std::string decompress(const std::string& data) + { + std::string buffer{}; + zlib_stream stream_container{}; + if (!stream_container.is_valid()) + { + return {}; + } + + int ret{}; + size_t offset = 0; + static thread_local uint8_t dest[CHUNK] = {0}; + auto& stream = stream_container.get(); + + do + { + const auto input_size = std::min(sizeof(dest), data.size() - offset); + stream.avail_in = static_cast(input_size); + stream.next_in = reinterpret_cast(data.data()) + offset; + offset += stream.avail_in; + + do + { + stream.avail_out = sizeof(dest); + stream.next_out = dest; + + ret = inflate(&stream, Z_NO_FLUSH); + if (ret != Z_OK && ret != Z_STREAM_END) + { + return {}; + } + + buffer.insert(buffer.end(), dest, dest + sizeof(dest) - stream.avail_out); + } + while (stream.avail_out == 0); + } + while (ret != Z_STREAM_END); + + return buffer; + } + + std::string compress(const std::string& data) + { + std::string result{}; + auto length = compressBound(static_cast(data.size())); + result.resize(length); + + if (compress2(reinterpret_cast(result.data()), &length, + reinterpret_cast(data.data()), static_cast(data.size()), + Z_BEST_COMPRESSION) != Z_OK) + { + return {}; + } + + result.resize(length); + return result; + } + } + + namespace zip + { + namespace + { + bool add_file(zipFile& zip_file, const std::string& filename, const std::string& data) + { + const auto zip_64 = data.size() > 0xffffffff ? 1 : 0; + if (ZIP_OK != zipOpenNewFileInZip64(zip_file, filename.data(), nullptr, nullptr, 0, nullptr, 0, nullptr, + Z_DEFLATED, Z_BEST_COMPRESSION, zip_64)) + { + return false; + } + + const auto _ = gsl::finally([&zip_file]() + { + zipCloseFileInZip(zip_file); + }); + + return ZIP_OK == zipWriteInFileInZip(zip_file, data.data(), static_cast(data.size())); + } + } + + void archive::add(std::string filename, std::string data) + { + this->files_[std::move(filename)] = std::move(data); + } + + bool archive::write(const std::string& filename, const std::string& comment) + { + // Hack to create the directory :3 + io::write_file(filename, {}); + io::remove_file(filename); + + auto* zip_file = zipOpen64(filename.data(), 0); + if (!zip_file) + { + return false; + } + + const auto _ = gsl::finally([&zip_file, &comment]() + { + zipClose(zip_file, comment.empty() ? nullptr : comment.data()); + }); + + for (const auto& file : this->files_) + { + if (!add_file(zip_file, file.first, file.second)) + { + return false; + } + } + + return true; + } + } +} diff --git a/src/utils/compression.hpp b/src/utils/compression.hpp new file mode 100644 index 0000000..dfe36ad --- /dev/null +++ b/src/utils/compression.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +#define CHUNK 16384u + +namespace utils::compression +{ + namespace zlib + { + std::string compress(const std::string& data); + std::string decompress(const std::string& data); + } + + namespace zip + { + class archive + { + public: + void add(std::string filename, std::string data); + bool write(const std::string& filename, const std::string& comment = {}); + + private: + std::unordered_map files_; + }; + } +}; diff --git a/src/utils/concurrency.hpp b/src/utils/concurrency.hpp new file mode 100644 index 0000000..05c5d3a --- /dev/null +++ b/src/utils/concurrency.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include + +namespace utils::concurrency +{ + template + class container + { + public: + template + R access(F&& accessor) const + { + std::lock_guard _{mutex_}; + return accessor(object_); + } + + template + R access(F&& accessor) + { + std::lock_guard _{mutex_}; + return accessor(object_); + } + + template + R access_with_lock(F&& accessor) const + { + std::unique_lock lock{mutex_}; + return accessor(object_, lock); + } + + template + R access_with_lock(F&& accessor) + { + std::unique_lock lock{mutex_}; + return accessor(object_, lock); + } + + T& get_raw() { return object_; } + const T& get_raw() const { return object_; } + + private: + mutable MutexType mutex_{}; + T object_{}; + }; +} diff --git a/src/utils/flags.cpp b/src/utils/flags.cpp new file mode 100644 index 0000000..9d467f4 --- /dev/null +++ b/src/utils/flags.cpp @@ -0,0 +1,54 @@ +#include + +#include "flags.hpp" +#include "string.hpp" + +#include + +namespace utils::flags +{ + void parse_flags(std::vector& flags) + { + int num_args; + auto* const argv = CommandLineToArgvW(GetCommandLineW(), &num_args); + + flags.clear(); + + if (argv) + { + for (auto i = 0; i < num_args; ++i) + { + std::wstring wide_flag(argv[i]); + if (wide_flag[0] == L'-') + { + wide_flag.erase(wide_flag.begin()); + flags.emplace_back(string::convert(wide_flag)); + } + } + + LocalFree(argv); + } + } + + bool has_flag(const std::string& flag) + { + static auto parsed = false; + static std::vector enabled_flags; + + if (!parsed) + { + parse_flags(enabled_flags); + parsed = true; + } + + for (const auto& entry : enabled_flags) + { + if (string::to_lower(entry) == string::to_lower(flag)) + { + return true; + } + } + + return false; + } +} diff --git a/src/utils/flags.hpp b/src/utils/flags.hpp new file mode 100644 index 0000000..7f09cae --- /dev/null +++ b/src/utils/flags.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace utils::flags +{ + bool has_flag(const std::string& flag); +} diff --git a/src/utils/hook.cpp b/src/utils/hook.cpp new file mode 100644 index 0000000..22c57c0 --- /dev/null +++ b/src/utils/hook.cpp @@ -0,0 +1,208 @@ +#include +#include "hook.hpp" +#include "string.hpp" +// iw6x-client + +namespace utils::hook +{ + namespace + { + [[maybe_unused]] class _ + { + public: + _() + { + if (MH_Initialize() != MH_OK) + { + throw std::runtime_error("Failed to initialize MinHook"); + } + } + + ~_() + { + MH_Uninitialize(); + } + } __; + } + + asmjit::Error assembler::call(void* target) + { + return Assembler::call(size_t(target)); + } + + asmjit::Error assembler::jmp(void* target) + { + return Assembler::jmp(size_t(target)); + } + + detour::detour(const size_t place, void* target) : detour(reinterpret_cast(place), target) + { + } + + detour::detour(void* place, void* target) + { + this->create(place, target); + } + + detour::~detour() + { + this->clear(); + } + + void detour::enable() const + { + MH_EnableHook(this->place_); + } + + void detour::disable() const + { + MH_DisableHook(this->place_); + } + + void detour::create(void* place, void* target) + { + this->clear(); + this->place_ = place; + + if (MH_CreateHook(this->place_, target, &this->original_) != MH_OK) + { + throw std::runtime_error(string::va("Unable to create hook at location: %p", this->place_)); + } + + this->enable(); + } + + void detour::create(const size_t place, void* target) + { + this->create(reinterpret_cast(place), target); + } + + void detour::clear() + { + if (this->place_) + { + MH_RemoveHook(this->place_); + } + + this->place_ = nullptr; + this->original_ = nullptr; + } + + void* detour::get_original() const + { + return this->original_; + } + + void nop(void* place, const size_t length) + { + DWORD old_protect{}; + VirtualProtect(place, length, PAGE_EXECUTE_READWRITE, &old_protect); + + std::memset(place, 0x90, length); + + VirtualProtect(place, length, old_protect, &old_protect); + FlushInstructionCache(GetCurrentProcess(), place, length); + } + + void nop(const size_t place, const size_t length) + { + nop(reinterpret_cast(place), length); + } + + void copy(void* place, const void* data, const size_t length) + { + DWORD old_protect{}; + VirtualProtect(place, length, PAGE_EXECUTE_READWRITE, &old_protect); + + std::memmove(place, data, length); + + VirtualProtect(place, length, old_protect, &old_protect); + FlushInstructionCache(GetCurrentProcess(), place, length); + } + + void copy(const size_t place, const void* data, const size_t length) + { + copy(reinterpret_cast(place), data, length); + } + + bool is_relatively_far(const void* pointer, const void* data, int offset) + { + const int64_t diff = size_t(data) - (size_t(pointer) + offset); + const auto small_diff = int32_t(diff); + return diff != int64_t(small_diff); + } + + void call(void* pointer, void* data) + { + if (is_relatively_far(pointer, data)) + { + throw std::runtime_error("Too far away to create 32bit relative branch"); + } + + auto* patch_pointer = PBYTE(pointer); + set(patch_pointer, 0xE8); + set(patch_pointer + 1, int32_t(size_t(data) - (size_t(pointer) + 5))); + } + + void call(const size_t pointer, void* data) + { + return call(reinterpret_cast(pointer), data); + } + + void call(const size_t pointer, const size_t data) + { + return call(pointer, reinterpret_cast(data)); + } + + void set(std::uintptr_t address, std::vector&& bytes) + { + DWORD oldProtect = 0; + + auto* place = reinterpret_cast(address); + VirtualProtect(place, bytes.size(), PAGE_EXECUTE_READWRITE, &oldProtect); + memcpy(place, bytes.data(), bytes.size()); + VirtualProtect(place, bytes.size(), oldProtect, &oldProtect); + FlushInstructionCache(GetCurrentProcess(), place, bytes.size()); + } + + void set(std::uintptr_t address, void* buffer, size_t size) + { + DWORD oldProtect = 0; + + auto* place = reinterpret_cast(address); + VirtualProtect(place, size, PAGE_EXECUTE_READWRITE, &oldProtect); + memcpy(place, buffer, size); + VirtualProtect(place, size, oldProtect, &oldProtect); + FlushInstructionCache(GetCurrentProcess(), place, size); + } + + void jump(std::uintptr_t address, void* destination) + { + if (!address) return; + + std::uint8_t* bytes = new std::uint8_t[5]; + *bytes = 0xE9; + *reinterpret_cast(bytes + 1) = CalculateRelativeJMPAddress(address, destination); + + set(address, bytes, 5); + + delete[] bytes; + } + + void* assemble(const std::function& asm_function) + { + static asmjit::JitRuntime runtime; + + asmjit::CodeHolder code; + code.init(runtime.environment()); + + assembler a(&code); + + asm_function(a); + + void* result = nullptr; + runtime.add(&result, &code); + + return result; + } +} \ No newline at end of file diff --git a/src/utils/hook.hpp b/src/utils/hook.hpp new file mode 100644 index 0000000..8986745 --- /dev/null +++ b/src/utils/hook.hpp @@ -0,0 +1,134 @@ +#pragma once + +#define CalculateRelativeJMPAddress(X, Y) (((std::uintptr_t)Y - (std::uintptr_t)X) - 5) + +#include +#include + +using namespace asmjit::x86; + +namespace utils::hook +{ + class assembler : public Assembler + { + public: + using Assembler::Assembler; + using Assembler::call; + using Assembler::jmp; + + asmjit::Error call(void* target); + asmjit::Error jmp(void* target); + }; + + class detour + { + public: + detour() = default; + detour(void* place, void* target); + detour(size_t place, void* target); + ~detour(); + + detour(detour&& other) noexcept + { + this->operator=(std::move(other)); + } + + detour& operator= (detour&& other) noexcept + { + if (this != &other) + { + this->~detour(); + + this->place_ = other.place_; + this->original_ = other.original_; + + other.place_ = nullptr; + other.original_ = nullptr; + } + + return *this; + } + + detour(const detour&) = delete; + detour& operator= (const detour&) = delete; + + void enable() const; + void disable() const; + + void create(void* place, void* target); + void create(size_t place, void* target); + void clear(); + + template + T* get() const + { + return static_cast(this->get_original()); + } + + template + T invoke(Args... args) + { + return static_cast(this->get_original())(args...); + } + + [[nodiscard]] void* get_original() const; + + private: + void* place_{}; + void* original_{}; + }; + + void nop(void* place, size_t length); + void nop(size_t place, size_t length); + + void copy(void* place, const void* data, size_t length); + void copy(size_t place, const void* data, size_t length); + + bool is_relatively_far(const void* pointer, const void* data, int offset = 5); + + void call(void* pointer, void* data); + void call(size_t pointer, void* data); + void call(size_t pointer, size_t data); + + void jump(std::uintptr_t address, void* destination); + + void* assemble(const std::function& asm_function); + + template + T extract(void* address) + { + const auto data = static_cast(address); + const auto offset = *reinterpret_cast(data); + return reinterpret_cast(data + offset + 4); + } + + template + static void set(void* place, T value) + { + DWORD old_protect; + VirtualProtect(place, sizeof(T), PAGE_EXECUTE_READWRITE, &old_protect); + + *static_cast(place) = value; + + VirtualProtect(place, sizeof(T), old_protect, &old_protect); + FlushInstructionCache(GetCurrentProcess(), place, sizeof(T)); + } + + template + static void set(const size_t place, T value) + { + return set(reinterpret_cast(place), value); + } + + template + static T invoke(size_t func, Args... args) + { + return reinterpret_cast(func)(args...); + } + + template + static T invoke(void* func, Args... args) + { + return static_cast(func)(args...); + } +} \ No newline at end of file diff --git a/src/utils/http.cpp b/src/utils/http.cpp new file mode 100644 index 0000000..624faa3 --- /dev/null +++ b/src/utils/http.cpp @@ -0,0 +1,101 @@ +#include +#include "http.hpp" +#include +#include + +#pragma comment(lib, "ws2_32.lib") + +namespace utils::http +{ + namespace + { + struct progress_helper + { + const std::function* callback{}; + std::exception_ptr exception{}; + }; + + int progress_callback(void* clientp, const curl_off_t /*dltotal*/, const curl_off_t dlnow, const curl_off_t /*ultotal*/, const curl_off_t /*ulnow*/) + { + auto* helper = static_cast(clientp); + + try + { + if (*helper->callback) + { + (*helper->callback)(dlnow); + } + } + catch (...) + { + helper->exception = std::current_exception(); + return -1; + } + + return 0; + } + + size_t write_callback(void* contents, const size_t size, const size_t nmemb, void* userp) + { + auto* buffer = static_cast(userp); + + const auto total_size = size * nmemb; + buffer->append(static_cast(contents), total_size); + return total_size; + } + } + + std::optional get_data(const std::string& url, const headers& headers, const std::function& callback) + { + curl_slist* header_list = nullptr; + auto* curl = curl_easy_init(); + if (!curl) + { + 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{}; + progress_helper helper{}; + helper.callback = &callback; + + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_list); + curl_easy_setopt(curl, CURLOPT_URL, url.data()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); + curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback); + curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &helper); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); + + if (curl_easy_perform(curl) == CURLE_OK) + { + return {std::move(buffer)}; + } + + if (helper.exception) + { + std::rethrow_exception(helper.exception); + } + + return {}; + } + + std::future> get_data_async(const std::string& url, const headers& headers) + { + return std::async(std::launch::async, [url, headers]() + { + return get_data(url, headers); + }); + } +} \ No newline at end of file diff --git a/src/utils/http.hpp b/src/utils/http.hpp new file mode 100644 index 0000000..94c4451 --- /dev/null +++ b/src/utils/http.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include +#include +#include + +namespace utils::http +{ + using headers = std::unordered_map; + + std::optional get_data(const std::string& url, const headers& headers = {}, const std::function& callback = {}); + std::future> get_data_async(const std::string& url, const headers& headers = {}); +} \ No newline at end of file diff --git a/src/utils/io.cpp b/src/utils/io.cpp new file mode 100644 index 0000000..038c085 --- /dev/null +++ b/src/utils/io.cpp @@ -0,0 +1,123 @@ +#include +#include +#include "io.hpp" + +namespace utils::io +{ + bool remove_file(const std::string& file) + { + return DeleteFileA(file.data()) == TRUE; + } + + bool move_file(const std::string& src, const std::string& target) + { + return MoveFileA(src.data(), target.data()) == TRUE; + } + + bool file_exists(const std::string& file) + { + return std::ifstream(file).good(); + } + + bool write_file(const std::string& file, const std::string& data, const bool append) + { + const auto pos = file.find_last_of("/\\"); + if (pos != std::string::npos) + { + create_directory(file.substr(0, pos)); + } + + std::ofstream stream( + file, std::ios::binary | std::ofstream::out | (append ? std::ofstream::app : 0)); + + if (stream.is_open()) + { + stream.write(data.data(), data.size()); + stream.close(); + return true; + } + + return false; + } + + std::string read_file(const std::string& file) + { + std::string data; + read_file(file, &data); + return data; + } + + bool read_file(const std::string& file, std::string* data) + { + if (!data) return false; + data->clear(); + + if (file_exists(file)) + { + std::ifstream stream(file, std::ios::binary); + if (!stream.is_open()) return false; + + stream.seekg(0, std::ios::end); + const std::streamsize size = stream.tellg(); + stream.seekg(0, std::ios::beg); + + if (size > -1) + { + data->resize(static_cast(size)); + stream.read(const_cast(data->data()), size); + stream.close(); + return true; + } + } + + return false; + } + + size_t file_size(const std::string& file) + { + if (file_exists(file)) + { + std::ifstream stream(file, std::ios::binary); + + if (stream.good()) + { + stream.seekg(0, std::ios::end); + return static_cast(stream.tellg()); + } + } + + return 0; + } + + bool create_directory(const std::string& directory) + { + return std::filesystem::create_directories(directory); + } + + bool directory_exists(const std::string& directory) + { + return std::filesystem::is_directory(directory); + } + + bool directory_is_empty(const std::string& directory) + { + return std::filesystem::is_empty(directory); + } + + std::vector list_files(const std::string& directory) + { + std::vector files; + + for (auto& file : std::filesystem::directory_iterator(directory)) + { + files.push_back(file.path().generic_string()); + } + + return files; + } + + void copy_folder(const std::filesystem::path& src, const std::filesystem::path& target) + { + std::filesystem::copy(src, target, std::filesystem::copy_options::overwrite_existing | std::filesystem::copy_options::recursive); + } +} \ No newline at end of file diff --git a/src/utils/io.hpp b/src/utils/io.hpp new file mode 100644 index 0000000..ab4ebaa --- /dev/null +++ b/src/utils/io.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include + +namespace utils::io +{ + bool remove_file(const std::string& file); + bool move_file(const std::string& src, const std::string& target); + bool file_exists(const std::string& file); + bool write_file(const std::string& file, const std::string& data, bool append = false); + bool read_file(const std::string& file, std::string* data); + std::string read_file(const std::string& file); + size_t file_size(const std::string& file); + bool create_directory(const std::string& directory); + bool directory_exists(const std::string& directory); + bool directory_is_empty(const std::string& directory); + std::vector list_files(const std::string& directory); + void copy_folder(const std::filesystem::path& src, const std::filesystem::path& target); +} diff --git a/src/utils/memory.cpp b/src/utils/memory.cpp new file mode 100644 index 0000000..3809feb --- /dev/null +++ b/src/utils/memory.cpp @@ -0,0 +1,109 @@ +#include +#include "memory.hpp" + +namespace utils +{ + memory::allocator memory::mem_allocator_; + + memory::allocator::~allocator() + { + this->clear(); + } + + void memory::allocator::clear() + { + std::lock_guard _(this->mutex_); + + for (auto& data : this->pool_) + { + memory::free(data); + } + + this->pool_.clear(); + } + + void memory::allocator::free(void* data) + { + std::lock_guard _(this->mutex_); + + const auto j = std::find(this->pool_.begin(), this->pool_.end(), data); + if (j != this->pool_.end()) + { + memory::free(data); + this->pool_.erase(j); + } + } + + void memory::allocator::free(const void* data) + { + this->free(const_cast(data)); + } + + void* memory::allocator::allocate(const size_t length) + { + std::lock_guard _(this->mutex_); + + const auto data = memory::allocate(length); + this->pool_.push_back(data); + return data; + } + + bool memory::allocator::empty() const + { + return this->pool_.empty(); + } + + char* memory::allocator::duplicate_string(const std::string& string) + { + std::lock_guard _(this->mutex_); + + const auto data = memory::duplicate_string(string); + this->pool_.push_back(data); + return data; + } + + void* memory::allocate(const size_t length) + { + return calloc(length, 1); + } + + char* memory::duplicate_string(const std::string& string) + { + const auto new_string = allocate_array(string.size() + 1); + std::memcpy(new_string, string.data(), string.size()); + return new_string; + } + + void memory::free(void* data) + { + if (data) + { + ::free(data); + } + } + + void memory::free(const void* data) + { + free(const_cast(data)); + } + + bool memory::is_set(const void* mem, const char chr, const size_t length) + { + const auto mem_arr = static_cast(mem); + + for (size_t i = 0; i < length; ++i) + { + if (mem_arr[i] != chr) + { + return false; + } + } + + return true; + } + + memory::allocator* memory::get_allocator() + { + return &memory::mem_allocator_; + } +} \ No newline at end of file diff --git a/src/utils/memory.hpp b/src/utils/memory.hpp new file mode 100644 index 0000000..6d6a8b5 --- /dev/null +++ b/src/utils/memory.hpp @@ -0,0 +1,75 @@ +#pragma once + +#include +#include + +namespace utils +{ + class memory final + { + public: + class allocator final + { + public: + ~allocator(); + + void clear(); + + void free(void* data); + + void free(const void* data); + + void* allocate(size_t length); + + template + inline T* allocate() + { + return this->allocate_array(1); + } + + template + inline T* allocate_array(const size_t count = 1) + { + return static_cast(this->allocate(count * sizeof(T))); + } + + bool empty() const; + + char* duplicate_string(const std::string& string); + + private: + std::mutex mutex_; + std::vector pool_; + }; + + static void* allocate(size_t length); + + template + static inline T* allocate() + { + return allocate_array(1); + } + + template + static inline T* allocate_array(const size_t count = 1) + { + return static_cast(allocate(count * sizeof(T))); + } + + static char* duplicate_string(const std::string& string); + + static void free(void* data); + static void free(const void* data); + + static bool is_set(const void* mem, char chr, size_t length); + + static bool is_bad_read_ptr(const void* ptr); + static bool is_bad_code_ptr(const void* ptr); + static bool is_rdata_ptr(void* ptr); + + static allocator* get_allocator(); + + private: + static allocator mem_allocator_; + }; +} \ No newline at end of file diff --git a/src/utils/string.cpp b/src/utils/string.cpp new file mode 100644 index 0000000..8e1f53b --- /dev/null +++ b/src/utils/string.cpp @@ -0,0 +1,172 @@ +#include +#include "string.hpp" +#include +#include +#include + +namespace utils::string +{ + const char* va(const char* fmt, ...) + { + static thread_local va_provider<8, 256> provider; + + va_list ap; + va_start(ap, fmt); + + const char* result = provider.get(fmt, ap); + + va_end(ap); + return result; + } + + std::vector split(const std::string& s, const char delim) + { + std::stringstream ss(s); + std::string item; + std::vector elems; + + while (std::getline(ss, item, delim)) + { + elems.push_back(item); // elems.push_back(std::move(item)); // if C++11 (based on comment from @mchiasson) + } + + return elems; + } + + std::string to_lower(std::string text) + { + std::transform(text.begin(), text.end(), text.begin(), [](const char input) + { + return static_cast(tolower(input)); + }); + + return text; + } + + std::string to_upper(std::string text) + { + std::transform(text.begin(), text.end(), text.begin(), [](const char input) + { + return static_cast(toupper(input)); + }); + + return text; + } + + bool starts_with(const std::string& text, const std::string& substring) + { + return text.find(substring) == 0; + } + + bool ends_with(const std::string& text, const std::string& substring) + { + if (substring.size() > text.size()) return false; + return std::equal(substring.rbegin(), substring.rend(), text.rbegin()); + } + + bool is_numeric(const std::string& text) + { + return std::to_string(atoi(text.data())) == text; + } + + std::string dump_hex(const std::string& data, const std::string& separator) + { + std::string result; + + for (unsigned int i = 0; i < data.size(); ++i) + { + if (i > 0) + { + result.append(separator); + } + + result.append(va("%02X", data[i] & 0xFF)); + } + + return result; + } + + void strip(const char* in, char* out, int max) + { + if (!in || !out) return; + + max--; + auto current = 0; + while (*in != 0 && current < max) + { + const auto color_index = (*(in + 1) - 48) >= 0xC ? 7 : (*(in + 1) - 48); + + if (*in == '^' && (color_index != 7 || *(in + 1) == '7')) + { + ++in; + } + else + { + *out = *in; + ++out; + ++current; + } + + ++in; + } + *out = '\0'; + } + +#pragma warning(push) +#pragma warning(disable: 4100) + std::string convert(const std::wstring& wstr) + { + std::string result; + result.reserve(wstr.size()); + + for (const auto& chr : wstr) + { + result.push_back(static_cast(chr)); + } + + return result; + } + + std::wstring convert(const std::string& str) + { + std::wstring result; + result.reserve(str.size()); + + for (const auto& chr : str) + { + result.push_back(static_cast(chr)); + } + + return result; + } +#pragma warning(pop) + + std::string replace(std::string str, const std::string& from, const std::string& to) + { + if (from.empty()) + { + return str; + } + + size_t start_pos = 0; + while ((start_pos = str.find(from, start_pos)) != std::string::npos) + { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + + return str; + } + + std::string get_timestamp() + { + tm ltime{}; + char timestamp[MAX_PATH] = { 0 }; + const auto time = _time64(nullptr); + + _localtime64_s(<ime, &time); + strftime(timestamp, sizeof(timestamp) - 1, "%Y-%m-%d-%H-%M-%S", <ime); + + return timestamp; + } +} \ No newline at end of file diff --git a/src/utils/string.hpp b/src/utils/string.hpp new file mode 100644 index 0000000..baa8da8 --- /dev/null +++ b/src/utils/string.hpp @@ -0,0 +1,101 @@ +#pragma once +#include "memory.hpp" +#include + +#ifndef ARRAYSIZE +template +size_t ARRAYSIZE(Type(&)[n]) { return n; } +#endif + +namespace utils::string +{ + template + class va_provider final + { + public: + static_assert(Buffers != 0 && MinBufferSize != 0, "Buffers and MinBufferSize mustn't be 0"); + + va_provider() : current_buffer_(0) + { + } + + char* get(const char* format, const va_list ap) + { + ++this->current_buffer_ %= ARRAYSIZE(this->string_pool_); + auto entry = &this->string_pool_[this->current_buffer_]; + + if (!entry->size || !entry->buffer) + { + throw std::runtime_error("String pool not initialized"); + } + + while (true) + { + const int res = vsnprintf_s(entry->buffer, entry->size, _TRUNCATE, format, ap); + if (res > 0) break; // Success + if (res == 0) return nullptr; // Error + + entry->double_size(); + } + + return entry->buffer; + } + + private: + class entry final + { + public: + explicit entry(const size_t _size = MinBufferSize) : size(_size), buffer(nullptr) + { + if (this->size < MinBufferSize) this->size = MinBufferSize; + this->allocate(); + } + + ~entry() + { + if (this->buffer) memory::get_allocator()->free(this->buffer); + this->size = 0; + this->buffer = nullptr; + } + + void allocate() + { + if (this->buffer) memory::get_allocator()->free(this->buffer); + this->buffer = memory::get_allocator()->allocate_array(this->size + 1); + } + + void double_size() + { + this->size *= 2; + this->allocate(); + } + + size_t size; + char* buffer; + }; + + size_t current_buffer_; + entry string_pool_[Buffers]; + }; + + const char* va(const char* fmt, ...); + + std::vector split(const std::string& s, char delim); + + std::string to_lower(std::string text); + std::string to_upper(std::string text); + bool starts_with(const std::string& text, const std::string& substring); + bool ends_with(const std::string& text, const std::string& substring); + bool is_numeric(const std::string& text); + + std::string dump_hex(const std::string& data, const std::string& separator = " "); + + void strip(const char* in, char* out, int max); + + std::string convert(const std::wstring& wstr); + std::wstring convert(const std::string& str); + + std::string replace(std::string str, const std::string& from, const std::string& to); + + std::string get_timestamp(); +} \ No newline at end of file diff --git a/src/utils/thread.cpp b/src/utils/thread.cpp new file mode 100644 index 0000000..1c20ff5 --- /dev/null +++ b/src/utils/thread.cpp @@ -0,0 +1,89 @@ +#include +#include "thread.hpp" +#include "string.hpp" + +#include + +#include + +namespace utils::thread +{ + std::vector get_thread_ids() + { + auto* const h = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, GetCurrentProcessId()); + if (h == INVALID_HANDLE_VALUE) + { + return {}; + } + + const auto _ = gsl::finally([h]() + { + CloseHandle(h); + }); + + THREADENTRY32 entry{}; + entry.dwSize = sizeof(entry); + if (!Thread32First(h, &entry)) + { + return {}; + } + + std::vector ids{}; + + do + { + const auto check_size = entry.dwSize < FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + + sizeof(entry.th32OwnerProcessID); + entry.dwSize = sizeof(entry); + + if (check_size && entry.th32OwnerProcessID == GetCurrentProcessId()) + { + ids.emplace_back(entry.th32ThreadID); + } + } + while (Thread32Next(h, &entry)); + + return ids; + } + + void for_each_thread(const std::function& callback) + { + const auto ids = get_thread_ids(); + + for (const auto& id : ids) + { + auto* const thread = OpenThread(THREAD_ALL_ACCESS, FALSE, id); + if (thread != nullptr) + { + const auto _ = gsl::finally([thread]() + { + CloseHandle(thread); + }); + + callback(thread); + } + } + } + + void suspend_other_threads() + { + for_each_thread([](const HANDLE thread) + { + if (GetThreadId(thread) != GetCurrentThreadId()) + { + SuspendThread(thread); + } + }); + } + + void resume_other_threads() + { + for_each_thread([](const HANDLE thread) + { + if (GetThreadId(thread) != GetCurrentThreadId()) + { + ResumeThread(thread); + } + }); + } +} diff --git a/src/utils/thread.hpp b/src/utils/thread.hpp new file mode 100644 index 0000000..4d67c1a --- /dev/null +++ b/src/utils/thread.hpp @@ -0,0 +1,19 @@ +#pragma once +#include + +namespace utils::thread +{ + template + std::thread create_named_thread(const std::string& name, Args&&... args) + { + auto t = std::thread(std::forward(args)...); + set_name(t, name); + return t; + } + + std::vector get_thread_ids(); + void for_each_thread(const std::function& callback); + + void suspend_other_threads(); + void resume_other_threads(); +} diff --git a/tools/windows/premake5.exe b/tools/windows/premake5.exe new file mode 100644 index 0000000..c73da1f Binary files /dev/null and b/tools/windows/premake5.exe differ