diff --git a/.gitattributes b/.gitattributes index dfe07704..1ff0c423 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,63 @@ -# Auto detect text files and perform LF normalization +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### * text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..130a33a7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,150 @@ +### Windows + +# 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 + +# Premake binary +#premake5.exe \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..4c70f39e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "deps/minhook"] + path = deps/minhook + url = https://github.com/TsudaKageyu/minhook.git diff --git a/deps/minhook b/deps/minhook new file mode 160000 index 00000000..423d1e45 --- /dev/null +++ b/deps/minhook @@ -0,0 +1 @@ +Subproject commit 423d1e45af2ed2719a5c31e990e935ef301ed9c3 diff --git a/deps/premake/minhook.lua b/deps/premake/minhook.lua new file mode 100644 index 00000000..396d4d38 --- /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/generate.bat b/generate.bat new file mode 100644 index 00000000..62971d28 --- /dev/null +++ b/generate.bat @@ -0,0 +1,3 @@ +@echo off +git submodule update --init --recursive +tools\windows\premake5.exe vs2019 diff --git a/premake5.lua b/premake5.lua new file mode 100644 index 00000000..d45321a3 --- /dev/null +++ b/premake5.lua @@ -0,0 +1,121 @@ +gitVersioningCommand = "git describe --tags --dirty --always" +gitCurrentBranchCommand = "git symbolic-ref -q --short HEAD" + +-- Quote the given string input as a C string +function cstrquote(value) + if value == nil then + return "\"\"" + end + result = value:gsub("\\", "\\\\") + result = result:gsub("\"", "\\\"") + result = result:gsub("\n", "\\n") + result = result:gsub("\t", "\\t") + result = result:gsub("\r", "\\r") + result = result:gsub("\a", "\\a") + result = result:gsub("\b", "\\b") + result = "\"" .. result .. "\"" + return result +end + +-- Converts tags in "vX.X.X" format and given revision number Y to an array of numbers {X,X,X,Y}. +-- In the case where the format does not work fall back to padding with zeroes and just ending with the revision number. +-- partscount can be either 3 or 4. +function vertonumarr(value, vernumber, partscount) + vernum = {} + for num in string.gmatch(value or "", "%d+") do + if #vernum < 3 then + table.insert(vernum, tonumber(num)) + end + end + while #vernum < 3 do + table.insert(vernum, 0) + end + if #vernum < partscount then + table.insert(vernum, tonumber(vernumber)) + end + return vernum +end + +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 "h2-mod" + location "./build" + objdir "%{wks.location}/obj/%{cfg.buildcfg}" + targetdir "%{wks.location}/bin/%{cfg.buildcfg}" + targetname "%{prj.name}" + + language "C++" + + architecture "x64" + platforms "x64" + + buildoptions "/std:c++latest" + systemversion "latest" + + flags + { + "NoIncrementalLink", + "MultiProcessorCompile", + } + + configurations { "Debug", "Release", } + + symbols "On" + + configuration "Release" + optimize "Full" + defines { "NDEBUG" } + configuration{} + + configuration "Debug" + optimize "Debug" + defines { "DEBUG", "_DEBUG" } + configuration {} + + startproject "h2-mod" + + project "h2-mod" + kind "SharedLib" + language "C++" + + pchheader "stdinc.hpp" + pchsource "src/stdinc.cpp" + + includedirs { "src" } + + files { "src/**.h", "src/**.hpp", "src/**.cpp" } + + dependencies.imports() + + group "Dependencies" + dependencies.projects() \ No newline at end of file diff --git a/src/component/command.cpp b/src/component/command.cpp new file mode 100644 index 00000000..443c68f1 --- /dev/null +++ b/src/component/command.cpp @@ -0,0 +1,232 @@ +#include +#include "command.hpp" + +#include "game/game.hpp" + +#include +#include +#include + +namespace command +{ + namespace + { + std::unordered_map> handlers; + + void main_handler() + { + params params = {}; + + const auto command = utils::string::to_lower(params[0]); + if (handlers.find(command) != handlers.end()) + { + handlers[command](params); + } + } + + void enum_assets(const game::XAssetType type, const std::function& callback, const bool includeOverride) + { + game::DB_EnumXAssets_Internal(type, static_cast([](game::XAssetHeader header, void* data) + { + const auto& cb = *static_cast*>(data); + cb(header); + }), &callback, includeOverride); + } + } + + params::params() + : nesting_(game::cmd_args->nesting) + { + } + + int params::size() const + { + return game::cmd_args->argc[this->nesting_]; + } + + const char* params::get(const int index) const + { + if (index >= this->size()) + { + return ""; + } + + return game::cmd_args->argv[this->nesting_][index]; + } + + std::string params::join(const int index) const + { + std::string result = {}; + + for (auto i = index; i < this->size(); i++) + { + if (i > index) result.append(" "); + result.append(this->get(i)); + } + return result; + } + + void add_raw(const char* name, void (*callback)()) + { + game::Cmd_AddCommandInternal(name, callback, utils::memory::get_allocator()->allocate()); + } + + void add(const char* name, const std::function& callback) + { + const auto command = utils::string::to_lower(name); + + if (handlers.find(command) == handlers.end()) + add_raw(name, main_handler); + + handlers[command] = callback; + } + + void add(const char* name, const std::function& callback) + { + add(name, [callback](const params&) + { + callback(); + }); + } + + void execute(std::string command, const bool sync) + { + command += "\n"; + + if (sync) + { + game::Cmd_ExecuteSingleCommand(0, 0, command.data()); + } + else + { + game::Cbuf_AddText(0, command.data()); + } + } + + void init() + { + add("listassetpool", [](const params& params) + { + if (params.size() < 2) + { + game_console::print(game_console::con_type_info, "listassetpool : list all the assets in the specified pool\n"); + + for (auto i = 0; i < game::XAssetType::ASSET_TYPE_COUNT; i++) + { + game_console::print(game_console::con_type_info, "%d %s\n", i, game::g_assetNames[i]); + } + } + else + { + const auto type = static_cast(atoi(params.get(1))); + + if (type < 0 || type >= game::XAssetType::ASSET_TYPE_COUNT) + { + game_console::print(game_console::con_type_info, "Invalid pool passed must be between [%d, %d]\n", 0, game::XAssetType::ASSET_TYPE_COUNT - 1); + return; + } + + game_console::print(game_console::con_type_info, "Listing assets in pool %s\n", game::g_assetNames[type]); + + enum_assets(type, [type](const game::XAssetHeader header) + { + const auto asset = game::XAsset{ type, header }; + const auto* const asset_name = game::DB_GetXAssetName(&asset); + //const auto entry = game::DB_FindXAssetEntry(type, asset_name); + //TODO: display which zone the asset is from + game_console::print(game_console::con_type_info, "%s\n", asset_name); + }, true); + } + }); + + add("baseAddress", []() + { + printf("%p\n", (void*)game::base_address); + }); + + add("commandDump", []() + { + printf("======== Start command dump =========\n"); + + game::cmd_function_s* cmd = (*game::cmd_functions); + + while (cmd) + { + if (cmd->name) + { + game_console::print(game_console::con_type_info, "%s\n", cmd->name); + } + + cmd = cmd->next; + } + + printf("======== End command dump =========\n"); + }); + + /*add("noclip", [&]() + { + if (!game::SV_Loaded()) + { + return; + } + + game::sp::g_entities[0].client->flags ^= 1; + game::CG_GameMessage(0, utils::string::va("noclip %s", + game::sp::g_entities[0].client->flags & 1 + ? "^2on" + : "^1off")); + }); + + add("ufo", [&]() + { + if (!game::SV_Loaded()) + { + return; + } + + game::sp::g_entities[0].client->flags ^= 2; + game::CG_GameMessage( + 0, utils::string::va("ufo %s", game::sp::g_entities[0].client->flags & 2 ? "^2on" : "^1off")); + }); + + add("give", [](const params& params) + { + if (!game::SV_Loaded()) + { + return; + } + + if (params.size() < 2) + { + game::CG_GameMessage(0, "You did not specify a weapon name"); + return; + } + + auto ps = game::SV_GetPlayerstateForClientNum(0); + auto wp = game::G_GetWeaponForName(params.get(1)); + if (game::G_GivePlayerWeapon(ps, wp, 0, 0, 0)) + { + game::G_InitializeAmmo(ps, wp, 0); + game::G_SelectWeapon(0, wp); + } + }); + + add("take", [](const params& params) + { + if (!game::SV_Loaded()) + { + return; + } + + if (params.size() < 2) + { + game::CG_GameMessage(0, "You did not specify a weapon name"); + return; + } + + auto ps = game::SV_GetPlayerstateForClientNum(0); + auto wp = game::G_GetWeaponForName(params.get(1)); + game::G_TakePlayerWeapon(ps, wp); + });*/ + } +} diff --git a/src/component/command.hpp b/src/component/command.hpp new file mode 100644 index 00000000..74a51cb7 --- /dev/null +++ b/src/component/command.hpp @@ -0,0 +1,30 @@ +#pragma once + +namespace command +{ + class params + { + public: + params(); + + int size() const; + const char* get(int index) const; + std::string join(int index) const; + + const char* operator[](const int index) const + { + return this->get(index); // + } + + private: + int nesting_; + }; + + void add_raw(const char* name, void (*callback)()); + void add(const char* name, const std::function& callback); + void add(const char* name, const std::function& callback); + + void execute(std::string command, bool sync = false); + + void init(); +} diff --git a/src/component/game_console.cpp b/src/component/game_console.cpp new file mode 100644 index 00000000..e697af1a --- /dev/null +++ b/src/component/game_console.cpp @@ -0,0 +1,685 @@ +#include +#include "game_console.hpp" +#include "command.hpp" +#include "scheduler.hpp" + +#include "game/game.hpp" +#include "game/dvars.hpp" + +#include +#include +#include + +#define console_font game::R_RegisterFont("fonts/fira_mono_regular.ttf", 18) +#define material_white game::Material_RegisterHandle("white") + +namespace game_console +{ + namespace + { + struct console_globals + { + float x; + float y; + float left_x; + float font_height; + bool may_auto_complete; + char auto_complete_choice[64]; + int info_line_count; + }; + + struct ingame_console + { + char buffer[256]; + int cursor; + int font_height; + int visible_line_count; + int visible_pixel_width; + float screen_min[2]; //left & top + float screen_max[2]; //right & bottom + console_globals globals; + bool output_visible; + int display_line_offset; + int line_count; + std::deque output; + }; + + ingame_console con; + + std::int32_t history_index = -1; + std::deque history; + + std::string fixed_input; + std::vector matches; + + float color_white[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + float color_iw6[4] = { 0.0f, 0.7f, 1.0f, 1.0f }; + + void clear() + { + strncpy_s(con.buffer, "", 256); + con.cursor = 0; + + fixed_input = ""; + matches.clear(); + } + + void print(const std::string& data) + { + if (con.visible_line_count > 0 && con.display_line_offset == (con.output.size() - con.visible_line_count)) + { + con.display_line_offset++; + } + + con.output.push_back(data); + + printf("%s\n", data.data()); + + if (con.output.size() > 1024) + { + con.output.pop_front(); + } + } + + void toggle_console() + { + clear(); + + con.output_visible = false; + *game::keyCatchers ^= 1; + } + + void toggle_console_output() + { + con.output_visible = con.output_visible == 0; + } + + void check_resize() + { + con.screen_min[0] = 6.0f; + con.screen_min[1] = 6.0f; + con.screen_max[0] = game::ScrPlace_GetViewPlacement()->realViewportSize[0] - 6.0f; + con.screen_max[1] = game::ScrPlace_GetViewPlacement()->realViewportSize[1] - 6.0f; + + if (console_font) + { + con.font_height = console_font->pixelHeight; + con.visible_line_count = static_cast((con.screen_max[1] - con.screen_min[1] - (con.font_height * 2) + ) - + 24.0f) / con.font_height; + con.visible_pixel_width = static_cast(((con.screen_max[0] - con.screen_min[0]) - 10.0f) - 18.0f); + } + else + { + con.font_height = 0; + con.visible_line_count = 0; + con.visible_pixel_width = 0; + } + } + + void draw_box(const float x, const float y, const float w, const float h, float* color) + { + game::vec4_t dark_color; + + dark_color[0] = color[0] * 0.5f; + dark_color[1] = color[1] * 0.5f; + dark_color[2] = color[2] * 0.5f; + dark_color[3] = color[3]; + + game::R_AddCmdDrawStretchPic(x, y, w, h, 0.0f, 0.0f, 0.0f, 0.0f, color, material_white); + game::R_AddCmdDrawStretchPic(x, y, 2.0f, h, 0.0f, 0.0f, 0.0f, 0.0f, dark_color, material_white); + game::R_AddCmdDrawStretchPic((x + w) - 2.0f, y, 2.0f, h, 0.0f, 0.0f, 0.0f, 0.0f, dark_color, + material_white); + game::R_AddCmdDrawStretchPic(x, y, w, 2.0f, 0.0f, 0.0f, 0.0f, 0.0f, dark_color, material_white); + game::R_AddCmdDrawStretchPic(x, (y + h) - 2.0f, w, 2.0f, 0.0f, 0.0f, 0.0f, 0.0f, dark_color, + material_white); + } + + void draw_input_box(const int lines, float* color) + { + draw_box( + con.globals.x - 6.0f, + con.globals.y - 6.0f, + (con.screen_max[0] - con.screen_min[0]) - ((con.globals.x - 6.0f) - con.screen_min[0]), + (lines * con.globals.font_height) + 12.0f, + color); + } + + void draw_input_text_and_over(const char* str, float* color) + { + game::R_AddCmdDrawText(str, 0x7FFFFFFF, console_font, con.globals.x, + con.globals.y + con.globals.font_height, 1.0f, + 1.0f, 0.0f, color, 0); + con.globals.x = game::R_TextWidth(str, 0, console_font) + con.globals.x + 6.0f; + } + + void draw_hint_box(const int lines, float* color, [[maybe_unused]] float offset_x = 0.0f, + [[maybe_unused]] float offset_y = 0.0f) + { + const auto _h = lines * con.globals.font_height + 12.0f; + const auto _y = con.globals.y - 3.0f + con.globals.font_height + 12.0f; + const auto _w = (con.screen_max[0] - con.screen_min[0]) - ((con.globals.x - 6.0f) - con.screen_min[0]); + + draw_box(con.globals.x - 6.0f, _y, _w, _h, color); + } + + void draw_hint_text(const int line, const char* text, float* color, const float offset = 0.0f) + { + const auto _y = con.globals.font_height + con.globals.y + (con.globals.font_height * (line + 1)) + 15.0f; + + game::R_AddCmdDrawText(text, 0x7FFFFFFF, console_font, con.globals.x + offset, _y, 1.0f, 1.0f, 0.0f, color, 0); + } + + bool match_compare(const std::string& input, const std::string& text, const bool exact) + { + if (exact && text == input) return true; + if (!exact && text.find(input) != std::string::npos) return true; + return false; + } + + void find_matches(std::string input, std::vector& suggestions, const bool exact) + { + input = utils::string::to_lower(input); + + game::cmd_function_s* cmd = (*game::cmd_functions); + while (cmd) + { + if (cmd->name) + { + std::string name = utils::string::to_lower(cmd->name); + + if (match_compare(input, name, exact)) + { + suggestions.push_back(cmd->name); + } + + if (exact && suggestions.size() > 1) + { + return; + } + } + cmd = cmd->next; + } + } + + void draw_input() + { + con.globals.font_height = static_cast(console_font->pixelHeight); + con.globals.x = con.screen_min[0] + 6.0f; + con.globals.y = con.screen_min[1] + 6.0f; + con.globals.left_x = con.screen_min[0] + 6.0f; + + draw_input_box(1, dvars::con_inputBoxColor->current.vector); + draw_input_text_and_over("h1-mod >", color_iw6); + + con.globals.left_x = con.globals.x; + con.globals.auto_complete_choice[0] = 0; + + game::R_AddCmdDrawText(con.buffer, 0x7FFFFFFF, console_font, con.globals.x, + con.globals.y + con.globals.font_height, 1.0f, 1.0f, 0.0f, color_white, 0/*, + con.cursor, '|'*/); + + // check if using a prefixed '/' or not + const auto input = con.buffer[1] && (con.buffer[0] == '/' || con.buffer[0] == '\\') + ? std::string(con.buffer).substr(1) + : std::string(con.buffer); + + if (!input.length()) + { + return; + } + + if (input != fixed_input) + { + matches.clear(); + + if (input.find(" ") != std::string::npos) + { + find_matches(input.substr(0, input.find(" ")), matches, true); + } + else + { + find_matches(input, matches, false); + } + + fixed_input = input; + } + + con.globals.may_auto_complete = false; + if (matches.size() > 24) + { + draw_hint_box(1, dvars::con_inputHintBoxColor->current.vector); + draw_hint_text(0, utils::string::va("%i matches (too many to show here)", matches.size()), + dvars::con_inputDvarMatchColor->current.vector); + } + else if (matches.size() == 1) + { + draw_hint_box(1, dvars::con_inputHintBoxColor->current.vector); + draw_hint_text(0, matches[0].data(), dvars::con_inputCmdMatchColor->current.vector); + + strncpy_s(con.globals.auto_complete_choice, matches[0].data(), 64); + con.globals.may_auto_complete = true; + } + else if (matches.size() > 1) + { + draw_hint_box(static_cast(matches.size()), dvars::con_inputHintBoxColor->current.vector); + + const auto offset = (con.screen_max[0] - con.globals.x) / 2.5f; + + for (size_t i = 0; i < matches.size(); i++) + { + draw_hint_text(static_cast(i), matches[i].data(), dvars::con_inputCmdMatchColor->current.vector); + } + + strncpy_s(con.globals.auto_complete_choice, matches[0].data(), 64); + con.globals.may_auto_complete = true; + } + } + + void draw_output_scrollbar(const float x, float y, const float width, const float height) + { + const auto _x = (x + width) - 10.0f; + draw_box(_x, y, 10.0f, height, dvars::con_outputBarColor->current.vector); + + auto _height = height; + if (con.output.size() > con.visible_line_count) + { + const auto percentage = static_cast(con.visible_line_count) / con.output.size(); + _height *= percentage; + + const auto remainingSpace = height - _height; + const auto percentageAbove = static_cast(con.display_line_offset) / (con.output.size() - con. + visible_line_count); + + y = y + (remainingSpace * percentageAbove); + } + + draw_box(_x, y, 10.0f, _height, dvars::con_outputSliderColor->current.vector); + } + + void draw_output_text(const float x, float y) + { + const auto offset = con.output.size() >= con.visible_line_count + ? 0.0f + : (con.font_height * (con.visible_line_count - con.output.size())); + + for (auto i = 0; i < con.visible_line_count; i++) + { + y = console_font->pixelHeight + y; + + const auto index = i + con.display_line_offset; + if (index >= con.output.size()) + { + break; + } + + game::R_AddCmdDrawText(con.output.at(index).data(), 0x7FFF, console_font, x, y + offset, 1.0f, 1.0f, + 0.0f, color_white, 0); + } + } + + void draw_output_window() + { + draw_box(con.screen_min[0], con.screen_min[1] + 32.0f, con.screen_max[0] - con.screen_min[0], + (con.screen_max[1] - con.screen_min[1]) - 32.0f, dvars::con_outputWindowColor->current.vector); + + const auto x = con.screen_min[0] + 6.0f; + const auto y = (con.screen_min[1] + 32.0f) + 6.0f; + const auto width = (con.screen_max[0] - con.screen_min[0]) - 12.0f; + const auto height = ((con.screen_max[1] - con.screen_min[1]) - 32.0f) - 12.0f; + + game::R_AddCmdDrawText("h1-mod", 0x7FFFFFFF, console_font, x, + ((height - 16.0f) + y) + console_font->pixelHeight, 1.0f, 1.0f, 0.0f, color_iw6, 0); + + draw_output_scrollbar(x, y, width, height); + draw_output_text(x, y); + } + + void draw_console() + { + check_resize(); + + if (*game::keyCatchers & 1) + { + if (!(*game::keyCatchers & 1)) + { + con.output_visible = false; + } + + if (con.output_visible) + { + draw_output_window(); + } + + draw_input(); + } + } + } + + void print(const int type, const char* fmt, ...) + { + char va_buffer[0x200] = { 0 }; + + va_list ap; + va_start(ap, fmt); + vsprintf_s(va_buffer, fmt, ap); + va_end(ap); + + const auto formatted = std::string(va_buffer); + const auto lines = utils::string::split(formatted, '\n'); + + for (auto& line : lines) + { + print(type == con_type_info ? line : "^"s.append(std::to_string(type)).append(line)); + } + } + + bool console_char_event(const int localClientNum, const int key) + { + if (key == game::keyNum_t::K_GRAVE || key == game::keyNum_t::K_TILDE) + { + return false; + } + + if (*game::keyCatchers & 1) + { + if (key == game::keyNum_t::K_TAB) // tab (auto complete) + { + if (con.globals.may_auto_complete) + { + const auto firstChar = con.buffer[0]; + + clear(); + + if (firstChar == '\\' || firstChar == '/') + { + con.buffer[0] = firstChar; + con.buffer[1] = '\0'; + } + + strncat_s(con.buffer, con.globals.auto_complete_choice, 64); + con.cursor = static_cast(std::string(con.buffer).length()); + + if (con.cursor != 254) + { + con.buffer[con.cursor++] = ' '; + con.buffer[con.cursor] = '\0'; + } + } + } + + if (key == 'v' - 'a' + 1) // paste + { + const auto clipboard = utils::string::get_clipboard_data(); + if (clipboard.empty()) + { + return false; + } + + for (auto i = 0; i < clipboard.length(); i++) + { + console_char_event(localClientNum, clipboard[i]); + } + + return false; + } + + if (key == 'c' - 'a' + 1) // clear + { + clear(); + con.line_count = 0; + con.output.clear(); + history_index = -1; + history.clear(); + + return false; + } + + if (key == 'h' - 'a' + 1) // backspace + { + if (con.cursor > 0) + { + memmove(con.buffer + con.cursor - 1, con.buffer + con.cursor, + strlen(con.buffer) + 1 - con.cursor); + con.cursor--; + } + + return false; + } + + if (key < 32) + { + return false; + } + + if (con.cursor == 256 - 1) + { + return false; + } + + memmove(con.buffer + con.cursor + 1, con.buffer + con.cursor, strlen(con.buffer) + 1 - con.cursor); + con.buffer[con.cursor] = static_cast(key); + con.cursor++; + + if (con.cursor == strlen(con.buffer) + 1) + { + con.buffer[con.cursor] = 0; + } + } + + return true; + } + + bool console_key_event(const int localClientNum, const int key, const int down) + { + if (key == game::keyNum_t::K_F10) + { + game::Cmd_ExecuteSingleCommand(localClientNum, 0, "lui_open menu_systemlink_join\n"); + } + + if (key == game::keyNum_t::K_GRAVE || key == game::keyNum_t::K_TILDE) + { + if (!down) + { + return false; + } + + if (game::playerKeys[localClientNum].keys[game::keyNum_t::K_SHIFT].down) + { + if (!(*game::keyCatchers & 1)) + toggle_console(); + + toggle_console_output(); + return false; + } + + toggle_console(); + + return false; + } + + if (*game::keyCatchers & 1) + { + if (down) + { + if (key == game::keyNum_t::K_UPARROW) + { + if (++history_index >= history.size()) + { + history_index = static_cast(history.size()) - 1; + } + + clear(); + + if (history_index != -1) + { + strncpy_s(con.buffer, history.at(history_index).c_str(), 0x100); + con.cursor = static_cast(strlen(con.buffer)); + } + } + else if (key == game::keyNum_t::K_DOWNARROW) + { + if (--history_index < -1) + { + history_index = -1; + } + + clear(); + + if (history_index != -1) + { + strncpy_s(con.buffer, history.at(history_index).c_str(), 0x100); + con.cursor = static_cast(strlen(con.buffer)); + } + } + + if (key == game::keyNum_t::K_RIGHTARROW) + { + if (con.cursor < strlen(con.buffer)) + { + con.cursor++; + } + + return false; + } + + if (key == game::keyNum_t::K_LEFTARROW) + { + if (con.cursor > 0) + { + con.cursor--; + } + + return false; + } + + //scroll through output + if (key == game::keyNum_t::K_MWHEELUP || key == game::keyNum_t::K_PGUP) + { + if (con.output.size() > con.visible_line_count && con.display_line_offset > 0) + { + con.display_line_offset--; + } + } + else if (key == game::keyNum_t::K_MWHEELDOWN || key == game::keyNum_t::K_PGDN) + { + if (con.output.size() > con.visible_line_count && con.display_line_offset < (con.output.size() - + con. + visible_line_count)) + { + con.display_line_offset++; + } + } + + if (key == game::keyNum_t::K_ENTER) + { + game::Cbuf_AddText(0, utils::string::va("%s \n", fixed_input.data())); + + if (history_index != -1) + { + const auto itr = history.begin() + history_index; + + if (*itr == con.buffer) + { + history.erase(history.begin() + history_index); + } + } + + history.push_front(con.buffer); + + print("]"s.append(con.buffer)); + + if (history.size() > 10) + { + history.erase(history.begin() + 10); + } + + history_index = -1; + + clear(); + } + } + } + + return true; + } + + void init() + { + scheduler::loop(draw_console, scheduler::pipeline::renderer); + + con.cursor = 0; + con.visible_line_count = 0; + con.output_visible = false; + con.display_line_offset = 0; + con.line_count = 0; + strncpy_s(con.buffer, "", 256); + + con.globals.x = 0.0f; + con.globals.y = 0.0f; + con.globals.left_x = 0.0f; + con.globals.font_height = 0.0f; + con.globals.may_auto_complete = false; + con.globals.info_line_count = 0; + strncpy_s(con.globals.auto_complete_choice, "", 64); + + // add clear command + command::add("clear", [&]() + { + clear(); + con.line_count = 0; + con.output.clear(); + history_index = -1; + history.clear(); + }); + + char a2[1] = {}; + + // add our dvars + dvars::con_inputBoxColor = game::Dvar_RegisterVec4(game::generateHashValue("con_inputBoxColor"), a2, + 0.2f, 0.2f, 0.2f, 0.9f, + 0.0f, 1.0f, + 1); + + dvars::con_inputHintBoxColor = game::Dvar_RegisterVec4(game::generateHashValue("con_inputHintBoxColor"), a2, + 0.3f, 0.3f, 0.3f, 1.0f, + 0.0f, 1.0f, + 1); + + dvars::con_outputBarColor = game::Dvar_RegisterVec4(game::generateHashValue("con_outputBarColor"), a2, + 0.5f, 0.5f, 0.5f, 0.6f, + 0.0f, 1.0f, + 1); + + dvars::con_outputSliderColor = game::Dvar_RegisterVec4(game::generateHashValue("con_outputSliderColor"), a2, + 0.0f, 0.7f, 1.0f, 1.00f, + 0.0f, 1.0f, + 1); + + dvars::con_outputWindowColor = game::Dvar_RegisterVec4(game::generateHashValue("con_outputWindowColor"), a2, + 0.25f, 0.25f, 0.25f, 0.85f, + 0.0f, 1.0f, + 1); + + dvars::con_inputDvarMatchColor = game::Dvar_RegisterVec4(game::generateHashValue("con_inputDvarMatchColor"), a2, + 1.0f, 1.0f, 0.8f, 1.0f, + 0.0f, 1.0f, + 1); + + dvars::con_inputDvarValueColor = game::Dvar_RegisterVec4(game::generateHashValue("con_inputDvarValueColor"), a2, + 1.0f, 1.0f, 0.8f, 1.0f, + 0.0f, 1.0f, + 1); + + dvars::con_inputDvarInactiveValueColor = game::Dvar_RegisterVec4(game::generateHashValue("con_inputDvarInactiveValueColor"), a2, + 0.8f, 0.8f, 0.8f, 1.0f, + 0.0f, 1.0f, + 1); + + dvars::con_inputCmdMatchColor = game::Dvar_RegisterVec4(game::generateHashValue("con_inputCmdMatchColor"), a2, + 0.80f, 0.80f, 1.0f, 1.0f, + 0.0f, 1.0f, + 1); + + } +} diff --git a/src/component/game_console.hpp b/src/component/game_console.hpp new file mode 100644 index 00000000..873f4e79 --- /dev/null +++ b/src/component/game_console.hpp @@ -0,0 +1,18 @@ +#pragma once + +namespace game_console +{ + enum console_type + { + con_type_error = 1, + con_type_warning = 3, + con_type_info = 7 + }; + + void print(int type, const char* fmt, ...); + + bool console_char_event(int local_client_num, int key); + bool console_key_event(int local_client_num, int key, int down); + + void init(); +} \ No newline at end of file diff --git a/src/component/input.cpp b/src/component/input.cpp new file mode 100644 index 00000000..8c70335a --- /dev/null +++ b/src/component/input.cpp @@ -0,0 +1,42 @@ +#include + +#include "game/game.hpp" + +#include "game_console.hpp" + +#include + +namespace input +{ + namespace + { + utils::hook::detour cl_char_event_hook; + utils::hook::detour cl_key_event_hook; + + void cl_char_event_stub(const int local_client_num, const int key) + { + if (!game_console::console_char_event(local_client_num, key)) + { + return; + } + + cl_char_event_hook.invoke(local_client_num, key); + } + + void cl_key_event_stub(const int local_client_num, const int key, const int down) + { + if (!game_console::console_key_event(local_client_num, key, down)) + { + return; + } + + cl_key_event_hook.invoke(local_client_num, key, down); + } + } + + void init() + { + cl_char_event_hook.create(game::base_address + 0x3D27B0, cl_char_event_stub); + cl_key_event_hook.create(game::base_address + 0x3D2AE0, cl_key_event_stub); + } +} diff --git a/src/component/input.hpp b/src/component/input.hpp new file mode 100644 index 00000000..c187288f --- /dev/null +++ b/src/component/input.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace input +{ + void init(); +} diff --git a/src/component/scheduler.cpp b/src/component/scheduler.cpp new file mode 100644 index 00000000..41c0828f --- /dev/null +++ b/src/component/scheduler.cpp @@ -0,0 +1,151 @@ +#include +#include "scheduler.hpp" +#include "game/game.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 = {}; + }); + }); + } + }; + + volatile bool kill = false; + std::thread thread; + task_pipeline pipelines[pipeline::count]; + utils::hook::detour r_end_frame_hook; + + void execute(const pipeline type) + { + assert(type >= 0 && type < pipeline::count); + pipelines[type].execute(); + } + + void r_end_frame_stub() + { + execute(pipeline::renderer); + r_end_frame_hook.invoke(); + } + + void server_frame_stub() + { + //game::G_Glass_Update(); + execute(pipeline::server); + } + } + + 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); + } + + void init() + { + thread = std::thread([]() + { + while (!kill) + { + execute(pipeline::async); + std::this_thread::sleep_for(10ms); + } + }); + + r_end_frame_hook.create(game::base_address + 0x76D7B0, scheduler::r_end_frame_stub); + //utils::hook::call(0x1402F8879, scheduler::server_frame_stub); + } +} diff --git a/src/component/scheduler.hpp b/src/component/scheduler.hpp new file mode 100644 index 00000000..c129c96f --- /dev/null +++ b/src/component/scheduler.hpp @@ -0,0 +1,33 @@ +#pragma once + +namespace scheduler +{ + enum pipeline + { + // Asynchronuous pipeline, disconnected from the game + async = 0, + + // The game's rendering pipeline + renderer, + + // The game's server thread + server, + + // The game's main thread + main, + + count, + }; + + static const bool cond_continue = false; + static const bool cond_end = true; + + void schedule(const std::function& callback, pipeline type = pipeline::async, + std::chrono::milliseconds delay = 0ms); + void loop(const std::function& callback, pipeline type = pipeline::async, + std::chrono::milliseconds delay = 0ms); + void once(const std::function& callback, pipeline type = pipeline::async, + std::chrono::milliseconds delay = 0ms); + + void init(); +} diff --git a/src/dllmain.cpp b/src/dllmain.cpp new file mode 100644 index 00000000..1dd660e6 --- /dev/null +++ b/src/dllmain.cpp @@ -0,0 +1,48 @@ +#include "stdinc.hpp" + +#pragma warning(disable:4996) + +DWORD WINAPI dwConsole(LPVOID) +{ + AllocConsole(); + AttachConsole(GetCurrentProcessId()); + + freopen("CONIN$", "r", stdin); + freopen("CONOUT$", "w", stdout); + + std::string cmd; + + while (true) + { + std::cout << "\n"; + std::getline(std::cin, cmd); + + game::Cbuf_AddText(0, cmd.data()); + } + + return 0; +} + +void init() +{ + CreateThread(0, 0, dwConsole, 0, 0, 0); + + game::load_base_address(); + + utils::hook::set(game::base_address + 0xBE7F83C, true); // disable bnet popup + + command::init(); + input::init(); + scheduler::init(); + game_console::init(); +} + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) +{ + if (ul_reason_for_call == DLL_PROCESS_ATTACH) + { + init(); + } + + return TRUE; +} \ No newline at end of file diff --git a/src/game/dvars.cpp b/src/game/dvars.cpp new file mode 100644 index 00000000..0f4818ea --- /dev/null +++ b/src/game/dvars.cpp @@ -0,0 +1,136 @@ +#include +#include + +#include "game.hpp" + +namespace dvars +{ + game::dvar_t* con_inputBoxColor = nullptr; + game::dvar_t* con_inputHintBoxColor = nullptr; + game::dvar_t* con_outputBarColor = nullptr; + game::dvar_t* con_outputSliderColor = nullptr; + game::dvar_t* con_outputWindowColor = nullptr; + game::dvar_t* con_inputDvarMatchColor = nullptr; + game::dvar_t* con_inputDvarValueColor = nullptr; + game::dvar_t* con_inputDvarInactiveValueColor = nullptr; + game::dvar_t* con_inputCmdMatchColor = nullptr; + + game::dvar_t* g_playerEjection = nullptr; + game::dvar_t* g_playerCollision = nullptr; + game::dvar_t* g_gravity = nullptr; + game::dvar_t* g_speed = nullptr; + + game::dvar_t* pm_bouncing = nullptr; + + game::dvar_t* jump_slowDownEnable = nullptr; + game::dvar_t* jump_enableFallDamage = nullptr; + game::dvar_t* jump_height = nullptr; + game::dvar_t* jump_ladderPushVel = nullptr; + + game::dvar_t* r_aspectRatioCustom = nullptr; + game::dvar_t* r_fullbright = nullptr; + + game::dvar_t* aimassist_enabled = nullptr; + + std::string dvar_get_vector_domain(const int components, const game::dvar_limits& domain) + { + if (domain.vector.min == -FLT_MAX) + { + if (domain.vector.max == FLT_MAX) + { + return utils::string::va("Domain is any %iD vector", components); + } + else + { + return utils::string::va("Domain is any %iD vector with components %g or smaller", components, + domain.vector.max); + } + } + else if (domain.vector.max == FLT_MAX) + { + return utils::string::va("Domain is any %iD vector with components %g or bigger", components, + domain.vector.min); + } + else + { + return utils::string::va("Domain is any %iD vector with components from %g to %g", components, + domain.vector.min, domain.vector.max); + } + } + + std::string dvar_get_domain(const game::dvar_type type, const game::dvar_limits& domain) + { + std::string str; + + switch (type) + { + case game::dvar_type::boolean: + return "Domain is 0 or 1"s; + + case game::dvar_type::value: + if (domain.value.min == -FLT_MAX) + { + if (domain.value.max == FLT_MAX) + { + return "Domain is any number"s; + } + + return utils::string::va("Domain is any number %g or smaller", domain.value.max); + } + + if (domain.value.max == FLT_MAX) + { + return utils::string::va("Domain is any number %g or bigger", domain.value.min); + } + + return utils::string::va("Domain is any number from %g to %g", domain.value.min, domain.value.max); + + case game::dvar_type::vec2: + return dvar_get_vector_domain(2, domain); + + case game::dvar_type::rgb: + case game::dvar_type::vec3: + return dvar_get_vector_domain(3, domain); + + case game::dvar_type::vec4: + return dvar_get_vector_domain(4, domain); + + case game::dvar_type::integer: + if (domain.enumeration.stringCount == INT_MIN) + { + if (domain.integer.max == INT_MAX) + { + return "Domain is any integer"s; + } + + return utils::string::va("Domain is any integer %i or smaller", domain.integer.max); + } + + if (domain.integer.max == INT_MAX) + { + return utils::string::va("Domain is any integer %i or bigger", domain.integer.min); + } + + return utils::string::va("Domain is any integer from %i to %i", domain.integer.min, domain.integer.max); + + case game::dvar_type::color: + return "Domain is any 4-component color, in RGBA format"s; + + case game::dvar_type::enumeration: + str = "Domain is one of the following:"s; + + for (auto string_index = 0; string_index < domain.enumeration.stringCount; ++string_index) + { + str += utils::string::va("\n %2i: %s", string_index, domain.enumeration.strings[string_index]); + } + + return str; + + case game::dvar_type::string: + return "Domain is any text"s; + + default: + return utils::string::va("unhandled dvar type '%i'", type); + } + } +} diff --git a/src/game/dvars.hpp b/src/game/dvars.hpp new file mode 100644 index 00000000..73883bdb --- /dev/null +++ b/src/game/dvars.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "game.hpp" +#include "structs.hpp" +#include + +namespace dvars +{ + extern game::dvar_t* con_inputBoxColor; + extern game::dvar_t* con_inputHintBoxColor; + extern game::dvar_t* con_outputBarColor; + extern game::dvar_t* con_outputSliderColor; + extern game::dvar_t* con_outputWindowColor; + extern game::dvar_t* con_inputDvarMatchColor; + extern game::dvar_t* con_inputDvarValueColor; + extern game::dvar_t* con_inputDvarInactiveValueColor; + extern game::dvar_t* con_inputCmdMatchColor; + + extern game::dvar_t* g_playerCollision; + extern game::dvar_t* g_playerEjection; + extern game::dvar_t* g_gravity; + extern game::dvar_t* g_speed; + + extern game::dvar_t* pm_bouncing; + + extern game::dvar_t* r_aspectRatioCustom; + extern game::dvar_t* jump_slowDownEnable; + extern game::dvar_t* jump_enableFallDamage; + extern game::dvar_t* jump_height; + extern game::dvar_t* jump_ladderPushVel; + + extern game::dvar_t* r_fullbright; + + extern game::dvar_t* aimassist_enabled; + + std::string dvar_get_vector_domain(const int components, const game::dvar_limits& domain); + std::string dvar_get_domain(const game::dvar_type type, const game::dvar_limits& domain); +} diff --git a/src/game/game.cpp b/src/game/game.cpp new file mode 100644 index 00000000..4c39ee27 --- /dev/null +++ b/src/game/game.cpp @@ -0,0 +1,12 @@ +#include "stdinc.hpp" + +namespace game +{ + uint64_t base_address; + + void load_base_address() + { + const auto module = GetModuleHandle(NULL); + base_address = uint64_t(module); + } +} \ No newline at end of file diff --git a/src/game/game.hpp b/src/game/game.hpp new file mode 100644 index 00000000..a880afed --- /dev/null +++ b/src/game/game.hpp @@ -0,0 +1,38 @@ +#pragma once + +namespace game +{ + extern uint64_t base_address; + + void load_base_address(); + + template + class symbol + { + public: + symbol(const size_t address) + : address_(reinterpret_cast(address)) + { + } + + T* get() const + { + return reinterpret_cast((uint64_t)address_ + base_address); + } + + operator T* () const + { + return this->get(); + } + + T* operator->() const + { + return this->get(); + } + + private: + T* address_; + }; +} + +#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 00000000..5200539f --- /dev/null +++ b/src/game/structs.hpp @@ -0,0 +1,531 @@ +#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]; + + struct gclient_s + { + char __pad0[7684]; + int flags; + }; + + struct EntityState + { + char entityNum; + }; + + struct gentity_s + { + EntityState s; + }; + + struct Material + { + const char* name; + }; + + struct Glyph + { + unsigned short letter; + char x0; + char y0; + char dx; + char pixelWidth; + char pixelHeight; + float s0; + float t0; + float s1; + float t1; + }; + + struct Font_s + { + const char* fontName; + int pixelHeight; + int glyphCount; + Material* material; + Material* glowMaterial; + Glyph* glyphs; + }; + + enum keyNum_t + { + K_NONE = 0x0, + K_FIRSTGAMEPADBUTTON_RANGE_1 = 0x1, + K_BUTTON_A = 0x1, + K_BUTTON_B = 0x2, + K_BUTTON_X = 0x3, + K_BUTTON_Y = 0x4, + K_BUTTON_LSHLDR = 0x5, + K_BUTTON_RSHLDR = 0x6, + K_LASTGAMEPADBUTTON_RANGE_1 = 0x6, + K_BS = 0x8, + K_TAB = 0x9, + K_ENTER = 0xD, + K_FIRSTGAMEPADBUTTON_RANGE_2 = 0xE, + K_BUTTON_START = 0xE, + K_BUTTON_BACK = 0xF, + K_BUTTON_LSTICK = 0x10, + K_BUTTON_RSTICK = 0x11, + K_BUTTON_LTRIG = 0x12, + K_BUTTON_RTRIG = 0x13, + K_DPAD_UP = 0x14, + K_FIRSTDPAD = 0x14, + K_DPAD_DOWN = 0x15, + K_DPAD_LEFT = 0x16, + K_DPAD_RIGHT = 0x17, + K_BUTTON_LSTICK_ALTIMAGE2 = 0x10, + K_BUTTON_RSTICK_ALTIMAGE2 = 0x11, + K_BUTTON_LSTICK_ALTIMAGE = 0xBC, + K_BUTTON_RSTICK_ALTIMAGE = 0xBD, + K_LASTDPAD = 0x17, + K_LASTGAMEPADBUTTON_RANGE_2 = 0x17, + K_ESCAPE = 0x1B, + K_FIRSTGAMEPADBUTTON_RANGE_3 = 0x1C, + K_APAD_UP = 0x1C, + K_FIRSTAPAD = 0x1C, + K_APAD_DOWN = 0x1D, + K_APAD_LEFT = 0x1E, + K_APAD_RIGHT = 0x1F, + K_LASTAPAD = 0x1F, + K_LASTGAMEPADBUTTON_RANGE_3 = 0x1F, + K_SPACE = 0x20, + K_GRAVE = 0x60, + K_TILDE = 0x7E, + K_BACKSPACE = 0x7F, + K_ASCII_FIRST = 0x80, + K_ASCII_181 = 0x80, + K_ASCII_191 = 0x81, + K_ASCII_223 = 0x82, + K_ASCII_224 = 0x83, + K_ASCII_225 = 0x84, + K_ASCII_228 = 0x85, + K_ASCII_229 = 0x86, + K_ASCII_230 = 0x87, + K_ASCII_231 = 0x88, + K_ASCII_232 = 0x89, + K_ASCII_233 = 0x8A, + K_ASCII_236 = 0x8B, + K_ASCII_241 = 0x8C, + K_ASCII_242 = 0x8D, + K_ASCII_243 = 0x8E, + K_ASCII_246 = 0x8F, + K_ASCII_248 = 0x90, + K_ASCII_249 = 0x91, + K_ASCII_250 = 0x92, + K_ASCII_252 = 0x93, + K_END_ASCII_CHARS = 0x94, + K_COMMAND = 0x96, + K_CAPSLOCK = 0x97, + K_POWER = 0x98, + K_PAUSE = 0x99, + K_UPARROW = 0x9A, + K_DOWNARROW = 0x9B, + K_LEFTARROW = 0x9C, + K_RIGHTARROW = 0x9D, + K_ALT = 0x9E, + K_CTRL = 0x9F, + K_SHIFT = 0xA0, + K_INS = 0xA1, + K_DEL = 0xA2, + K_PGDN = 0xA3, + K_PGUP = 0xA4, + K_HOME = 0xA5, + K_END = 0xA6, + K_F1 = 0xA7, + K_F2 = 0xA8, + K_F3 = 0xA9, + K_F4 = 0xAA, + K_F5 = 0xAB, + K_F6 = 0xAC, + K_F7 = 0xAD, + K_F8 = 0xAE, + K_F9 = 0xAF, + K_F10 = 0xB0, + K_F11 = 0xB1, + K_F12 = 0xB2, + K_F13 = 0xB3, + K_F14 = 0xB4, + K_F15 = 0xB5, + K_KP_HOME = 0xB6, + K_KP_UPARROW = 0xB7, + K_KP_PGUP = 0xB8, + K_KP_LEFTARROW = 0xB9, + K_KP_5 = 0xBA, + K_KP_RIGHTARROW = 0xBB, + K_KP_END = 0xBC, + K_KP_DOWNARROW = 0xBD, + K_KP_PGDN = 0xBE, + K_KP_ENTER = 0xBF, + K_KP_INS = 0xC0, + K_KP_DEL = 0xC1, + K_KP_SLASH = 0xC2, + K_KP_MINUS = 0xC3, + K_KP_PLUS = 0xC4, + K_KP_NUMLOCK = 0xC5, + K_KP_STAR = 0xC6, + K_KP_EQUALS = 0xC7, + K_MOUSE1 = 0xC8, + K_MOUSE2 = 0xC9, + K_MOUSE3 = 0xCA, + K_MOUSE4 = 0xCB, + K_MOUSE5 = 0xCC, + K_MWHEELDOWN = 0xCD, + K_MWHEELUP = 0xCE, + K_AUX1 = 0xCF, + K_AUX2 = 0xD0, + K_AUX3 = 0xD1, + K_AUX4 = 0xD2, + K_AUX5 = 0xD3, + K_AUX6 = 0xD4, + K_AUX7 = 0xD5, + K_AUX8 = 0xD6, + K_AUX9 = 0xD7, + K_AUX10 = 0xD8, + K_AUX11 = 0xD9, + K_AUX12 = 0xDA, + K_AUX13 = 0xDB, + K_AUX14 = 0xDC, + K_AUX15 = 0xDD, + K_AUX16 = 0xDE, + K_LAST_KEY = 0xDF + }; + + struct KeyState + { + int down; + int repeats; + int binding; + }; + + struct PlayerKeyState + { + int overstrikeMode; + int anyKeyDown; + KeyState keys[256]; + }; + + enum DvarSetSource : std::uint32_t + { + DVAR_SOURCE_INTERNAL = 0x0, + DVAR_SOURCE_EXTERNAL = 0x1, + DVAR_SOURCE_SCRIPT = 0x2, + DVAR_SOURCE_UISCRIPT = 0x3, + DVAR_SOURCE_SERVERCMD = 0x4, + DVAR_SOURCE_NUM = 0x5, + }; + + enum DvarFlags : std::uint32_t + { + DVAR_FLAG_NONE = 0, + DVAR_FLAG_SAVED = 0x1, + DVAR_FLAG_LATCHED = 0x2, + DVAR_FLAG_CHEAT = 0x4, + DVAR_FLAG_REPLICATED = 0x8, + DVAR_FLAG_WRITE = 0x800, + DVAR_FLAG_READ = 0x2000, + }; + + enum dvar_type : std::int8_t + { + boolean = 0, + value = 1, + vec2 = 2, + vec3 = 3, + vec4 = 4, + integer = 5, + enumeration = 6, + string = 7, + color = 8, + rgb = 9 // Color without alpha + }; + + union dvar_value + { + bool enabled; + int integer; + unsigned int unsignedInt; + float value; + float vector[4]; + 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 dvar_limits + { + $A37BA207B3DDD6345C554D4661813EDD enumeration; + $9CA192F9DB66A3CB7E01DE78A0DEA53D integer; + $251C2428A496074035CACA7AAF3D55BD value; + $251C2428A496074035CACA7AAF3D55BD vector; + }; + + struct dvar_t + { + const char* name; //00 + unsigned int flags; //08 + dvar_type type; //0C + bool modified; //0D + dvar_value current; //10 + dvar_value latched; + dvar_value reset; + dvar_limits domain; + }; + + struct ScreenPlacement + { + vec2_t scaleVirtualToReal; + vec2_t scaleVirtualToFull; + vec2_t scaleRealToVirtual; + vec2_t realViewportPosition; + vec2_t realViewportSize; + vec2_t virtualViewableMin; + vec2_t virtualViewableMax; + vec2_t realViewableMin; + vec2_t realViewableMax; + vec2_t virtualAdjustableMin; + vec2_t virtualAdjustableMax; + vec2_t realAdjustableMin; + vec2_t realAdjustableMax; + vec2_t subScreenLeft; + }; + + struct CmdArgs + { + int nesting; + int localClientNum[8]; + int controllerIndex[8]; + int argc[8]; + const char** argv[8]; + }; + + struct cmd_function_s + { + cmd_function_s* next; + const char* name; + void(__cdecl* function)(); + }; + + enum XAssetType + { + ASSET_TYPE_PHYSPRESET, + ASSET_TYPE_PHYSCOLLMAP, + ASSET_TYPE_PHYSWATERPRESET, + ASSET_TYPE_PHYSWORLDMAP, + ASSET_TYPE_PHYSCONSTRAINT, + ASSET_TYPE_XANIMPARTS, + ASSET_TYPE_XMODELSURFS, + ASSET_TYPE_XMODEL, + ASSET_TYPE_MATERIAL, + ASSET_TYPE_COMPUTESHADER, + ASSET_TYPE_VERTEXSHADER, + ASSET_TYPE_HULLSHADER, + ASSET_TYPE_DOMAINSHADER, + ASSET_TYPE_PIXELSHADER, + ASSET_TYPE_VERTEXDECL, + ASSET_TYPE_TECHNIQUE_SET, + ASSET_TYPE_IMAGE, + ASSET_TYPE_SOUND, + ASSET_TYPE_SOUND_SUBMIX, + ASSET_TYPE_SOUND_CURVE, + ASSET_TYPE_LPF_CURVE, + ASSET_TYPE_REVERB_CURVE, + ASSET_TYPE_SOUND_CONTEXT, + ASSET_TYPE_LOADED_SOUND, + ASSET_TYPE_CLIPMAP, + ASSET_TYPE_COMWORLD, + ASSET_TYPE_GLASSWORLD, + ASSET_TYPE_PATHDATA, + ASSET_TYPE_VEHICLE_TRACK, + ASSET_TYPE_MAP_ENTS, + ASSET_TYPE_FXWORLD, + ASSET_TYPE_GFXWORLD, + ASSET_TYPE_LIGHT_DEF, + ASSET_TYPE_UI_MAP, + ASSET_TYPE_FONT, + ASSET_TYPE_MENULIST, + ASSET_TYPE_MENU, + ASSET_TYPE_ANIMCLASS, + ASSET_TYPE_LOCALIZE_ENTRY, + ASSET_TYPE_ATTACHMENT, + ASSET_TYPE_WEAPON, + ASSET_TYPE_SNDDRIVER_GLOBALS, + ASSET_TYPE_FX, + ASSET_TYPE_IMPACT_FX, + ASSET_TYPE_SURFACE_FX, + ASSET_TYPE_AITYPE, + ASSET_TYPE_MPTYPE, + ASSET_TYPE_CHARACTER, + ASSET_TYPE_XMODELALIAS, + ASSET_TYPE_RAWFILE, + ASSET_TYPE_SCRIPTFILE, + ASSET_TYPE_STRINGTABLE, + ASSET_TYPE_LEADERBOARD, + ASSET_TYPE_STRUCTURED_DATA_DEF, + ASSET_TYPE_TRACER, + ASSET_TYPE_VEHICLE, + ASSET_TYPE_ADDON_MAP_ENTS, + ASSET_TYPE_NET_CONST_STRINGS, + ASSET_TYPE_REVERB_PRESET, + ASSET_TYPE_LUA_FILE, + ASSET_TYPE_SCRIPTABLE, + ASSET_TYPE_EQUIPMENT_SND_TABLE, + ASSET_TYPE_VECTORFIELD, + ASSET_TYPE_DOPPLER_PRESET, + ASSET_TYPE_PARTICLE_SIM_ANIMATION, + ASSET_TYPE_LASER, + ASSET_TYPE_SKELETON_SCRIPT, + ASSET_TYPE_CLUT, + ASSET_TYPE_COUNT, + }; + + struct StreamFileNameRaw + { + const char* dir; + const char* name; + }; + + struct StreamFileNamePacked + { + unsigned __int64 offset; + unsigned __int64 length; + }; + + union StreamFileInfo + { + StreamFileNameRaw raw; + StreamFileNamePacked packed; + }; + + struct StreamFileName + { + unsigned __int16 isLocalized; + unsigned __int16 fileIndex; + StreamFileInfo info; + }; + + struct StreamedSound + { + StreamFileName filename; + unsigned int totalMsec; + }; + + union SoundFileRef + { + StreamedSound streamSnd; + }; + + struct SoundFile + { + char type; + char exists; + SoundFileRef u; + }; + + struct snd_alias_t + { + const char* aliasName; + char __pad0[24]; + SoundFile* soundFile; + char __pad1[198]; + // not gonna map this out... + }; + + struct snd_alias_list_t + { + const char* aliasName; + snd_alias_t* head; + void* unk; + unsigned char count; + unsigned char unkCount; + char __pad0[6]; + }; + + struct RawFile + { + const char* name; + int compressedLen; + int len; + const char* buffer; + }; + + struct ScriptFile + { + const char* name; + int compressedLen; + int len; + int bytecodeLen; + const char* buffer; + char* bytecode; + }; + + struct StringTableCell + { + const char* string; + int hash; + }; + + struct StringTable + { + const char* name; + int columnCount; + int rowCount; + StringTableCell* values; + }; + + struct LuaFile + { + const char* name; + int len; + char strippingType; + const char* buffer; + }; + + union XAssetHeader + { + void* data; + Material* material; + Font_s* font; + RawFile* rawfile; + ScriptFile* scriptfile; + StringTable* stringTable; + LuaFile* luaFile; + }; + + struct XAsset + { + XAssetType type; + XAssetHeader header; + }; + + struct XAssetEntry + { + XAsset asset; + char zoneIndex; + volatile char inuseMask; + unsigned int nextHash; + unsigned int nextOverride; + unsigned int nextPoolEntry; + }; +} \ No newline at end of file diff --git a/src/game/symbols.hpp b/src/game/symbols.hpp new file mode 100644 index 00000000..b7592d0c --- /dev/null +++ b/src/game/symbols.hpp @@ -0,0 +1,58 @@ +#pragma once + +#define WEAK __declspec(selectany) + +namespace game +{ + // Functions + + WEAK symbol Cbuf_AddText{0x59A050}; + + WEAK symbol Cmd_AddCommandInternal{0x59A5F0}; + WEAK symbol Cmd_ExecuteSingleCommand{0x59ABA0}; + + WEAK symbol + DB_EnumXAssets_Internal{0x4129F0}; + WEAK symbol DB_GetXAssetName{0x3E4090}; + + WEAK symbol Dvar_FindVar{0x618F90}; + WEAK symbol Dvar_GetCombinedString{0x5A75D0}; + WEAK symbol + Dvar_RegisterBool{0x617BB0}; + WEAK symbol Dvar_RegisterVec4{0x6185F0}; + WEAK symbol Dvar_ValueToString{0x61B8F0}; + + WEAK symbol generateHashValue{0x343D20}; + + WEAK symbol Material_RegisterHandle{0x759BA0}; + + WEAK symbol R_AddCmdDrawStretchPic{0x3C9710}; + WEAK symbol R_AddCmdDrawText{0x76C660}; + WEAK symbol + R_AddCmdDrawTextWithCursor{0x769D90}; + WEAK symbol R_RegisterFont{0x746FE0}; + WEAK symbol R_TextWidth{0x7472A0}; + + WEAK symbol ScrPlace_GetViewPlacement{0x3E16A0}; + + WEAK symbol Sys_ShowConsole{0x633080}; + + // Variables + + WEAK symbol cmd_functions{0xAD17BB8}; + WEAK symbol cmd_args{0xAD17A60}; + + WEAK symbol g_assetNames{0xBEF280}; + + WEAK symbol g_entities{0x52DDEC0}; + + WEAK symbol keyCatchers{0x203F3C0}; + + WEAK symbol playerKeys{0x1E8767C}; + + WEAK symbol dvarCount{0xBFBB310}; + WEAK symbol sortedDvars{0xBFBB320}; +} diff --git a/src/stdinc.cpp b/src/stdinc.cpp new file mode 100644 index 00000000..ea7961be --- /dev/null +++ b/src/stdinc.cpp @@ -0,0 +1 @@ +#include "stdinc.hpp" \ No newline at end of file diff --git a/src/stdinc.hpp b/src/stdinc.hpp new file mode 100644 index 00000000..8e86df63 --- /dev/null +++ b/src/stdinc.hpp @@ -0,0 +1,38 @@ +#pragma once + +#pragma warning(disable: 4244) +#pragma warning(disable: 26812) + +#define WIN32_LEAN_AND_MEAN +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace std::literals; + +#include "utils/memory.hpp" +#include "utils/string.hpp" +#include "utils/hook.hpp" +#include "utils/string.hpp" + +#include "game/structs.hpp" +#include "game/game.hpp" + +#include "component/command.hpp" +#include "component/scheduler.hpp" +#include "component/input.hpp" +#include "component/game_console.hpp" diff --git a/src/utils/concurrency.hpp b/src/utils/concurrency.hpp new file mode 100644 index 00000000..05c5d3ad --- /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/hook.cpp b/src/utils/hook.cpp new file mode 100644 index 00000000..f570731b --- /dev/null +++ b/src/utils/hook.cpp @@ -0,0 +1,180 @@ +#include "stdinc.hpp" +#include "hook.hpp" +#include "string.hpp" + +namespace utils::hook +{ + namespace + { + [[maybe_unused]] class _ + { + public: + _() + { + if (MH_Initialize() != MH_OK) + { + throw std::runtime_error("Failed to initialize MinHook"); + } + } + + ~_() + { + MH_Uninitialize(); + } + } __; + } + + 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 jump(void* pointer, void* data, const bool use_far) + { + static const unsigned char jump_data[] = { + 0x48, 0xb8, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0xff, 0xe0 + }; + + if (!use_far && is_relatively_far(pointer, data)) + { + throw std::runtime_error("Too far away to create 32bit relative branch"); + } + + auto* patch_pointer = PBYTE(pointer); + + if (use_far) + { + copy(patch_pointer, jump_data, sizeof(jump_data)); + copy(patch_pointer + 2, &data, sizeof(data)); + } + else + { + set(patch_pointer, 0xE9); + set(patch_pointer + 1, int32_t(size_t(data) - (size_t(pointer) + 5))); + } + } + + void jump(const size_t pointer, void* data, const bool use_far) + { + return jump(reinterpret_cast(pointer), data, use_far); + } + + void jump(const size_t pointer, const size_t data, const bool use_far) + { + return jump(pointer, reinterpret_cast(data), use_far); + } +} diff --git a/src/utils/hook.hpp b/src/utils/hook.hpp new file mode 100644 index 00000000..90977996 --- /dev/null +++ b/src/utils/hook.hpp @@ -0,0 +1,121 @@ +#pragma once + +namespace utils::hook +{ + 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(void* pointer, void* data, bool use_far = false); + void jump(size_t pointer, void* data, bool use_far = false); + void jump(size_t pointer, size_t data, bool use_far = false); + + void inject(void* pointer, const void* data); + void inject(size_t pointer, const void* data); + + template + T extract(void* address) + { + const auto data = static_cast(address); + const auto offset = *reinterpret_cast(data); + return reinterpret_cast(data + offset + 4); + } + + void* follow_branch(void* address); + + 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/memory.cpp b/src/utils/memory.cpp new file mode 100644 index 00000000..3abe1a1c --- /dev/null +++ b/src/utils/memory.cpp @@ -0,0 +1,112 @@ +// https://github.com/momo5502/open-iw5 + +#include "stdinc.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) + { + const auto data = calloc(length, 1); + assert(data != nullptr); + return data; + } + + /*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_; + } +} diff --git a/src/utils/memory.hpp b/src/utils/memory.hpp new file mode 100644 index 00000000..bcc9d965 --- /dev/null +++ b/src/utils/memory.hpp @@ -0,0 +1,70 @@ +// https://github.com/momo5502/open-iw5 + +#pragma once + +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 + T* allocate() + { + return this->allocate_array(1); + } + + template + 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 allocator* get_allocator(); + + private: + static allocator mem_allocator_; + }; +} diff --git a/src/utils/string.cpp b/src/utils/string.cpp new file mode 100644 index 00000000..934a9dba --- /dev/null +++ b/src/utils/string.cpp @@ -0,0 +1,124 @@ +// iw6x-client + +#include "stdinc.hpp" + +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 CHAR(tolower(input)); + }); + + return text; + } + + std::string to_upper(std::string text) + { + std::transform(text.begin(), text.end(), text.begin(), [](const char input) + { + return CHAR(toupper(input)); + }); + + return text; + } + + bool starts_with(const std::string& text, const std::string& substring) + { + return text.find(substring) == 0; + } + + 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; + } + + std::string get_clipboard_data() + { + if (OpenClipboard(0)) + { + std::string data; + + const auto clipboard_data = GetClipboardData(1u); + if (clipboard_data) + { + const auto cliptext = static_cast(GlobalLock(clipboard_data)); + if (cliptext) + { + data.append(cliptext); + GlobalUnlock(clipboard_data); + } + } + CloseClipboard(); + + return data; + } + return {}; + } + + 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'; + } +} diff --git a/src/utils/string.hpp b/src/utils/string.hpp new file mode 100644 index 00000000..2a3cbdaf --- /dev/null +++ b/src/utils/string.hpp @@ -0,0 +1,88 @@ +#pragma once +#include "stdinc.hpp" + +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); + + std::string dump_hex(const std::string& data, const std::string& separator = " "); + + std::string get_clipboard_data(); + + void strip(const char* in, char* out, int max); +} diff --git a/tools/windows/premake5.exe b/tools/windows/premake5.exe new file mode 100644 index 00000000..9048d51e Binary files /dev/null and b/tools/windows/premake5.exe differ