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/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/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..d7f9333 --- /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 "t6-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 "t6-gsc-utils" + + project "t6-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/signatures.cpp b/src/component/signatures.cpp new file mode 100644 index 0000000..1be06b2 --- /dev/null +++ b/src/component/signatures.cpp @@ -0,0 +1,72 @@ +#include + +#include "game/game.hpp" +#include "signatures.hpp" + +#include +#include + +namespace signatures +{ + size_t load_image_size() + { + MODULEINFO info{}; + GetModuleInformation(GetCurrentProcess(), + GetModuleHandle("plutonium-bootstrapper-win32.exe"), &info, sizeof(MODULEINFO)); + return info.SizeOfImage; + } + + size_t get_image_size() + { + static const auto image_size = load_image_size(); + return image_size; + } + + size_t find_string_ptr(const std::string& string) + { + const char* string_ptr = nullptr; + std::string mask(string.size(), 'x'); + const auto base = reinterpret_cast(GetModuleHandle("plutonium-bootstrapper-win32.exe")); + utils::hook::signature signature(base, get_image_size() - base); + + signature.add({ + string, + mask, + [&](char* address) + { + string_ptr = address; + } + }); + + signature.process(); + return reinterpret_cast(string_ptr); + } + + size_t find_string_ref(const std::string& string) + { + char bytes[4] = {0}; + const auto string_ptr = find_string_ptr(string); + memcpy(bytes, &string_ptr, sizeof(bytes)); + return find_string_ptr({bytes, 4}); + } + + + bool process_printf() + { + const auto string_ref = find_string_ref("A critical exception occured!\n"); + if (!string_ref) + { + return false; + } + + const auto offset = *reinterpret_cast(string_ref + 5); + OutputDebugString(utils::string::va("%p\n", string_ref + 4 + 5 + offset)); + game::plutonium::printf.set(string_ref + 4 + 5 + offset); + return true; + } + + bool process() + { + return process_printf(); + } +} diff --git a/src/component/signatures.hpp b/src/component/signatures.hpp new file mode 100644 index 0000000..046cf80 --- /dev/null +++ b/src/component/signatures.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace signatures +{ + bool process(); +} \ No newline at end of file diff --git a/src/dllmain.cpp b/src/dllmain.cpp new file mode 100644 index 0000000..aba749e --- /dev/null +++ b/src/dllmain.cpp @@ -0,0 +1,32 @@ +#include +#include "loader/component_loader.hpp" + +#include "game/game.hpp" + +#include "component/signatures.hpp" +#include + +BOOL APIENTRY DllMain(HMODULE /*module_*/, DWORD ul_reason_for_call, LPVOID /*reserved_*/) +{ + if (ul_reason_for_call == DLL_PROCESS_ATTACH) + { + if (!signatures::process()) + { + MessageBoxA(NULL, + "This version of T4SP-Server-Plugin is outdated.\n" \ + "Download the latest dll from here: https://github.com/fedddddd/t6-gsc-utils/releases", + "ERROR", MB_ICONERROR); + + return FALSE; + } + + if (game::plutonium::printf.get() != nullptr) + { + 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/game/game.cpp b/src/game/game.cpp new file mode 100644 index 0000000..2975417 --- /dev/null +++ b/src/game/game.cpp @@ -0,0 +1,32 @@ +#include +#include "game.hpp" + +#include + +namespace game +{ + gamemode current = reinterpret_cast(0x88A5DC) != "CoDWaW.exe"s + ? gamemode::multiplayer + : gamemode::singleplayer; + + namespace environment + { + bool t4mp() + { + return current == gamemode::multiplayer; + } + + bool t4sp() + { + return current == gamemode::singleplayer; + } + } + + namespace plutonium + { + bool is_up_to_date() + { + return true; + } + } +} diff --git a/src/game/game.hpp b/src/game/game.hpp new file mode 100644 index 0000000..99ce037 --- /dev/null +++ b/src/game/game.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include "structs.hpp" + +#define SELECT(mp, sp) (game::environment::t4mp() ? mp : sp) + +namespace game +{ + enum gamemode + { + multiplayer, + singleplayer, + none + }; + + extern gamemode current; + + namespace environment + { + bool t4mp(); + bool t4sp(); + } + + template + class symbol + { + public: + symbol(const size_t t4mp, const size_t t4sp) + : t4mp_(reinterpret_cast(t4mp)) + , t4sp_(reinterpret_cast(t4sp)) + { + } + + T* get() const + { + if (environment::t4mp()) + { + return t4mp_; + } + + return t4sp_; + } + + void set(const size_t ptr) + { + this->t4mp_ = reinterpret_cast(ptr); + this->t4sp_ = reinterpret_cast(ptr); + } + + operator T* () const + { + return this->get(); + } + + T* operator->() const + { + return this->get(); + } + + private: + T* t4mp_; + T* t4sp_; + }; +} + +#include "symbols.hpp" \ No newline at end of file diff --git a/src/game/structs.hpp b/src/game/structs.hpp new file mode 100644 index 0000000..731be76 --- /dev/null +++ b/src/game/structs.hpp @@ -0,0 +1,9 @@ +#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]; +} \ No newline at end of file diff --git a/src/game/symbols.hpp b/src/game/symbols.hpp new file mode 100644 index 0000000..3f17147 --- /dev/null +++ b/src/game/symbols.hpp @@ -0,0 +1,15 @@ +#pragma once + +#define WEAK __declspec(selectany) + +namespace game +{ + // Functions + + // Variables + + namespace plutonium + { + WEAK symbol printf{0, 0}; + } +} 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..8f012d3 --- /dev/null +++ b/src/stdinc.hpp @@ -0,0 +1,56 @@ +#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 +#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..7248bec --- /dev/null +++ b/src/utils/hook.cpp @@ -0,0 +1,247 @@ +#include +#include "hook.hpp" +#include "string.hpp" +// iw6x-client + +namespace utils::hook +{ + void signature::process() + { + if (this->signatures_.empty()) return; + + const auto start = static_cast(this->start_); + + const unsigned int sig_count = this->signatures_.size(); + const auto containers = this->signatures_.data(); + + for (size_t i = 0; i < this->length_; ++i) + { + const auto address = start + i; + + for (unsigned int k = 0; k < sig_count; ++k) + { + const auto container = &containers[k]; + + unsigned int j; + for (j = 0; j < static_cast(container->mask.size()); ++j) + { + if (container->mask[j] != '?' && container->signature[j] != address[j]) + { + break; + } + } + + if (j == container->mask.size()) + { + container->callback(address); + } + } + } + } + + void signature::add(const container& container) + { + signatures_.push_back(container); + } + + 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..160ea38 --- /dev/null +++ b/src/utils/hook.hpp @@ -0,0 +1,165 @@ +#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 signature final + { + public: + struct container final + { + std::string signature; + std::string mask; + std::function callback; + }; + + signature(void* start, const size_t length) : start_(start), length_(length) + { + } + + signature(const DWORD start, const size_t length) : signature(reinterpret_cast(start), length) + { + } + + signature() : signature(0x400000, 0x800000) + { + } + + void process(); + void add(const container& container); + + private: + void* start_; + size_t length_; + std::vector signatures_; + }; + + 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..4254bb1 --- /dev/null +++ b/src/utils/io.cpp @@ -0,0 +1,118 @@ +#include +#include +#include "io.hpp" + +namespace utils::io +{ + bool remove_file(const std::string& file) + { + return DeleteFileA(file.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..29d6912 --- /dev/null +++ b/src/utils/io.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include + +namespace utils::io +{ + bool remove_file(const std::string& file); + 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