This commit is contained in:
6arelyFuture 2021-12-29 16:40:44 +01:00
commit c8b84a9b6a
Signed by: Future
GPG Key ID: FA77F074E98D98A5
45 changed files with 3311 additions and 0 deletions

7
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,7 @@
version: 2
updates:
- package-ecosystem: gitsubmodule
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10

153
.gitignore vendored Normal file
View File

@ -0,0 +1,153 @@
### 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/
#Visual Studio Code
.vscode/
# 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

9
.gitmodules vendored Normal file
View File

@ -0,0 +1,9 @@
[submodule "deps/GSL"]
path = deps/GSL
url = https://github.com/microsoft/GSL.git
[submodule "deps/minhook"]
path = deps/minhook
url = https://github.com/TsudaKageyu/minhook.git
[submodule "deps/rapidjson"]
path = deps/rapidjson
url = https://github.com/Tencent/rapidjson.git

29
LICENSE Normal file
View File

@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2021, Edoardo Sanguineti
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

15
README.md Normal file
View File

@ -0,0 +1,15 @@
# Black-Ops-Plugin
I have no clue as to what this does. I use this program to test experimental features.
## Thanks to
*[momo5502](https://github.com/momo5502)
## Disclaimer
This software has been created purely for the purposes of academic research. It is not intended to be used to attack other systems. Project maintainers are not responsible or liable for misuse of the software. Use responsibly.
## Compile from source
- Clone the Git repo. Do NOT download it as ZIP, that won't work.
- Update the submodules and run `premake5 vs2019` or simply use the delivered `generate.bat`.
- Build via solution file found inside the build folder.

1
deps/GSL vendored Submodule

@ -0,0 +1 @@
Subproject commit c412deb31e73c9b824abeb6619e11511b279222f

1
deps/minhook vendored Submodule

@ -0,0 +1 @@
Subproject commit 4a455528f61b5a375b1f9d44e7d296d47f18bb18

19
deps/premake/gsl.lua vendored Normal file
View File

@ -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)

31
deps/premake/minhook.lua vendored Normal file
View File

@ -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)

19
deps/premake/rapidjson.lua vendored Normal file
View File

@ -0,0 +1,19 @@
rapidjson = {
source = path.join(dependencies.basePath, "rapidjson"),
}
function rapidjson.import()
rapidjson.includes()
end
function rapidjson.includes()
includedirs {
path.join(rapidjson.source, "include"),
}
end
function rapidjson.project()
end
table.insert(dependencies, rapidjson)

1
deps/rapidjson vendored Submodule

@ -0,0 +1 @@
Subproject commit fd3dc29a5c2852df569e1ea81dbde2c412ac5051

4
generate.bat Normal file
View File

@ -0,0 +1,4 @@
@echo off
echo Updating submodules...
call git submodule update --init --recursive
tools\premake5 %* vs2019

94
premake5.lua Normal file
View File

@ -0,0 +1,94 @@
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
newoption {
trigger = "copy-to",
description = "Optional, copy the EXE to a custom folder after build, define the path here if wanted.",
value = "PATH"
}
dependencies.load()
workspace "black-ops-plugin"
location "./build"
objdir "%{wks.location}/obj"
targetdir "%{wks.location}/bin/%{cfg.platform}/%{cfg.buildcfg}"
targetname "%{prj.name}"
configurations {"Debug", "Release"}
language "C++"
architecture "x86"
platforms "x86"
systemversion "latest"
symbols "On"
staticruntime "On"
editandcontinue "Off"
warnings "Extra"
characterset "ASCII"
flags { "NoIncrementalLink", "NoMinimalRebuild", "MultiProcessorCompile", "No64BitChecks" }
filter "windows"
defines {"_WINDOWS", "WIN32"}
filter "Release"
optimize "Size"
buildoptions {"/GL"}
linkoptions { "/IGNORE:4702", "/LTCG" }
defines {"NDEBUG"}
flags {"FatalCompileWarnings"}
filter "Debug"
optimize "Debug"
defines {"DEBUG", "_DEBUG"}
filter {}
project "black-ops-plugin"
kind "StaticLib"
language "C++"
files {"./src/**.hpp", "./src/**.cpp"}
includedirs {"src"}
dependencies.imports()
group "Dependencies"
dependencies.projects()
workspace "*"
cppdialect "C++20"

17
src/component/chat.cpp Normal file
View File

@ -0,0 +1,17 @@
#include <stdinc.hpp>
#include "loader/component_loader.hpp"
namespace chat
{
class component final : public component_interface
{
public:
void post_unpack() override
{
}
};
}
REGISTER_COMPONENT(chat::component)

163
src/component/command.cpp Normal file
View File

@ -0,0 +1,163 @@
#include <stdinc.hpp>
#include "loader/component_loader.hpp"
#include "utils/string.hpp"
#include "utils/nt.hpp"
#include "utils/io.hpp"
#include "command.hpp"
namespace command
{
std::unordered_map<std::string, std::function<void(params&)>> handlers;
void main_handler()
{
params params = {};
const auto command = utils::string::to_lower(params[0]);
if (handlers.find(command) != handlers.end())
{
handlers[command](params);
}
}
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<game::cmd_function_t>());
}
void add(const char* name, const std::function<void(const params&)>& callback)
{
const auto command = utils::string::to_lower(name);
if (handlers.find(command) == handlers.end())
{
add_raw(name, main_handler);
}
handlers[command] = callback;
}
std::vector<std::string> script_commands;
utils::memory::allocator allocator;
void add_script_command(const std::string& name, const std::function<void(const params&)>& callback)
{
script_commands.push_back(name);
const auto _name = allocator.duplicate_string(name);
add(_name, callback);
}
void clear_script_commands()
{
for (const auto& name : script_commands)
{
handlers.erase(name);
game::Cmd_RemoveCommand(name.data());
}
allocator.clear();
script_commands.clear();
}
void execute(std::string command, const bool sync)
{
command += "\n";
if (sync)
{
game::Cmd_ExecuteSingleCommand(game::LOCAL_CLIENT_0, 0, command.data());
}
else
{
game::Cbuf_AddText(game::LOCAL_CLIENT_0, command.data());
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
add_commands_generic();
}
void pre_destroy() override
{
clear_script_commands();
}
private:
static void add_commands_generic()
{
add("properQuit", [](const params&)
{
utils::nt::raise_hard_exception();
});
add("dvarDump", [](const params& params)
{
if (params.size() < 2)
{
return;
}
std::string filename = "dump/";
filename.append(params.get(1));
if (!filename.ends_with(".txt"))
{
filename.append(".txt");
}
for (auto i = 0; i < *game::dvarCount; i++)
{
const auto dvar = game::sortedDvars[i];
if (dvar)
{
const auto line = std::format("{} \"{}\"\r\n", dvar->name,
game::Dvar_DisplayableValue(dvar));
utils::io::write_file(filename, line, i != 0);
}
}
game::Com_Printf(game::CON_CHANNEL_SERVER, "%i dvars\n", *game::dvarCount);
});
}
};
}
REGISTER_COMPONENT(command::component)

30
src/component/command.hpp Normal file
View File

@ -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<void(const params&)>& callback);
void add_script_command(const std::string& name, const std::function<void(const params&)>& callback);
void clear_script_commands();
void execute(std::string command, bool sync = false);
}

View File

@ -0,0 +1,17 @@
#include <stdinc.hpp>
#include "loader/component_loader.hpp"
namespace gameplay
{
class component final : public component_interface
{
public:
void post_unpack() override
{
}
};
}
REGISTER_COMPONENT(gameplay::component)

21
src/game/game.cpp Normal file
View File

@ -0,0 +1,21 @@
#include <stdinc.hpp>
namespace game
{
gamemode current = reinterpret_cast<const char*>(0xA6840C) == "multiplayer"s
? gamemode::multiplayer
: gamemode::zombies;
namespace environment
{
bool t6mp()
{
return current == gamemode::multiplayer;
}
bool t6zm()
{
return current == gamemode::zombies;
}
}
}

59
src/game/game.hpp Normal file
View File

@ -0,0 +1,59 @@
#pragma once
#define SELECT(mp, zm) (game::environment::t6mp() ? mp : zm)
namespace game
{
enum gamemode
{
none,
multiplayer,
zombies
};
extern gamemode current;
namespace environment
{
bool t5mp();
bool t5zm();
}
template <typename T>
class symbol
{
public:
symbol(const size_t t5mp, const size_t t5zm)
: t5mp_(reinterpret_cast<T*>(t5mp))
, t5zm_(reinterpret_cast<T*>(t5zm))
{
}
T* get() const
{
if (environment::t5mp())
{
return t5mp_;
}
return t5zm_;
}
operator T* () const
{
return this->get();
}
T* operator->() const
{
return this->get();
}
private:
T* t5mp_;
T* t5zm_;
};
}
#include "symbols.hpp"

527
src/game/structs.hpp Normal file
View File

@ -0,0 +1,527 @@
#pragma once
namespace game
{
typedef float vec_t;
typedef vec_t vec2_t[2];
typedef vec_t vec3_t[3];
typedef vec_t vec4_t[4];
enum FsListBehavior_e
{
FS_LIST_PURE_ONLY = 0x0,
FS_LIST_ALL = 0x1
};
enum svscmd_type
{
SV_CMD_CAN_IGNORE,
SV_CMD_RELIABLE
};
typedef enum
{
ERR_FATAL = 0x0,
ERR_DROP = 0x1,
ERR_SERVERDISCONNECT = 0x2,
ERR_DISCONNECT = 0x3,
ERR_SCRIPT = 0x4,
ERR_SCRIPT_DROP = 0x5,
ERR_LOCALIZATION = 0x6
} errorParm_t;
typedef enum
{
LOCAL_CLIENT_INVALID = -1,
LOCAL_CLIENT_0 = 0,
LOCAL_CLIENT_1 = 1,
LOCAL_CLIENT_2 = 2,
LOCAL_CLIENT_3 = 3,
LOCAL_CLIENT_LAST = 3,
LOCAL_CLIENT_COUNT = 4
} LocalClientNum_t;
typedef enum
{
CON_CHANNEL_DONT_FILTER = 0x0,
CON_CHANNEL_ERROR = 0x1,
CON_CHANNEL_GAMENOTIFY = 0x2,
CON_CHANNEL_BOLDGAME = 0x3,
CON_CHANNEL_SUBTITLE = 0x4,
CON_CHANNEL_OBITUARY = 0x5,
CON_CHANNEL_LOGFILEONLY = 0x6,
CON_CHANNEL_CONSOLEONLY = 0x7,
CON_CHANNEL_GFX = 0x8,
CON_CHANNEL_SOUND = 0x9,
CON_CHANNEL_FILES = 0xA,
CON_CHANNEL_DEVGUI = 0xB,
CON_CHANNEL_PROFILE = 0xC,
CON_CHANNEL_UI = 0xD,
CON_CHANNEL_CLIENT = 0xE,
CON_CHANNEL_SERVER = 0xF,
CON_CHANNEL_SYSTEM = 0x10,
CON_CHANNEL_PLAYERWEAP = 0x11,
CON_CHANNEL_AI = 0x12,
CON_CHANNEL_ANIM = 0x13,
CON_CHANNEL_PHYS = 0x14,
CON_CHANNEL_FX = 0x15,
CON_CHANNEL_LEADERBOARDS = 0x16,
CON_CHANNEL_LIVE = 0x17,
CON_CHANNEL_PARSERSCRIPT = 0x18,
CON_CHANNEL_SCRIPT = 0x19,
CON_CHANNEL_SPAWNSYSTEM = 0x1A,
CON_CHANNEL_COOPINFO = 0x1B,
CON_CHANNEL_SERVERDEMO = 0x1C,
CON_CHANNEL_DDL = 0x1D,
CON_CHANNEL_NETWORK = 0x1E,
CON_CHANNEL_SCHEDULER = 0x1F,
CON_FIRST_DEBUG_CHANNEL = 0x1F,
CON_CHANNEL_TASK = 0x20,
CON_CHANNEL_SPU = 0x21,
CON_BUILTIN_CHANNEL_COUNT = 0x22
} conChannel_t;
typedef enum
{
CON_DEST_CONSOLE = 0,
CON_DEST_MINICON = 1,
CON_DEST_ERROR = 2,
CON_DEST_GAME_FIRST = 3,
CON_DEST_GAME1 = 3,
CON_DEST_GAME2 = 4,
CON_DEST_GAME3 = 5,
CON_DEST_GAME_LAST = 5,
CON_DEST_COUNT = 6
} print_msg_dest_t;
typedef enum
{
NS_CLIENT1 = 0x0,
NS_SERVER = 0x1,
NS_MAXCLIENTS = 0x1,
NS_PACKET = 0x2
} netsrc_t;
typedef enum
{
CS_MOTD = 11,
CS_TIMESCALE = 14
} ConfigString;
typedef enum
{
FS_READ = 0,
FS_WRITE = 1,
FS_APPEND = 2,
FS_APPEND_SYNC = 3
} fsMode_t;
struct PrintChannel
{
char name[32];
bool allowScript;
};
static_assert(sizeof(PrintChannel) == 33);
struct PrintChannelGlob
{
PrintChannel openChannels[256];
unsigned int filters[6][8];
};
struct usercmd_s
{
int serverTime;
int button_bits[2];
int angles[3];
unsigned __int16 weapon;
unsigned __int16 offHandIndex;
unsigned __int16 lastWeaponAltModeSwitch;
char forwardmove;
char rightmove;
char upmove;
char pitchmove;
char yawmove;
float meleeChargeYaw;
unsigned char meleeChargeDist;
float rollmove;
char selectedLocation[2];
unsigned char selectedYaw;
};
static_assert(sizeof(usercmd_s) == 52);
struct cmd_function_t
{
cmd_function_t* next;
const char* name;
const char* autoCompleteDir;
const char* autoCompleteExt;
void(__cdecl* function)();
bool consoleAccess;
};
static_assert(sizeof(cmd_function_t) == 24);
struct CmdArgs
{
int nesting;
int localClientNum[8];
int controllerIndex[8];
void* itemDef[8];
int argshift[8];
int argc[8];
const char** argv[8];
char textPool[8192];
const char* argvPool[512];
int usedTextPool[8];
int totalUsedArgvPool;
int totalUsedTextPool;
};
static_assert(sizeof(CmdArgs) == 10476);
typedef enum
{
NA_BOT = 0x0,
NA_BAD = 0x1,
NA_LOOPBACK = 0x2,
NA_BROADCAST = 0x3,
NA_IP = 0x4
} netadrtype_t;
struct netadr_s // Confirm nigga
{
netadrtype_t type;
unsigned char ip[4];
unsigned __int16 port;
int addrHandleIndex;
};
static_assert(sizeof(netadr_s) == 16);
struct msg_t
{
int overflowed;
int readOnly;
unsigned char* data;
unsigned char* splitData;
int maxsize;
int cursize;
int splitSize;
int readcount;
int bit;
int lastEntityRef;
int flush;
netsrc_t targetLocalNetID;
};
static_assert(sizeof(msg_t) == 48);
enum dvar_flags : unsigned __int16
{
DVAR_FLAG_SAVED = 0x1,
DVAR_FLAG_WRITEPROTECTED = 0x10,
DVAR_FLAG_READONLY = 0x40,
DVAR_FLAG_CHEAT = 0x80,
DVAR_FLAG_REPLICATED = 0x100,
};
typedef enum : char
{
DVAR_TYPE_BOOL = 0x0,
DVAR_TYPE_FLOAT = 0x1,
DVAR_TYPE_FLOAT_2 = 0x2,
DVAR_TYPE_FLOAT_3 = 0x3,
DVAR_TYPE_FLOAT_4 = 0x4,
DVAR_TYPE_INT = 0x5,
DVAR_TYPE_ENUM = 0x6,
DVAR_TYPE_STRING = 0x7,
DVAR_TYPE_COLOR = 0x8,
DVAR_TYPE_INT64 = 0x9,
DVAR_TYPE_LINEAR_COLOR_RGB = 0xA,
DVAR_TYPE_COLOR_XYZ = 0xB,
DVAR_TYPE_COUNT = 0xC
} dvarType_t;
union DvarValue
{
bool enabled;
int integer;
unsigned int unsignedInt;
__int64 integer64;
unsigned __int64 unsignedInt64;
float value;
float vector[4];
const char* string;
unsigned char color[4];
};
struct enum_limit
{
int stringCount;
const char** strings;
};
struct int_limit
{
int min;
int max;
};
struct int64_limit
{
__int64 min;
__int64 max;
};
struct float_limit
{
float min;
float max;
};
union DvarLimits
{
enum_limit enumeration;
int_limit integer;
float_limit value;
float_limit vector;
};
typedef struct dvar_s
{
const char* name;
const char* description;
long hash;
unsigned int flags;
dvarType_t type;
bool modified;
bool loadedFromSaveGame;
DvarValue current;
DvarValue latched;
DvarValue reset;
DvarValue saved;
DvarLimits domain;
dvar_s* hashNext;
unsigned char pad0[8];
} dvar_t;
static_assert(sizeof(dvar_s) == 112);
enum playerFlag
{
PLAYER_FLAG_NOCLIP = 1 << 0,
PLAYER_FLAG_UFO = 1 << 1,
PLAYER_FLAG_FROZEN = 1 << 2,
};
struct gclient_s
{
char __pad0[10396];
int flags;
char __pad1[320];
};
static_assert(sizeof(gclient_s) == 10720);
enum entityFlag
{
FL_GODMODE = 1,
FL_DEMI_GODMODE = 2,
FL_NOTARGET = 4,
FL_NO_KNOCKBACK = 8,
FL_DROPPED_ITEM = 16,
FL_NO_BOTS = 32,
FL_NO_HUMANS = 64,
FL_TOGGLE = 128,
FL_SOFTACTIVATE = 256,
FL_LOW_PRIORITY_USEABLE = 512,
FL_NO_HEADCHECK = 1024,
FL_DYNAMICPATH = 2048,
FL_SUPPORTS_LINKTO = 4096,
FL_NO_AUTO_ANIM_UPDATE = 8192,
FL_GRENADE_TOUCH_DAMAGE = 16384,
FL_GRENADE_MARTYRDOM = 32768,
FL_MISSILE_DESTABILIZED = 65536,
FL_STABLE_MISSILES = 131072,
FL_REPEAT_ANIM_UPDATE = 262144,
FL_VEHICLE_TARGET = 524288,
FL_GROUND_ENT = 1048576,
FL_CURSOR_HINT = 2097152,
FL_USE_TURRET = 4194304,
FL_MISSILE_ATTRACTOR = 8388608,
FL_TARGET = 16777216,
FL_WEAPON_BEING_GRABBED = 33554432,
FL_OBSTACLE = 67108864,
FL_DODGE_LEFT = 134217728,
FL_DODGE_RIGHT = 268435456,
FL_BADPLACE_VOLUME = 536870912,
FL_AUTO_BLOCKPATHS = 1073741824
};
enum playerStateFlag
{
PMF_PRONE = 0x1,
PMF_DUCKED = 0x2
};
struct gentity_s
{
int entnum;
char __pad0[320];
gclient_s* client;
char __pad1[44];
int flags;
char __pad2[384];
};
static_assert(sizeof(gentity_s) == 760);
struct netProfilePacket_t
{
int iTime;
int iSize;
int bFragment;
};
struct netProfileStream_t
{
netProfilePacket_t packets[60];
int iCurrPacket;
int iBytesPerSecond;
int iLastBPSCalcTime;
int iCountedPackets;
int iCountedFragments;
int iFragmentPercentage;
int iLargestPacket;
int iSmallestPacket;
};
struct netProfileInfo_t
{
netProfileStream_t send;
netProfileStream_t recieve;
};
static_assert(sizeof(netProfileInfo_t) == 1504);
struct netchan_t
{
int outgoingSequence;
netsrc_t sock;
int dropped;
int incomingSequence;
netadr_s remoteAddress;
int qport;
int fragmentSequence;
int fragmentLength;
unsigned char* fragmentBuffer;
int fragmentBufferSize;
int unsentFragments;
int unsentFragmentStart;
int unsentLength;
unsigned char* unsentBuffer;
int unsentBufferSize;
int reliable_fragments;
unsigned char fragment_send_count[128];
unsigned int fragment_ack[4];
int lowest_send_count;
netProfileInfo_t prof;
};
static_assert(sizeof(netchan_t) == 1728);
struct MantleState
{
float yaw;
int timer;
int transIndex;
int flags;
};
enum EffectiveStance
{
PM_EFF_STANCE_DEFAULT = 0,
PM_EFF_STANCE_PRONE = 1,
PM_EFF_STANCE_DUCKED = 2,
PM_EFF_STANCE_LASTSTANDCRAWL = 3,
PM_EFF_STANCE_COUNT = 4
};
enum ViewLockTypes
{
PLAYERVIEWLOCK_NONE = 0,
PLAYERVIEWLOCK_FULL = 1,
PLAYERVIEWLOCK_WEAPONJITTER = 2,
PLAYERVIEWLOCKCOUNT = 3
};
typedef enum
{
PM_NORMAL = 0,
PM_NORMAL_LINKED = 1,
PM_NOCLIP = 2,
PM_UFO = 3,
PM_SPECTATOR = 4,
PM_INTERMISSION = 5,
PM_LASTSTAND = 6,
PM_REVIVEE = 7,
PM_LASTSTAND_TRANSITION = 8,
PM_DEAD = 9,
PM_DEAD_LINKED = 10
} pmtype_t;
enum clientState_t
{
CS_FREE = 0,
CS_ZOMBIE = 1,
CS_RECONNECTING = 2,
CS_CONNECTED = 3,
CS_CLIENTLOADING = 4,
CS_ACTIVE = 5
};
struct PredictedVehicleInfo
{
bool inVehicle;
vec3_t origin;
vec3_t angles;
vec3_t tVel;
vec3_t aVel;
};
static_assert(sizeof(PredictedVehicleInfo) == 52);
struct clientHeader_t
{
clientState_t state; // 0
int sendAsActive; // 4
int deltaMessage; // 8
int rateDealyed; // 12
netchan_t netchan; // 24
vec3_t predictedOrigin;
int predictedOriginServerTime;
PredictedVehicleInfo vehicle;
};
static_assert(sizeof(clientHeader_t) == 1812);
struct svscmd_info_t
{
const char* cmd;
int time;
int type;
};
struct client_s
{
clientHeader_t header;
const char* dropReason;
char userinfo[1024];
char reliableCommandBuffer[16384];
int reliableCommandBufferNext;
svscmd_info_t reliableCommandInfo[128];
};
}

26
src/game/symbols.hpp Normal file
View File

@ -0,0 +1,26 @@
#pragma once
#define WEAK __declspec(selectany)
namespace game
{
// Functions
WEAK symbol<void(errorParm_t, const char*, ...)> Com_Error{0x627380, 0x0};
WEAK symbol<void(conChannel_t, const char*, ...)> Com_Printf{0x4126C0, 0x0};
WEAK symbol<void(LocalClientNum_t, const char* text)> Cbuf_AddText{0x56EF70, 0x0};
WEAK symbol<void(LocalClientNum_t, int controllerIndex, const char* text)> Cmd_ExecuteSingleCommand{0x50B470, 0x0};
WEAK symbol<dvar_s*(const char*)> Dvar_FindVar{0x512F70, 0x0};
WEAK symbol<const char*(const dvar_s*)> Dvar_DisplayableValue{0x681DD0, 0x0};
WEAK symbol<void(const char*, void(), cmd_function_t*)> Cmd_AddCommandInternal{0x6AD580, 0x0};
WEAK symbol<void(const char* cmdName)> Cmd_RemoveCommand{0x527EA0, 0x0};
WEAK symbol<char*(int)> ConcatArgs{0x5D5F10, 0x0};
WEAK symbol<CmdArgs> cmd_args{0x355BD88, 0x0};
WEAK symbol<int> dvarCount{0x385BE74, 0x0};
WEAK symbol<dvar_t*> sortedDvars{0x385BE88, 0x0};
WEAK symbol<gentity_s> g_entities{0x32E5640, 0x0};
}

View File

@ -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;
}
};

View File

@ -0,0 +1,127 @@
#include <stdinc.hpp>
#include "component_loader.hpp"
void component_loader::register_component(std::unique_ptr<component_interface>&& 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<std::unique_ptr<component_interface>>& component_loader::get_components()
{
using component_vector = std::vector<std::unique_ptr<component_interface>>;
using component_vector_container = std::unique_ptr<component_vector, std::function<void(component_vector*)>>;
static component_vector_container components(new component_vector, [](component_vector* component_vector)
{
pre_destroy();
delete component_vector;
});
return *components;
}

View File

@ -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 <typename T>
class installer final
{
static_assert(std::is_base_of<component_interface, T>::value, "component has invalid base class");
public:
installer()
{
register_component(std::make_unique<T>());
}
};
template <typename T>
static T* get()
{
for (const auto& component_ : get_components())
{
if (typeid(*component_.get()) == typeid(T))
{
return reinterpret_cast<T*>(component_.get());
}
}
return nullptr;
}
static void register_component(std::unique_ptr<component_interface>&& 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<std::unique_ptr<component_interface>>& get_components();
};
#define REGISTER_COMPONENT(name) \
namespace \
{ \
static component_loader::installer<name> __component; \
}

16
src/main.cpp Normal file
View File

@ -0,0 +1,16 @@
#include <stdinc.hpp>
#include "loader/component_loader.hpp"
BOOL APIENTRY DllMain(HMODULE /*module_*/, DWORD ul_reason_for_call, LPVOID /*reserved_*/)
{
if (ul_reason_for_call == DLL_PROCESS_ATTACH)
{
component_loader::post_unpack();
}
else if (ul_reason_for_call == DLL_PROCESS_DETACH)
{
component_loader::pre_destroy();
}
return TRUE;
}

1
src/stdinc.cpp Normal file
View File

@ -0,0 +1 @@
#include <stdinc.hpp>

33
src/stdinc.hpp Normal file
View File

@ -0,0 +1,33 @@
#pragma once
#define BINARY_PAYLOAD_SIZE 0x0A000000
#define TLS_PAYLOAD_SIZE 0x2000
#define DLL_EXPORT extern "C" __declspec(dllexport)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <vector>
#include <cassert>
#include <mutex>
#include <string>
#include <iostream>
#include <algorithm>
#include <functional>
#include <unordered_set>
#include <map>
#include <vector>
#include <csetjmp>
#include <rapidjson/document.h>
#include <rapidjson/prettywriter.h>
#include <rapidjson/stringbuffer.h>
#pragma comment(lib, "ntdll.lib")
using namespace std::literals;
#include "game/structs.hpp"
#include "game/game.hpp"

46
src/utils/concurrency.hpp Normal file
View File

@ -0,0 +1,46 @@
#pragma once
#include <mutex>
namespace utils::concurrency
{
template <typename T, typename MutexType = std::mutex>
class container
{
public:
template <typename R = void, typename F>
R access(F&& accessor) const
{
std::lock_guard<MutexType> _{mutex_};
return accessor(object_);
}
template <typename R = void, typename F>
R access(F&& accessor)
{
std::lock_guard<MutexType> _{mutex_};
return accessor(object_);
}
template <typename R = void, typename F>
R access_with_lock(F&& accessor) const
{
std::unique_lock<MutexType> lock{mutex_};
return accessor(object_, lock);
}
template <typename R = void, typename F>
R access_with_lock(F&& accessor)
{
std::unique_lock<MutexType> lock{mutex_};
return accessor(object_, lock);
}
T& get_raw() { return object_; }
const T& get_raw() const { return object_; }
private:
mutable MutexType mutex_{};
T object_{};
};
}

View File

@ -0,0 +1,26 @@
#include "string.hpp"
#include "cryptography.hpp"
namespace jenkins_one_at_a_time
{
unsigned int jenkins_one_at_a_time::compute(const std::string& data)
{
return compute(data.data(), data.size());
}
unsigned int jenkins_one_at_a_time::compute(const char* key, const size_t len)
{
unsigned int hash, i;
for (hash = i = 0; i < len; ++i)
{
hash += key[i];
hash += (hash << 10);
hash ^= (hash >> 6);
}
hash += (hash << 3);
hash ^= (hash >> 11);
hash += (hash << 15);
return hash;
}
}

View File

@ -0,0 +1,7 @@
#pragma once
namespace jenkins_one_at_a_time
{
unsigned int compute(const std::string& data);
unsigned int compute(const char* key, size_t len);
}

193
src/utils/hook.cpp Normal file
View File

@ -0,0 +1,193 @@
#include "hook.hpp"
#include "string.hpp"
#include <MinHook.h>
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<void*>(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<void*>(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<void*>(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<void*>(place), data, length);
}
bool is_relatively_far(const void* pointer, const void* data, const int offset)
{
const std::int64_t diff = size_t(data) - (size_t(pointer) + offset);
const auto small_diff = std::int32_t(diff);
return diff != std::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<std::uint8_t>(patch_pointer, 0xE8);
set<std::int32_t>(patch_pointer + 1, std::int32_t(size_t(data) - (size_t(pointer) + 5)));
}
void call(const size_t pointer, void* data)
{
return call(reinterpret_cast<void*>(pointer), data);
}
void call(const size_t pointer, const size_t data)
{
return call(pointer, reinterpret_cast<void*>(data));
}
void set(std::uintptr_t address, std::vector<std::uint8_t>&& bytes)
{
DWORD oldProtect = 0;
auto* place = reinterpret_cast<void*>(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<void*>(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<std::uint32_t*>(bytes + 1) = CalculateRelativeJMPAddress(address, destination);
set(address, bytes, 5);
delete[] bytes;
}
void redirect_jump(void* pointer, void* data)
{
char* operand_ptr = static_cast<char*>(pointer) + 2;
std::int32_t new_operand = reinterpret_cast<std::int32_t>(data) - (reinterpret_cast<std::int32_t>(pointer) + 6);
set<std::int32_t>(operand_ptr, new_operand);
}
void redirect_jump(size_t pointer, void* data)
{
redirect_jump(reinterpret_cast<void*>(pointer), data);
}
}

120
src/utils/hook.hpp Normal file
View File

@ -0,0 +1,120 @@
#pragma once
#include "signature.hpp"
#define CalculateRelativeJMPAddress(X, Y) (((std::uintptr_t)Y - (std::uintptr_t)X) - 5)
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 <typename T>
T* get() const
{
return static_cast<T*>(this->get_original());
}
template <typename T, typename... Args>
T invoke(Args... args)
{
return static_cast<T(*)(Args ...)>(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 redirect_jump(void* pointer, void* data);
void redirect_jump(size_t pointer, void* data);
template <typename T>
T extract(void* address)
{
const auto data = static_cast<uint8_t*>(address);
const auto offset = *reinterpret_cast<int32_t*>(data);
return reinterpret_cast<T>(data + offset + 4);
}
template <typename T>
static void set(void* place, T value)
{
DWORD old_protect;
VirtualProtect(place, sizeof(T), PAGE_EXECUTE_READWRITE, &old_protect);
*static_cast<T*>(place) = value;
VirtualProtect(place, sizeof(T), old_protect, &old_protect);
FlushInstructionCache(GetCurrentProcess(), place, sizeof(T));
}
template <typename T>
static void set(const size_t place, T value)
{
return set<T>(reinterpret_cast<void*>(place), value);
}
template <typename T, typename... Args>
static T invoke(size_t func, Args ... args)
{
return reinterpret_cast<T(*)(Args ...)>(func)(args...);
}
template <typename T, typename... Args>
static T invoke(void* func, Args ... args)
{
return static_cast<T(*)(Args ...)>(func)(args...);
}
}

65
src/utils/info_string.cpp Normal file
View File

@ -0,0 +1,65 @@
#include "info_string.hpp"
#include "string.hpp"
namespace utils
{
info_string::info_string(const std::string& buffer)
{
this->parse(buffer);
}
info_string::info_string(const std::string_view& buffer)
: info_string(std::string{ buffer })
{
}
void info_string::set(const std::string& key, const std::string& value)
{
this->key_value_pairs_[key] = value;
}
std::string info_string::get(const std::string& key) const
{
const auto value = this->key_value_pairs_.find(key);
if (value != this->key_value_pairs_.end())
{
return value->second;
}
return "";
}
void info_string::parse(std::string buffer)
{
if (buffer[0] == '\\')
{
buffer = buffer.substr(1);
}
auto key_values = string::split(buffer, '\\');
for (size_t i = 0; !key_values.empty() && i < (key_values.size() - 1); i += 2)
{
const auto& key = key_values[i];
const auto& value = key_values[i + 1];
this->key_value_pairs_[key] = value;
}
}
std::string info_string::build() const
{
//auto first = true;
std::string info_string;
for (auto i = this->key_value_pairs_.begin(); i != this->key_value_pairs_.end(); ++i)
{
//if (first) first = false;
/*else*/
info_string.append("\\");
info_string.append(i->first); // Key
info_string.append("\\");
info_string.append(i->second); // Value
}
return info_string;
}
}

24
src/utils/info_string.hpp Normal file
View File

@ -0,0 +1,24 @@
#pragma once
#include <string>
#include <unordered_map>
namespace utils
{
class info_string
{
public:
info_string() = default;
info_string(const std::string& buffer);
info_string(const std::string_view& buffer);
void set(const std::string& key, const std::string& value);
std::string get(const std::string& key) const;
std::string build() const;
private:
std::unordered_map<std::string, std::string> key_value_pairs_{};
void parse(std::string buffer);
};
}

125
src/utils/io.cpp Normal file
View File

@ -0,0 +1,125 @@
#include "io.hpp"
#include "nt.hpp"
#include <fstream>
namespace utils::io
{
bool remove_file(const std::string& file)
{
return DeleteFileA(file.data()) == TRUE;
}
bool move_file(const std::string& src, const std::string& target)
{
return MoveFileA(src.data(), target.data()) == TRUE;
}
bool file_exists(const std::string& file)
{
return std::ifstream(file).good();
}
bool write_file(const std::string& file, const std::string& data, const bool append)
{
const auto pos = file.find_last_of("/\\");
if (pos != std::string::npos)
{
create_directory(file.substr(0, pos));
}
std::ofstream stream(
file, std::ios::binary | std::ofstream::out | (append ? std::ofstream::app : 0));
if (stream.is_open())
{
stream.write(data.data(), data.size());
stream.close();
return true;
}
return false;
}
std::string read_file(const std::string& file)
{
std::string data;
read_file(file, &data);
return data;
}
bool read_file(const std::string& file, std::string* data)
{
if (!data) return false;
data->clear();
if (file_exists(file))
{
std::ifstream stream(file, std::ios::binary);
if (!stream.is_open()) return false;
stream.seekg(0, std::ios::end);
const std::streamsize size = stream.tellg();
stream.seekg(0, std::ios::beg);
if (size > -1)
{
data->resize(static_cast<uint32_t>(size));
stream.read(const_cast<char*>(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<size_t>(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<std::string> list_files(const std::string& directory)
{
std::vector<std::string> 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);
}
}

21
src/utils/io.hpp Normal file
View File

@ -0,0 +1,21 @@
#pragma once
#include <string>
#include <vector>
#include <filesystem>
namespace utils::io
{
bool remove_file(const std::string& file);
bool move_file(const std::string& src, const std::string& target);
bool file_exists(const std::string& file);
bool write_file(const std::string& file, const std::string& data, bool append = false);
bool read_file(const std::string& file, std::string* data);
std::string read_file(const std::string& file);
size_t file_size(const std::string& file);
bool create_directory(const std::string& directory);
bool directory_exists(const std::string& directory);
bool directory_is_empty(const std::string& directory);
std::vector<std::string> list_files(const std::string& directory);
void copy_folder(const std::filesystem::path& src, const std::filesystem::path& target);
}

165
src/utils/memory.cpp Normal file
View File

@ -0,0 +1,165 @@
#include "memory.hpp"
#include "nt.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<void*>(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<char>(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<void*>(data));
}
bool memory::is_set(const void* mem, const char chr, const size_t length)
{
const auto mem_arr = static_cast<const char*>(mem);
for (size_t i = 0; i < length; ++i)
{
if (mem_arr[i] != chr)
{
return false;
}
}
return true;
}
bool memory::is_bad_read_ptr(const void* ptr)
{
MEMORY_BASIC_INFORMATION mbi = {};
if (VirtualQuery(ptr, &mbi, sizeof(mbi)))
{
const DWORD mask = (PAGE_READONLY | PAGE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_READ |
PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY);
auto b = !(mbi.Protect & mask);
// check the page is not a guard page
if (mbi.Protect & (PAGE_GUARD | PAGE_NOACCESS)) b = true;
return b;
}
return true;
}
bool memory::is_bad_code_ptr(const void* ptr)
{
MEMORY_BASIC_INFORMATION mbi = {};
if (VirtualQuery(ptr, &mbi, sizeof(mbi)))
{
const DWORD mask = (PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY);
auto b = !(mbi.Protect & mask);
// check the page is not a guard page
if (mbi.Protect & (PAGE_GUARD | PAGE_NOACCESS)) b = true;
return b;
}
return true;
}
bool memory::is_rdata_ptr(void* pointer)
{
const std::string rdata = ".rdata";
const auto pointer_lib = utils::nt::library::get_by_address(pointer);
for (const auto& section : pointer_lib.get_section_headers())
{
const auto size = sizeof(section->Name);
char name[size + 1];
name[size] = 0;
std::memcpy(name, section->Name, size);
if (name == rdata)
{
const auto target = size_t(pointer);
const size_t source_start = size_t(pointer_lib.get_ptr()) + section->PointerToRawData;
const size_t source_end = source_start + section->SizeOfRawData;
return target >= source_start && target <= source_end;
}
}
return false;
}
memory::allocator* memory::get_allocator()
{
return &memory::mem_allocator_;
}
}

75
src/utils/memory.hpp Normal file
View File

@ -0,0 +1,75 @@
#pragma once
#include <mutex>
#include <vector>
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 <typename T>
inline T* allocate()
{
return this->allocate_array<T>(1);
}
template <typename T>
inline T* allocate_array(const size_t count = 1)
{
return static_cast<T*>(this->allocate(count * sizeof(T)));
}
bool empty() const;
char* duplicate_string(const std::string& string);
private:
std::mutex mutex_;
std::vector<void*> pool_;
};
static void* allocate(size_t length);
template <typename T>
static inline T* allocate()
{
return allocate_array<T>(1);
}
template <typename T>
static inline T* allocate_array(const size_t count = 1)
{
return static_cast<T*>(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_;
};
}

254
src/utils/nt.cpp Normal file
View File

@ -0,0 +1,254 @@
#include "nt.hpp"
namespace utils::nt
{
library library::load(const std::string& name)
{
return library(LoadLibraryA(name.data()));
}
library library::load(const std::filesystem::path& path)
{
return library::load(path.generic_string());
}
library library::get_by_address(void* address)
{
HMODULE handle = nullptr;
GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, static_cast<LPCSTR>(address), &handle);
return library(handle);
}
library::library()
{
this->module_ = GetModuleHandleA(nullptr);
}
library::library(const std::string& name)
{
this->module_ = GetModuleHandleA(name.data());
}
library::library(const HMODULE handle)
{
this->module_ = handle;
}
bool library::operator==(const library& obj) const
{
return this->module_ == obj.module_;
}
library::operator bool() const
{
return this->is_valid();
}
library::operator HMODULE() const
{
return this->get_handle();
}
PIMAGE_NT_HEADERS library::get_nt_headers() const
{
if (!this->is_valid()) return nullptr;
return reinterpret_cast<PIMAGE_NT_HEADERS>(this->get_ptr() + this->get_dos_header()->e_lfanew);
}
PIMAGE_DOS_HEADER library::get_dos_header() const
{
return reinterpret_cast<PIMAGE_DOS_HEADER>(this->get_ptr());
}
PIMAGE_OPTIONAL_HEADER library::get_optional_header() const
{
if (!this->is_valid()) return nullptr;
return &this->get_nt_headers()->OptionalHeader;
}
std::vector<PIMAGE_SECTION_HEADER> library::get_section_headers() const
{
std::vector<PIMAGE_SECTION_HEADER> headers;
auto nt_headers = this->get_nt_headers();
auto section = IMAGE_FIRST_SECTION(nt_headers);
for (uint16_t i = 0; i < nt_headers->FileHeader.NumberOfSections; ++i, ++section)
{
if (section) headers.push_back(section);
else OutputDebugStringA("There was an invalid section :O");
}
return headers;
}
std::uint8_t* library::get_ptr() const
{
return reinterpret_cast<std::uint8_t*>(this->module_);
}
void library::unprotect() const
{
if (!this->is_valid()) return;
DWORD protection;
VirtualProtect(this->get_ptr(), this->get_optional_header()->SizeOfImage, PAGE_EXECUTE_READWRITE,
&protection);
}
size_t library::get_relative_entry_point() const
{
if (!this->is_valid()) return 0;
return this->get_nt_headers()->OptionalHeader.AddressOfEntryPoint;
}
void* library::get_entry_point() const
{
if (!this->is_valid()) return nullptr;
return this->get_ptr() + this->get_relative_entry_point();
}
bool library::is_valid() const
{
return this->module_ != nullptr && this->get_dos_header()->e_magic == IMAGE_DOS_SIGNATURE;
}
std::string library::get_name() const
{
if (!this->is_valid()) return "";
auto path = this->get_path();
const auto pos = path.find_last_of("/\\");
if (pos == std::string::npos) return path;
return path.substr(pos + 1);
}
std::string library::get_path() const
{
if (!this->is_valid()) return "";
char name[MAX_PATH] = {0};
GetModuleFileNameA(this->module_, name, sizeof name);
return name;
}
std::string library::get_folder() const
{
if (!this->is_valid()) return "";
const auto path = std::filesystem::path(this->get_path());
return path.parent_path().generic_string();
}
void library::free()
{
if (this->is_valid())
{
FreeLibrary(this->module_);
this->module_ = nullptr;
}
}
HMODULE library::get_handle() const
{
return this->module_;
}
void** library::get_iat_entry(const std::string& module_name, const std::string& proc_name) const
{
if (!this->is_valid()) return nullptr;
const library other_module(module_name);
if (!other_module.is_valid()) return nullptr;
auto* const target_function = other_module.get_proc<void*>(proc_name);
if (!target_function) return nullptr;
auto* header = this->get_optional_header();
if (!header) return nullptr;
auto* import_descriptor = reinterpret_cast<PIMAGE_IMPORT_DESCRIPTOR>(this->get_ptr() + header->DataDirectory
[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
while (import_descriptor->Name)
{
if (!_stricmp(reinterpret_cast<char*>(this->get_ptr() + import_descriptor->Name), module_name.data()))
{
auto* original_thunk_data = reinterpret_cast<PIMAGE_THUNK_DATA>(import_descriptor->
OriginalFirstThunk + this->get_ptr());
auto* thunk_data = reinterpret_cast<PIMAGE_THUNK_DATA>(import_descriptor->FirstThunk + this->
get_ptr());
while (original_thunk_data->u1.AddressOfData)
{
const size_t ordinal_number = original_thunk_data->u1.AddressOfData & 0xFFFFFFF;
if (ordinal_number > 0xFFFF) continue;
if (GetProcAddress(other_module.module_, reinterpret_cast<char*>(ordinal_number)) ==
target_function)
{
return reinterpret_cast<void**>(&thunk_data->u1.Function);
}
++original_thunk_data;
++thunk_data;
}
//break;
}
++import_descriptor;
}
return nullptr;
}
void raise_hard_exception()
{
int data = false;
const library ntdll("ntdll.dll");
ntdll.invoke_pascal<void>("RtlAdjustPrivilege", 19, true, false, &data);
ntdll.invoke_pascal<void>("NtRaiseHardError", 0xC000007B, 0, nullptr, nullptr, 6, &data);
}
std::string load_resource(const int id)
{
auto* const res = FindResource(library(), MAKEINTRESOURCE(id), RT_RCDATA);
if (!res) return {};
auto* const handle = LoadResource(nullptr, res);
if (!handle) return {};
return std::string(LPSTR(LockResource(handle)), SizeofResource(nullptr, res));
}
void relaunch_self()
{
const utils::nt::library self;
STARTUPINFOA startup_info;
PROCESS_INFORMATION process_info;
ZeroMemory(&startup_info, sizeof(startup_info));
ZeroMemory(&process_info, sizeof(process_info));
startup_info.cb = sizeof(startup_info);
char current_dir[MAX_PATH];
GetCurrentDirectoryA(sizeof(current_dir), current_dir);
auto* const command_line = GetCommandLineA();
CreateProcessA(self.get_path().data(), command_line, nullptr, nullptr, false, NULL, nullptr, current_dir,
&startup_info, &process_info);
if (process_info.hThread && process_info.hThread != INVALID_HANDLE_VALUE) CloseHandle(process_info.hThread);
if (process_info.hProcess && process_info.hProcess != INVALID_HANDLE_VALUE) CloseHandle(process_info.hProcess);
}
void terminate(const uint32_t code)
{
TerminateProcess(GetCurrentProcess(), code);
}
}

110
src/utils/nt.hpp Normal file
View File

@ -0,0 +1,110 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
// min and max is required by gdi, therefore NOMINMAX won't work
#ifdef max
#undef max
#endif
#ifdef min
#undef min
#endif
#include <string>
#include <functional>
#include <filesystem>
namespace utils::nt
{
class library final
{
public:
static library load(const std::string& name);
static library load(const std::filesystem::path& path);
static library get_by_address(void* address);
library();
explicit library(const std::string& name);
explicit library(HMODULE handle);
library(const library& a) : module_(a.module_)
{
}
bool operator!=(const library& obj) const { return !(*this == obj); };
bool operator==(const library& obj) const;
operator bool() const;
operator HMODULE() const;
void unprotect() const;
void* get_entry_point() const;
size_t get_relative_entry_point() const;
bool is_valid() const;
std::string get_name() const;
std::string get_path() const;
std::string get_folder() const;
std::uint8_t* get_ptr() const;
void free();
HMODULE get_handle() const;
template <typename T>
T get_proc(const std::string& process) const
{
if (!this->is_valid()) T{};
return reinterpret_cast<T>(GetProcAddress(this->module_, process.data()));
}
template <typename T>
std::function<T> get(const std::string& process) const
{
if (!this->is_valid()) return std::function<T>();
return static_cast<T*>(this->get_proc<void*>(process));
}
template <typename T, typename... Args>
T invoke(const std::string& process, Args ... args) const
{
auto method = this->get<T(__cdecl)(Args ...)>(process);
if (method) return method(args...);
return T();
}
template <typename T, typename... Args>
T invoke_pascal(const std::string& process, Args ... args) const
{
auto method = this->get<T(__stdcall)(Args ...)>(process);
if (method) return method(args...);
return T();
}
template <typename T, typename... Args>
T invoke_this(const std::string& process, void* this_ptr, Args ... args) const
{
auto method = this->get<T(__thiscall)(void*, Args ...)>(this_ptr, process);
if (method) return method(args...);
return T();
}
std::vector<PIMAGE_SECTION_HEADER> get_section_headers() const;
PIMAGE_NT_HEADERS get_nt_headers() const;
PIMAGE_DOS_HEADER get_dos_header() const;
PIMAGE_OPTIONAL_HEADER get_optional_header() const;
void** get_iat_entry(const std::string& module_name, const std::string& proc_name) const;
private:
HMODULE module_;
};
__declspec(noreturn) void raise_hard_exception();
std::string load_resource(int id);
void relaunch_self();
__declspec(noreturn) void terminate(uint32_t code = 0);
}

212
src/utils/signature.cpp Normal file
View File

@ -0,0 +1,212 @@
#include "signature.hpp"
#include <thread>
#include <mutex>
#include <intrin.h>
namespace utils::hook
{
void signature::load_pattern(const std::string& pattern)
{
this->mask_.clear();
this->pattern_.clear();
uint8_t nibble = 0;
auto has_nibble = false;
for (auto val : pattern)
{
if (val == ' ') continue;
if (val == '?')
{
this->mask_.push_back(val);
this->pattern_.push_back(0);
}
else
{
if ((val < '0' || val > '9') && (val < 'A' || val > 'F') && (val < 'a' || val > 'f'))
{
throw std::runtime_error("Invalid pattern");
}
char str[] = {val, 0};
const auto current_nibble = static_cast<uint8_t>(strtol(str, nullptr, 16));
if (!has_nibble)
{
has_nibble = true;
nibble = current_nibble;
}
else
{
has_nibble = false;
const uint8_t byte = current_nibble | (nibble << 4);
this->mask_.push_back('x');
this->pattern_.push_back(byte);
}
}
}
while (!this->mask_.empty() && this->mask_.back() == '?')
{
this->mask_.pop_back();
this->pattern_.pop_back();
}
if (this->has_sse_support())
{
while (this->pattern_.size() < 16)
{
this->pattern_.push_back(0);
}
}
if (has_nibble)
{
throw std::runtime_error("Invalid pattern");
}
}
std::vector<size_t> signature::process_range(uint8_t* start, const size_t length) const
{
if (this->has_sse_support()) return this->process_range_vectorized(start, length);
return this->process_range_linear(start, length);
}
std::vector<size_t> signature::process_range_linear(uint8_t* start, const size_t length) const
{
std::vector<size_t> result;
for (size_t i = 0; i < length; ++i)
{
const auto address = start + i;
size_t j = 0;
for (; j < this->mask_.size(); ++j)
{
if (this->mask_[j] != '?' && this->pattern_[j] != address[j])
{
break;
}
}
if (j == this->mask_.size())
{
result.push_back(size_t(address));
}
}
return result;
}
std::vector<size_t> signature::process_range_vectorized(uint8_t* start, const size_t length) const
{
std::vector<size_t> result;
__declspec(align(16)) char desired_mask[16] = {0};
for (size_t i = 0; i < this->mask_.size(); i++)
{
desired_mask[i / 8] |= (this->mask_[i] == '?' ? 0 : 1) << i % 8;
}
const auto mask = _mm_load_si128(reinterpret_cast<const __m128i*>(desired_mask));
const auto comparand = _mm_loadu_si128(reinterpret_cast<const __m128i*>(this->pattern_.data()));
for (size_t i = 0; i < length; ++i)
{
const auto address = start + i;
const auto value = _mm_loadu_si128(reinterpret_cast<const __m128i*>(address));
const auto comparison = _mm_cmpestrm(value, 16, comparand, static_cast<int>(this->mask_.size()),
_SIDD_CMP_EQUAL_EACH);
const auto matches = _mm_and_si128(mask, comparison);
const auto equivalence = _mm_xor_si128(mask, matches);
if (_mm_test_all_zeros(equivalence, equivalence))
{
result.push_back(size_t(address));
}
}
return result;
}
signature::signature_result signature::process() const
{
const auto range = this->length_ - this->mask_.size();
const auto cores = std::max(1u, std::thread::hardware_concurrency());
if (range <= cores * 10ull) return this->process_serial();
return this->process_parallel();
}
signature::signature_result signature::process_serial() const
{
const auto sub = this->has_sse_support() ? 16 : this->mask_.size();
return {this->process_range(this->start_, this->length_ - sub)};
}
signature::signature_result signature::process_parallel() const
{
const auto sub = this->has_sse_support() ? 16 : this->mask_.size();
const auto range = this->length_ - sub;
const auto cores = std::max(1u, std::thread::hardware_concurrency() / 2);
// Only use half of the available cores
const auto grid = range / cores;
std::mutex mutex;
std::vector<size_t> result;
std::vector<std::thread> threads;
for (auto i = 0u; i < cores; ++i)
{
const auto start = this->start_ + (grid * i);
const auto length = (i + 1 == cores) ? (this->start_ + this->length_ - sub) - start : grid;
threads.emplace_back([&, start, length]()
{
auto local_result = this->process_range(start, length);
if (local_result.empty()) return;
std::lock_guard _(mutex);
for (const auto& address : local_result)
{
result.push_back(address);
}
});
}
for (auto& t : threads)
{
if (t.joinable())
{
t.join();
}
}
std::sort(result.begin(), result.end());
return {std::move(result)};
}
bool signature::has_sse_support() const
{
if (this->mask_.size() <= 16)
{
int cpu_id[4];
__cpuid(cpu_id, 0);
if (cpu_id[0] >= 1)
{
__cpuidex(cpu_id, 1, 0);
return (cpu_id[2] & (1 << 20)) != 0;
}
}
return false;
}
}
utils::hook::signature::signature_result operator"" _sig(const char* str, const size_t len)
{
return utils::hook::signature(std::string(str, len)).process();
}

73
src/utils/signature.hpp Normal file
View File

@ -0,0 +1,73 @@
#pragma once
#include "nt.hpp"
#include <cstdint>
namespace utils::hook
{
class signature final
{
public:
class signature_result
{
public:
signature_result(std::vector<size_t>&& matches) : matches_(std::move(matches))
{
}
[[nodiscard]] uint8_t* get(const size_t index) const
{
if (index >= this->count())
{
throw std::runtime_error("Invalid index");
}
return reinterpret_cast<uint8_t*>(this->matches_[index]);
}
[[nodiscard]] size_t count() const
{
return this->matches_.size();
}
private:
std::vector<size_t> matches_;
};
explicit signature(const std::string& pattern, const nt::library library = {})
: signature(pattern, library.get_ptr(), library.get_optional_header()->SizeOfImage)
{
}
signature(const std::string& pattern, void* start, void* end)
: signature(pattern, start, size_t(end) - size_t(start))
{
}
signature(const std::string& pattern, void* start, const size_t length)
: start_(static_cast<uint8_t*>(start)), length_(length)
{
this->load_pattern(pattern);
}
signature_result process() const;
private:
std::string mask_;
std::basic_string<uint8_t> pattern_;
uint8_t* start_;
size_t length_;
void load_pattern(const std::string& pattern);
signature_result process_parallel() const;
signature_result process_serial() const;
std::vector<size_t> process_range(uint8_t* start, size_t length) const;
std::vector<size_t> process_range_linear(uint8_t* start, size_t length) const;
std::vector<size_t> process_range_vectorized(uint8_t* start, size_t length) const;
bool has_sse_support() const;
};
}
utils::hook::signature::signature_result operator"" _sig(const char* str, size_t len);

179
src/utils/string.cpp Normal file
View File

@ -0,0 +1,179 @@
#include "string.hpp"
#include <sstream>
#include <cstdarg>
#include <algorithm>
#include "nt.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<std::string> split(const std::string& s, const char delim)
{
std::stringstream ss(s);
std::string item;
std::vector<std::string> 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<char>(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<char>(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());
}
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(nullptr))
{
std::string data;
auto* const clipboard_data = GetClipboardData(1u);
if (clipboard_data)
{
auto* const cliptext = static_cast<char*>(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';
}
#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<char>(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<wchar_t>(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;
}
}

100
src/utils/string.hpp Normal file
View File

@ -0,0 +1,100 @@
#pragma once
#include "memory.hpp"
#include <cstdint>
#ifndef ARRAYSIZE
template <class Type, size_t n>
size_t ARRAYSIZE(Type(&)[n]) { return n; }
#endif
namespace utils::string
{
template <size_t Buffers, size_t MinBufferSize>
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<char>(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<std::string> 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);
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);
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);
}

BIN
tools/premake5.exe Normal file

Binary file not shown.