diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d1c0f69 --- /dev/null +++ b/.gitignore @@ -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 \ No newline at end of file diff --git a/deps/premake/minhook.lua b/deps/premake/minhook.lua new file mode 100644 index 0000000..b69d149 --- /dev/null +++ b/deps/premake/minhook.lua @@ -0,0 +1,32 @@ +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 0000000..b319889 --- /dev/null +++ b/generate.bat @@ -0,0 +1,4 @@ +@echo off +echo Updating submodules... +call git submodule update --init --recursive +call tools\premake5 %* vs2019 \ No newline at end of file diff --git a/premake5.lua b/premake5.lua new file mode 100644 index 0000000..4b4f0d5 --- /dev/null +++ b/premake5.lua @@ -0,0 +1,84 @@ +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 "mw3-server-freezer" + location "./build" + objdir "%{wks.location}/obj" + targetdir "%{wks.location}/bin/%{cfg.platform}/%{cfg.buildcfg}" + targetname "%{prj.name}" + + language "C++" + + architecture "x86" + platforms "x86" + + buildoptions "/std:c++latest" + systemversion "latest" + + flags { "NoIncrementalLink", "NoMinimalRebuild", "MultiProcessorCompile", "No64BitChecks" } + + configurations {"Debug", "Release"} + + symbols "On" + staticruntime "On" + editandcontinue "Off" + warnings "Extra" + characterset "ASCII" + + configuration "Release" + optimize "Size" + defines {"NDEBUG"} + flags {"FatalCompileWarnings"} + buildoptions {"/GL"} + linkoptions { "/IGNORE:4702", "/LTCG" } + configuration {} + + configuration "Debug" + optimize "Debug" + defines {"DEBUG", "_DEBUG"} + configuration {} + +project "mw3-server-freezer" + kind "SharedLib" + language "C++" + + pchheader "stdinc.hpp" + pchsource "src/stdinc.cpp" + + files { "/src/**.hpp", "/src/**.cpp" } + + includedirs { "src" } + +dependencies.imports() + +group "Dependencies" +dependencies.projects() diff --git a/src/component/client_utils.cpp b/src/component/client_utils.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/component/command.cpp b/src/component/command.cpp new file mode 100644 index 0000000..753e9e0 --- /dev/null +++ b/src/component/command.cpp @@ -0,0 +1,139 @@ +#include + +#include +#include +#include + +#include "command.hpp" + +namespace command +{ + 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); + } + } + + 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; + } + + std::vector script_commands; + utils::memory::allocator allocator; + + void add_script_command(const std::string& name, const std::function& 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(0, 0, command.data()); + } + else + { + game::Cbuf_AddText(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("quit_meme", [](const params&) + { + // Will cause blue screen + utils::nt::raise_hard_exception(); + }); + + add("dia_quit", [](const params&) + { + game::Com_Quit_f(); + }); + } + }; +} + +REGISTER_COMPONENT(command::component) diff --git a/src/component/command.hpp b/src/component/command.hpp new file mode 100644 index 0000000..84a3bab --- /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_script_command(const std::string& name, const std::function& callback); + void clear_script_commands(); + + void execute(std::string command, bool sync = false); +} diff --git a/src/component/dvar_patches.cpp b/src/component/dvar_patches.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/component/exploit.cpp b/src/component/exploit.cpp new file mode 100644 index 0000000..7d8fed3 --- /dev/null +++ b/src/component/exploit.cpp @@ -0,0 +1,49 @@ +#include + +#include +#include + +#include "command.hpp" + +namespace exploit +{ + bool exploit = false; + + void cl_netchan_transmit(int a1, unsigned char* data, int size) + { + if (exploit) + { + data[1] = 0xAA; + data[8] = 0x80; + } + + reinterpret_cast(0x4400E0)(a1, data, size); + } + + class component final : public component_interface + { + public: + void post_unpack() override + { + add_exploit_commands(); + + utils::hook::call(0x420E40, cl_netchan_transmit); + } + + private: + static void add_exploit_commands() + { + command::add("exploit", [](const command::params&) + { + exploit = true; + }); + + command::add("undo_exploit", [](const command::params&) + { + exploit = false; + }); + } + }; +} + +REGISTER_COMPONENT(exploit::component) diff --git a/src/dllmain.cpp b/src/dllmain.cpp new file mode 100644 index 0000000..1fd97d5 --- /dev/null +++ b/src/dllmain.cpp @@ -0,0 +1,20 @@ +#include +#include "loader/component_loader.hpp" + +BOOL APIENTRY DllMain(HMODULE /*hModule*/, + DWORD ul_reason_for_call, + LPVOID /*lpReserved*/ +) +{ + 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; +} diff --git a/src/game/game.cpp b/src/game/game.cpp new file mode 100644 index 0000000..63d1a9a --- /dev/null +++ b/src/game/game.cpp @@ -0,0 +1,5 @@ +#include + +namespace game +{ +} diff --git a/src/game/game.hpp b/src/game/game.hpp new file mode 100644 index 0000000..760b347 --- /dev/null +++ b/src/game/game.hpp @@ -0,0 +1,34 @@ +#pragma once + +namespace game +{ + template + class symbol + { + public: + symbol(const size_t dedi) + : dedi_(reinterpret_cast(dedi)) + { + } + + T* get() const + { + return dedi_; + } + + operator T* () const + { + return this->get(); + } + + T* operator->() const + { + return this->get(); + } + + private: + T* dedi_; + }; +} + +#include "symbols.hpp" diff --git a/src/game/structs.hpp b/src/game/structs.hpp new file mode 100644 index 0000000..07f110f --- /dev/null +++ b/src/game/structs.hpp @@ -0,0 +1,714 @@ +#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 cmd_function_t + { + cmd_function_t* next; + const char* name; + const char* autoCompleteDir; + const char* autoCompleteExt; + void(__cdecl* function)(); + int flags; + }; + + struct CmdArgs + { + int nesting; + int localClientNum[8]; + int controllerIndex[8]; + int argc[8]; + const char** argv[8]; + }; + + typedef enum + { + NS_CLIENT1 = 0, + NS_CLIENT2 = 1, + NS_CLIENT3 = 2, + NS_CLIENT4 = 3, + NS_MAXCLIENTS = 4, + NS_SERVER = 4, + NS_PACKET = 5, + NS_INVALID_NETSRC = 6 + } netsrc_t; + + enum netadrtype_t + { + NA_BOT = 0x0, + NA_BAD = 0x1, + NA_LOOPBACK = 0x2, + NA_BROADCAST = 0x3, + NA_IP = 0x4 + }; + + struct netadr_s + { + netadrtype_t type; + unsigned char ip[4]; + unsigned __int16 port; + netsrc_t localNetID; + char __pad0[4]; + unsigned int index; + }; + + static_assert(sizeof(netadr_s) == 24); + + struct msg_t + { + int overflowed; + int readOnly; + char* data; + char* splitData; + int maxsize; + int cursize; + int splitSize; + int readcount; + int bit; + int lastEntityRef; + }; + + struct XZoneInfo + { + const char* name; + int allocFlags; + int freeFlags; + }; + + struct scr_entref_t + { + unsigned __int16 entnum; + unsigned __int16 classnum; + }; + + typedef void(__cdecl* scr_call_t)(int entref); + + enum MeansOfDeath + { + MOD_UNKNOWN = 0, + MOD_PISTOL_BULLET = 1, + MOD_RIFLE_BULLET = 2, + MOD_EXPLOSIVE_BULLET = 3, + MOD_GRENADE = 4, + MOD_GRENADE_SPLASH = 5, + MOD_PROJECTILE = 6, + MOD_PROJECTILE_SPLASH = 7, + MOD_MELEE = 8, + MOD_HEAD_SHOT = 9, + MOD_CRUSH = 10, + MOD_FALLING = 11, + MOD_SUICIDE = 12, + MOD_TRIGGER_HURT = 13, + MOD_EXPLOSIVE = 14, + MOD_IMPACT = 15, + MOD_NUM = 16 + }; + + enum scriptType_e + { + SCRIPT_NONE = 0, + SCRIPT_OBJECT = 1, + SCRIPT_STRING = 2, + SCRIPT_ISTRING = 3, + SCRIPT_VECTOR = 4, + SCRIPT_FLOAT = 5, + SCRIPT_INTEGER = 6, + SCRIPT_END = 8, + SCRIPT_FUNCTION = 9, + SCRIPT_STRUCT = 19, + SCRIPT_ARRAY = 22, + }; + + struct VariableStackBuffer + { + const char* pos; + unsigned __int16 size; + unsigned __int16 bufLen; + unsigned __int16 localId; + char time; + char buf[1]; + }; + + union VariableUnion + { + int intValue; + float floatValue; + unsigned int stringValue; + const float* vectorValue; + const char* codePosValue; + unsigned int pointerValue; + VariableStackBuffer* stackValue; + unsigned int entityId; + unsigned int uintValue; + }; + + struct VariableValue + { + VariableUnion u; + scriptType_e type; + }; + + struct function_stack_t + { + const char* pos; + unsigned int localId; + unsigned int localVarCount; + VariableValue* top; + VariableValue* startTop; + }; + + struct function_frame_t + { + function_stack_t fs; + int topType; + }; + + struct scrVmPub_t + { + unsigned int* localVars; + VariableValue* maxstack; + int function_count; + function_frame_t* function_frame; + VariableValue* top; + /*bool debugCode; + bool abort_on_error; + bool terminal_error; + bool block_execution;*/ + unsigned int inparamcount; + unsigned int outparamcount; + unsigned int breakpointOutparamcount; + bool showError; + function_frame_t function_frame_start[32]; + VariableValue stack[2048]; + }; + + struct scr_classStruct_t + { + unsigned __int16 id; + unsigned __int16 entArrayId; + char charId; + const char* name; + }; + + struct ObjectVariableChildren + { + unsigned __int16 firstChild; + unsigned __int16 lastChild; + }; + + struct ObjectVariableValue_u_f + { + unsigned __int16 prev; + unsigned __int16 next; + }; + + union ObjectVariableValue_u_o_u + { + unsigned __int16 size; + unsigned __int16 entnum; + unsigned __int16 nextEntId; + unsigned __int16 self; + }; + + struct ObjectVariableValue_u_o + { + unsigned __int16 refCount; + ObjectVariableValue_u_o_u u; + }; + + union ObjectVariableValue_w + { + unsigned int type; + unsigned int classnum; + unsigned int notifyName; + unsigned int waitTime; + unsigned int parentLocalId; + }; + + struct ChildVariableValue_u_f + { + unsigned __int16 prev; + unsigned __int16 next; + }; + + union ChildVariableValue_u + { + ChildVariableValue_u_f f; + VariableUnion u; + }; + + struct ChildBucketMatchKeys_keys + { + unsigned __int16 name_hi; + unsigned __int16 parentId; + }; + + union ChildBucketMatchKeys + { + ChildBucketMatchKeys_keys keys; + unsigned int match; + }; + + struct ChildVariableValue + { + ChildVariableValue_u u; + unsigned __int16 next; + char type; + char name_lo; + ChildBucketMatchKeys k; + unsigned __int16 nextSibling; + unsigned __int16 prevSibling; + }; + + union ObjectVariableValue_u + { + ObjectVariableValue_u_f f; + ObjectVariableValue_u_o o; + }; + + struct ObjectVariableValue + { + ObjectVariableValue_u u; + ObjectVariableValue_w w; + }; + + struct scrVarGlob_t + { + ObjectVariableValue objectVariableValue[36864]; + ObjectVariableChildren objectVariableChildren[36864]; + unsigned __int16 childVariableBucket[65536]; + ChildVariableValue childVariableValue[102400]; + }; + + enum dvar_flags : std::uint16_t + { + DVAR_FLAG_NONE = 0, + DVAR_FLAG_SAVED = 1, + DVAR_FLAG_LATCHED = 2, + DVAR_FLAG_CHEAT = 4, + DVAR_FLAG_REPLICATED = 0x8, + DVAR_FLAG_UNK1 = 0x40, + DVAR_FLAG_UNK2 = 0x200, + DVAR_FLAG_SYSTEM = 0x400, + DVAR_FLAG_WRITE = 0x800, + DVAR_FLAG_READONLY = 0x2000, + }; + + union DvarValue + { + bool enabled; + int integer; + unsigned int unsignedInt; + float value; + float vector[4]; + const char* string; + char color[4]; + }; + + struct enum_limit + { + int stringCount; + const char** strings; + }; + + struct int_limit + { + int min; + int max; + }; + + struct float_limit + { + float min; + float max; + }; + + union DvarLimits + { + enum_limit enumeration; + int_limit integer; + float_limit value; + float_limit vector; + }; + + struct dvar_t + { + const char* name; + unsigned int flags; + char type; + bool modified; + DvarValue current; + DvarValue latched; + DvarValue reset; + DvarLimits domain; + bool(__cdecl* domainFunc)(dvar_t*, DvarValue); + dvar_t* hashNext; + }; + + struct usercmd_s + { + int serverTime; + int buttons; + int angles[3]; + unsigned int weapon; + unsigned int offHand; + char forwardmove; + char rightmove; + unsigned __int16 airburstMarkDistance; + unsigned __int16 meleeChargeEnt; + unsigned char meleeChargeDist; + char selectedLoc[2]; + char selectedLocAngle; + char remoteControlAngles[2]; + int remoteControlMove; + }; + + 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 clientState_t : std::int32_t + { + CS_FREE = 0, + CS_ZOMBIE = 1, + CS_UNKNOWN = 2, + CS_CONNECTED = 3, + CS_PRIMED = 4, + CS_ACTIVE = 5 + }; + + struct MantleState + { + float yaw; + int timer; + int transIndex; + int flags; + }; + + struct SprintState_s + { + int sprintButtonUpRequired; // 0x20C + int sprintDelay; // 0x210 + int lastSprintStart; + int lastSprintEnd; // 0x218 + int sprintStartMaxLength; + }; + + struct PlayerVehicleState + { + int entity; + int flags; + float origin[3]; + float angles[3]; + float velocity[3]; + float angVelocity[3]; + float tilt[2]; + float tiltVelocity[2]; + }; + + struct PlayerActiveWeaponState + { + int weapAnim; + int weaponTime; + int weaponDelay; + int weaponRestrictKickTime; + int weaponState; + int weaponHandFlags; + int weaponShotCount; + }; + + struct PlayerEquippedWeaponState + { + bool usedBefore; + bool dualWielding; + bool inAltMode; + bool needsRechamber[2]; + int zoomLevelIndex; + }; + + static_assert(sizeof(PlayerEquippedWeaponState) == 12); + + struct EntityEvent + { + int eventType; + int eventParm; + }; + + struct playerEvents_t + { + int eventSequence; + EntityEvent events[4]; + int oldEventSequence; + int timeADSCameUp; + }; + + static_assert(sizeof(playerEvents_t) == 44); + + enum ViewLockTypes + { + PLAYERVIEWLOCK_NONE = 0, + PLAYERVIEWLOCK_FULL = 1, + PLAYERVIEWLOCK_WEAPONJITTER = 2, + PLAYERVIEWLOCKCOUNT = 3 + }; + + enum TraceHitType + { + TRACE_HITTYPE_NONE = 0, + TRACE_HITTYPE_ENTITY = 1, + TRACE_HITTYPE_DYNENT_MODEL = 2, + TRACE_HITTYPE_DYNENT_BRUSH = 3, + TRACE_HITTYPE_GLASS = 4 + }; + +#pragma pack(push, 1) + struct playerState_s + { + int commandTime; + int pm_type; + int pm_time; + int pm_flags; + int otherFlags; + int linkFlags; + int bobCycle; + float origin[3]; + float velocity[3]; + int grenadeTimeLeft; + int throwbackGrenadeOwner; + int throwbackGrenadeTimeLeft; + unsigned int throwbackWeapon; + int remoteEyesEnt; + int remoteEyesTagname; + int remoteControlEnt; + int remoteTurretEnt; + int foliageSoundTime; + int gravity; + int speed; + float delta_angles[3]; + int groundEntityNum; + float vLadderVec[3]; + int jumpTime; + float jumpOriginZ; + int legsTimer; + int legsAnim; + int torsoTimer; + int torsoAnim; + int legsAnimDuration; + int torsoAnimDuration; + int damageTimer; + int damageDuration; + int flinchYawAnim; + int corpseIndex; + PlayerVehicleState vehicleState; + int movementDir; + int eFlags; + playerEvents_t pe; + int unpredictableEventSequence; + int unpredictableEventSequenceOld; + int unpredictableEvents[4]; + char unpredictableEventParms[16]; + int clientNum; + int viewmodelIndex; + float viewangles[3]; + int viewHeightTarget; + float viewHeightCurrent; + int viewHeightLerpTime; + int viewHeightLerpTarget; + int viewHeightLerpDown; + char viewAngleClampBase[8]; + char viewAngleClampRange[8]; + int damageEvent; + int damageYaw; + int damagePitch; + int damageCount; + int damageFlags; + int stats[4]; + float proneDirection; + float proneDirectionPitch; + float proneTorsoPitch; + ViewLockTypes viewlocked; + int viewlocked_entNum; + float linkAngles[3]; + float linkWeaponAngles[3]; + int linkWeaponEnt; + int loopSound; + int cursorHint; + int cursorHintString; + int cursorHintEntIndex; + int cursorHintDualWield; + int iCompassPlayerInfo; + int radarEnabled; + int radarBlocked; + int radarMode; + int radarStrength; + int radarShowEnemyDirection; + int locationSelectionInfo; + SprintState_s sprintState; + float holdBreathScale; + int holdBreathTimer; + float moveSpeedScaleMultiplier; + MantleState mantleState; + PlayerActiveWeaponState weapState[2]; + unsigned int weaponsEquipped[15]; + PlayerEquippedWeaponState weapEquippedData[15]; + char weapCommon[376]; + int meleeChargeDist; + int meleeChargeTime; + int meleeChargeEnt; + int airburstMarkDistance; + unsigned int perks[2]; + unsigned int perkSlots[9]; + char __pad0[11752]; + }; + + struct pmove_t + { + playerState_s* ps; + usercmd_s cmd; + usercmd_s oldcmd; + int tracemask; + int numtouch; + int touchents[32]; + char bounds[24]; + float speed; + int proneChange; + float maxSprintTimeMultiplier; + bool mantleStarted; + float mantleEndPos[3]; + int mantleDuration; + int viewChangeTime; + float viewChange; + float fTorsoPitch; + float fWaistPitch; + int remoteTurretFireTime; + int lastUpdateCMDServerTime; + unsigned char handler; + }; +#pragma pack(pop) + + static_assert(sizeof(playerState_s) == 13056); + + struct gclient_s + { + playerState_s ps; + char __pad0[716]; + int flags; + int spectatorClient; + int lastCmdTime; +// int mpviewer; // Debug ? + int buttons; + int oldButtons; + int latched_buttons; + int buttonsSinceLastFrame; + float oldOrigin[3]; + float fGunPitch; + float fGunYaw; + int damage_blood; + int damage_stun; + float damage_from[3]; + int damage_fromWorld; + int accurateCount; + int accuracy_shots; + int accuracy_hits; + int inactivityTime; + int inactivityWarning; + int lastVoiceTime; + int switchTeamTime; + float currentAimSpreadScale; + float prevLinkedInvQuat[4]; + bool prevLinkAnglesSet; + bool link_rotationMovesEyePos; + bool link_doCollision; + bool link_useTagAnglesForViewAngles; + bool link_useBaseAnglesForViewClamp; + float linkAnglesFrac; + char link_viewClamp[64]; + char persistantPowerup[4]; + int portalID; + int dropWeaponTime; + int sniperRifleFiredTime; + float sniperRifleMuzzleYaw; + int PCSpecialPickedUpCount; + int useHoldEntity; + int useHoldTime; + int useButtonDone; + int iLastCompassPlayerInfoEnt; + int compassPingTime; + int damageTime; + float v_dmg_roll; + float v_dmg_pitch; + float baseAngles[3]; + float baseOrigin[3]; + float swayViewAngles[3]; + float swayOffset[3]; + float swayAngles[3]; + float recoilAngles[3]; + float recoilSpeed[3]; + float fLastIdleFactor; + int lastServerTime; + int lastWeapon; + bool lastWeaponAltStatus; + bool previouslyFiring; + bool previouslyFiringLeftHand; + bool previouslyUsingNightVision; + bool previouslySprinting; + int visionDuration[6]; + char visionName[384]; + int lastStand; + int lastStandTime; + int hudElemLastAssignedSoundID; + float lockedTargetOffset[3]; + unsigned __int16 attachShieldTagName; + int hintForcedType; + int hintForcedString; + }; + + struct trace_t + { + float fraction; + float normal[3]; + int surfaceFlags; + int contents; + char material[4]; + TraceHitType hitType; + unsigned __int16 hitId; + float fractionForHitType; + unsigned __int16 modelIndex; + unsigned __int16 partName; + unsigned __int16 partGroup; + bool allsolid; + bool startsolid; + bool walkable; + }; + + static_assert(sizeof(trace_t) == 52); + + struct pml_t + { + float forward[3]; + float right[3]; + float up[3]; + float frametime; + int msec; + int walking; + int groundPlane; + int almostGroundPlane; + trace_t groundTrace; + float impactSpeed; + float previous_origin[3]; + float previous_velocity[3]; + int holdrand; + }; + + static_assert(sizeof(pml_t) == 140); +} diff --git a/src/game/symbols.hpp b/src/game/symbols.hpp new file mode 100644 index 0000000..a23c4f9 --- /dev/null +++ b/src/game/symbols.hpp @@ -0,0 +1,35 @@ +#pragma once + +#define WEAK __declspec(selectany) + +namespace game +{ + // Functions + WEAK symbol ConcatArgs{0x539060}; + WEAK symbol Cbuf_AddText{0x4C1030}; + WEAK symbol Cbuf_InsertText{0x429920}; + WEAK symbol Cmd_AddCommandInternal{0x537E70}; + WEAK symbol Cmd_ExecuteSingleCommand{0x4EB8F0}; + WEAK symbol Cmd_RemoveCommand{0x4EAF30}; + WEAK symbol Cmd_Argv{0x5580E0}; + + WEAK symbol Dvar_FindVar{0x4EBB50}; + WEAK symbol + Dvar_RegisterInt{0x50C760}; + WEAK symbol + Dvar_RegisterBool{0x4A3300}; + WEAK symbol + Dvar_RegisterString{0x4157E0}; + WEAK symbol + Dvar_RegisterFloat{0x4A5CF0}; + + WEAK symbol Key_GetBindingForCmd{0x47D300}; + + WEAK symbol SV_Cmd_ArgvBuffer{0x4F6B00}; + + WEAK symbol NET_OutOfBandPrint{0x496230}; + WEAK symbol Com_Quit_f{0x556060}; + + // Variables + WEAK symbol cmd_args{0x1C96850}; +} diff --git a/src/loader/component_interface.hpp b/src/loader/component_interface.hpp new file mode 100644 index 0000000..e1ee433 --- /dev/null +++ b/src/loader/component_interface.hpp @@ -0,0 +1,35 @@ +#pragma once + +class component_interface +{ +public: + virtual ~component_interface() + { + } + + virtual void post_start() + { + } + + virtual void post_load() + { + } + + virtual void pre_destroy() + { + } + + virtual void post_unpack() + { + } + + virtual void* load_import([[maybe_unused]] const std::string& library, [[maybe_unused]] const std::string& function) + { + return nullptr; + } + + virtual bool is_supported() + { + return true; + } +}; diff --git a/src/loader/component_loader.cpp b/src/loader/component_loader.cpp new file mode 100644 index 0000000..d6a0090 --- /dev/null +++ b/src/loader/component_loader.cpp @@ -0,0 +1,127 @@ +#include +#include "component_loader.hpp" + +void component_loader::register_component(std::unique_ptr&& component_) +{ + get_components().push_back(std::move(component_)); +} + +bool component_loader::post_start() +{ + static auto handled = false; + if (handled) return true; + handled = true; + + try + { + for (const auto& component_ : get_components()) + { + component_->post_start(); + } + } + catch (premature_shutdown_trigger&) + { + return false; + } + + return true; +} + +bool component_loader::post_load() +{ + static auto handled = false; + if (handled) return true; + handled = true; + + clean(); + + try + { + for (const auto& component_ : get_components()) + { + component_->post_load(); + } + } + catch (premature_shutdown_trigger&) + { + return false; + } + + return true; +} + +void component_loader::post_unpack() +{ + static auto handled = false; + if (handled) return; + handled = true; + + for (const auto& component_ : get_components()) + { + component_->post_unpack(); + } +} + +void component_loader::pre_destroy() +{ + static auto handled = false; + if (handled) return; + handled = true; + + for (const auto& component_ : get_components()) + { + component_->pre_destroy(); + } +} + +void component_loader::clean() +{ + auto& components = get_components(); + for (auto i = components.begin(); i != components.end();) + { + if (!(*i)->is_supported()) + { + (*i)->pre_destroy(); + i = components.erase(i); + } + else + { + ++i; + } + } +} + +void* component_loader::load_import(const std::string& library, const std::string& function) +{ + void* function_ptr = nullptr; + + for (const auto& component_ : get_components()) + { + auto* const component_function_ptr = component_->load_import(library, function); + if (component_function_ptr) + { + function_ptr = component_function_ptr; + } + } + + return function_ptr; +} + +void component_loader::trigger_premature_shutdown() +{ + throw premature_shutdown_trigger(); +} + +std::vector>& component_loader::get_components() +{ + using component_vector = std::vector>; + using component_vector_container = std::unique_ptr>; + + static component_vector_container components(new component_vector, [](component_vector* component_vector) + { + pre_destroy(); + delete component_vector; + }); + + return *components; +} diff --git a/src/loader/component_loader.hpp b/src/loader/component_loader.hpp new file mode 100644 index 0000000..1f6b4d1 --- /dev/null +++ b/src/loader/component_loader.hpp @@ -0,0 +1,61 @@ +#pragma once +#include "component_interface.hpp" + +class component_loader final +{ +public: + class premature_shutdown_trigger final : public std::exception + { + [[nodiscard]] const char* what() const noexcept override + { + return "Premature shutdown requested"; + } + }; + + template + class installer final + { + static_assert(std::is_base_of::value, "component has invalid base class"); + + public: + installer() + { + register_component(std::make_unique()); + } + }; + + template + static T* get() + { + for (const auto& component_ : get_components()) + { + if (typeid(*component_.get()) == typeid(T)) + { + return reinterpret_cast(component_.get()); + } + } + + return nullptr; + } + + static void register_component(std::unique_ptr&& component); + + static bool post_start(); + static bool post_load(); + static void post_unpack(); + static void pre_destroy(); + static void clean(); + + static void* load_import(const std::string& library, const std::string& function); + + static void trigger_premature_shutdown(); + +private: + static std::vector>& get_components(); +}; + +#define REGISTER_COMPONENT(name) \ +namespace \ +{ \ + static component_loader::installer __component; \ +} diff --git a/src/stdinc.cpp b/src/stdinc.cpp new file mode 100644 index 0000000..c4d450f --- /dev/null +++ b/src/stdinc.cpp @@ -0,0 +1 @@ +#include diff --git a/src/stdinc.hpp b/src/stdinc.hpp new file mode 100644 index 0000000..6b3947d --- /dev/null +++ b/src/stdinc.hpp @@ -0,0 +1,23 @@ +#pragma once + +#define DLL_EXPORT extern "C" __declspec(dllexport) +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#pragma comment(lib, "ntdll.lib") + +using namespace std::literals; + +#include "game/structs.hpp" +#include "game/game.hpp" diff --git a/src/utils/hook.cpp b/src/utils/hook.cpp new file mode 100644 index 0000000..4c736bf --- /dev/null +++ b/src/utils/hook.cpp @@ -0,0 +1,193 @@ +#include "hook.hpp" +#include "string.hpp" + +#include + +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, const int offset) + { + const int64_t diff = size_t(data) - (size_t(pointer) + offset); + const auto small_diff = int32_t(diff); + return diff != int64_t(small_diff); + } + + void call(void* pointer, void* data) + { + if (is_relatively_far(pointer, data)) + { + throw std::runtime_error("Too far away to create 32bit relative branch"); + } + + auto* patch_pointer = PBYTE(pointer); + set(patch_pointer, 0xE8); + set(patch_pointer + 1, int32_t(size_t(data) - (size_t(pointer) + 5))); + } + + void call(const size_t pointer, void* data) + { + return call(reinterpret_cast(pointer), data); + } + + void call(const size_t pointer, const size_t data) + { + return call(pointer, reinterpret_cast(data)); + } + + void set(std::uintptr_t address, std::vector&& bytes) + { + DWORD oldProtect = 0; + + auto* place = reinterpret_cast(address); + VirtualProtect(place, bytes.size(), PAGE_EXECUTE_READWRITE, &oldProtect); + memcpy(place, bytes.data(), bytes.size()); + VirtualProtect(place, bytes.size(), oldProtect, &oldProtect); + FlushInstructionCache(GetCurrentProcess(), place, bytes.size()); + } + + void set(std::uintptr_t address, void* buffer, size_t size) + { + DWORD oldProtect = 0; + + auto* place = reinterpret_cast(address); + VirtualProtect(place, size, PAGE_EXECUTE_READWRITE, &oldProtect); + memcpy(place, buffer, size); + VirtualProtect(place, size, oldProtect, &oldProtect); + FlushInstructionCache(GetCurrentProcess(), place, size); + } + + void jump(std::uintptr_t address, void* destination) + { + if (!address) return; + + std::uint8_t* bytes = new std::uint8_t[5]; + *bytes = 0xE9; + *reinterpret_cast(bytes + 1) = CalculateRelativeJMPAddress(address, destination); + + set(address, bytes, 5); + + delete[] bytes; + } + + void redirect_jump(void* pointer, void* data) + { + char* operand_ptr = static_cast(pointer) + 2; + int new_operand = reinterpret_cast(data) - (reinterpret_cast(pointer) + 6); + set(operand_ptr, new_operand); + } + + void redirect_jump(size_t pointer, void* data) + { + redirect_jump(reinterpret_cast(pointer), data); + } +} diff --git a/src/utils/hook.hpp b/src/utils/hook.hpp new file mode 100644 index 0000000..a80740e --- /dev/null +++ b/src/utils/hook.hpp @@ -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 + T* get() const + { + return static_cast(this->get_original()); + } + + template + T invoke(Args... args) + { + return static_cast(this->get_original())(args...); + } + + [[nodiscard]] void* get_original() const; + + private: + void* place_{}; + void* original_{}; + }; + + void nop(void* place, size_t length); + void nop(size_t place, size_t length); + + void copy(void* place, const void* data, size_t length); + void copy(size_t place, const void* data, size_t length); + + bool is_relatively_far(const void* pointer, const void* data, int offset = 5); + + void call(void* pointer, void* data); + void call(size_t pointer, void* data); + void call(size_t pointer, size_t data); + + void jump(std::uintptr_t address, void* destination); + + void redirect_jump(void* pointer, void* data); + void redirect_jump(size_t pointer, 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); + } + + 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...); + } +} diff --git a/src/utils/memory.cpp b/src/utils/memory.cpp new file mode 100644 index 0000000..52d4677 --- /dev/null +++ b/src/utils/memory.cpp @@ -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(data)); + } + + void* memory::allocator::allocate(const size_t length) + { + std::lock_guard _(this->mutex_); + + const auto data = memory::allocate(length); + this->pool_.push_back(data); + return data; + } + + bool memory::allocator::empty() const + { + return this->pool_.empty(); + } + + char* memory::allocator::duplicate_string(const std::string& string) + { + std::lock_guard _(this->mutex_); + + const auto data = memory::duplicate_string(string); + this->pool_.push_back(data); + return data; + } + + void* memory::allocate(const size_t length) + { + return calloc(length, 1); + } + + char* memory::duplicate_string(const std::string& string) + { + const auto new_string = allocate_array(string.size() + 1); + std::memcpy(new_string, string.data(), string.size()); + return new_string; + } + + void memory::free(void* data) + { + if (data) + { + ::free(data); + } + } + + void memory::free(const void* data) + { + free(const_cast(data)); + } + + bool memory::is_set(const void* mem, const char chr, const size_t length) + { + const auto mem_arr = static_cast(mem); + + for (size_t i = 0; i < length; ++i) + { + if (mem_arr[i] != chr) + { + return false; + } + } + + return true; + } + + 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_; + } +} diff --git a/src/utils/memory.hpp b/src/utils/memory.hpp new file mode 100644 index 0000000..5af5645 --- /dev/null +++ b/src/utils/memory.hpp @@ -0,0 +1,75 @@ +#pragma once + +#include +#include + +namespace utils +{ + class memory final + { + public: + class allocator final + { + public: + ~allocator(); + + void clear(); + + void free(void* data); + + void free(const void* data); + + void* allocate(size_t length); + + template + inline T* allocate() + { + return this->allocate_array(1); + } + + template + inline T* allocate_array(const size_t count = 1) + { + return static_cast(this->allocate(count * sizeof(T))); + } + + bool empty() const; + + char* duplicate_string(const std::string& string); + + private: + std::mutex mutex_; + std::vector pool_; + }; + + static void* allocate(size_t length); + + template + static inline T* allocate() + { + return allocate_array(1); + } + + template + static inline T* allocate_array(const size_t count = 1) + { + return static_cast(allocate(count * sizeof(T))); + } + + static char* duplicate_string(const std::string& string); + + static void free(void* data); + static void free(const void* data); + + static bool is_set(const void* mem, char chr, size_t length); + + static bool is_bad_read_ptr(const void* ptr); + static bool is_bad_code_ptr(const void* ptr); + static bool is_rdata_ptr(void* ptr); + + static allocator* get_allocator(); + + private: + static allocator mem_allocator_; + }; +} diff --git a/src/utils/nt.cpp b/src/utils/nt.cpp new file mode 100644 index 0000000..3d12c9e --- /dev/null +++ b/src/utils/nt.cpp @@ -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(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(this->get_ptr() + this->get_dos_header()->e_lfanew); + } + + PIMAGE_DOS_HEADER library::get_dos_header() const + { + return reinterpret_cast(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 library::get_section_headers() const + { + std::vector 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(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(proc_name); + if (!target_function) return nullptr; + + auto* header = this->get_optional_header(); + if (!header) return nullptr; + + auto* import_descriptor = reinterpret_cast(this->get_ptr() + header->DataDirectory + [IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); + + while (import_descriptor->Name) + { + if (!_stricmp(reinterpret_cast(this->get_ptr() + import_descriptor->Name), module_name.data())) + { + auto* original_thunk_data = reinterpret_cast(import_descriptor-> + OriginalFirstThunk + this->get_ptr()); + auto* thunk_data = reinterpret_cast(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(ordinal_number)) == + target_function) + { + return reinterpret_cast(&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("RtlAdjustPrivilege", 19, true, false, &data); + ntdll.invoke_pascal("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); + } +} diff --git a/src/utils/nt.hpp b/src/utils/nt.hpp new file mode 100644 index 0000000..86001be --- /dev/null +++ b/src/utils/nt.hpp @@ -0,0 +1,110 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN +#include + +// min and max is required by gdi, therefore NOMINMAX won't work +#ifdef max +#undef max +#endif + +#ifdef min +#undef min +#endif + +#include +#include +#include + +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 + T get_proc(const std::string& process) const + { + if (!this->is_valid()) T{}; + return reinterpret_cast(GetProcAddress(this->module_, process.data())); + } + + template + std::function get(const std::string& process) const + { + if (!this->is_valid()) return std::function(); + return static_cast(this->get_proc(process)); + } + + template + T invoke(const std::string& process, Args ... args) const + { + auto method = this->get(process); + if (method) return method(args...); + return T(); + } + + template + T invoke_pascal(const std::string& process, Args ... args) const + { + auto method = this->get(process); + if (method) return method(args...); + return T(); + } + + template + T invoke_this(const std::string& process, void* this_ptr, Args ... args) const + { + auto method = this->get(this_ptr, process); + if (method) return method(args...); + return T(); + } + + std::vector 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); +} diff --git a/src/utils/signature.cpp b/src/utils/signature.cpp new file mode 100644 index 0000000..9bb8c62 --- /dev/null +++ b/src/utils/signature.cpp @@ -0,0 +1,212 @@ +#include "signature.hpp" +#include +#include + +#include + +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(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 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 signature::process_range_linear(uint8_t* start, const size_t length) const + { + std::vector 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 signature::process_range_vectorized(uint8_t* start, const size_t length) const + { + std::vector 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(desired_mask)); + const auto comparand = _mm_loadu_si128(reinterpret_cast(this->pattern_.data())); + + for (size_t i = 0; i < length; ++i) + { + const auto address = start + i; + const auto value = _mm_loadu_si128(reinterpret_cast(address)); + const auto comparison = _mm_cmpestrm(value, 16, comparand, static_cast(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 result; + std::vector 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(); +} diff --git a/src/utils/signature.hpp b/src/utils/signature.hpp new file mode 100644 index 0000000..a372832 --- /dev/null +++ b/src/utils/signature.hpp @@ -0,0 +1,73 @@ +#pragma once +#include "nt.hpp" +#include + +namespace utils::hook +{ + class signature final + { + public: + class signature_result + { + public: + signature_result(std::vector&& 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(this->matches_[index]); + } + + [[nodiscard]] size_t count() const + { + return this->matches_.size(); + } + + private: + std::vector 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(start)), length_(length) + { + this->load_pattern(pattern); + } + + signature_result process() const; + + private: + std::string mask_; + std::basic_string 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 process_range(uint8_t* start, size_t length) const; + std::vector process_range_linear(uint8_t* start, size_t length) const; + std::vector 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); diff --git a/src/utils/string.cpp b/src/utils/string.cpp new file mode 100644 index 0000000..f212955 --- /dev/null +++ b/src/utils/string.cpp @@ -0,0 +1,184 @@ +#include "string.hpp" +#include +#include +#include + +#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 split(const std::string& s, const char delim) + { + std::stringstream ss(s); + std::string item; + std::vector elems; + + while (std::getline(ss, item, delim)) + { + elems.push_back(item); // elems.push_back(std::move(item)); // if C++11 (based on comment from @mchiasson) + } + + return elems; + } + + std::string to_lower(std::string text) + { + std::transform(text.begin(), text.end(), text.begin(), [](const char input) + { + return static_cast(tolower(input)); + }); + + return text; + } + + std::string to_upper(std::string text) + { + std::transform(text.begin(), text.end(), text.begin(), [](const char input) + { + return static_cast(toupper(input)); + }); + + return text; + } + + bool starts_with(const std::string& text, const std::string& substring) + { + return text.find(substring) == 0; + } + + bool ends_with(const std::string& text, const std::string& substring) + { + if (substring.size() > text.size()) return false; + return std::equal(substring.rbegin(), substring.rend(), text.rbegin()); + } + + 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(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(chr)); + } + + return result; + } + + std::wstring convert(const std::string& str) + { + std::wstring result; + result.reserve(str.size()); + + for (const auto& chr : str) + { + result.push_back(static_cast(chr)); + } + + return result; + } +#pragma warning(pop) + + std::string replace(std::string str, const std::string& from, const std::string& to) + { + if (from.empty()) + { + return str; + } + + size_t start_pos = 0; + while ((start_pos = str.find(from, start_pos)) != std::string::npos) + { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + + return str; + } + + bool contains(const std::string& str1, const std::string& str2) + { + return str1.find(str2) != std::string::npos; + } +} diff --git a/src/utils/string.hpp b/src/utils/string.hpp new file mode 100644 index 0000000..050998a --- /dev/null +++ b/src/utils/string.hpp @@ -0,0 +1,102 @@ +#pragma once +#include "memory.hpp" +#include + +#ifndef ARRAYSIZE +template +size_t ARRAYSIZE(Type (&)[n]) { return n; } +#endif + +namespace utils::string +{ + template + class va_provider final + { + public: + static_assert(Buffers != 0 && MinBufferSize != 0, "Buffers and MinBufferSize mustn't be 0"); + + va_provider() : current_buffer_(0) + { + } + + char* get(const char* format, const va_list ap) + { + ++this->current_buffer_ %= ARRAYSIZE(this->string_pool_); + auto entry = &this->string_pool_[this->current_buffer_]; + + if (!entry->size || !entry->buffer) + { + throw std::runtime_error("String pool not initialized"); + } + + while (true) + { + const int res = vsnprintf_s(entry->buffer, entry->size, _TRUNCATE, format, ap); + if (res > 0) break; // Success + if (res == 0) return nullptr; // Error + + entry->double_size(); + } + + return entry->buffer; + } + + private: + class entry final + { + public: + explicit entry(const size_t _size = MinBufferSize) : size(_size), buffer(nullptr) + { + if (this->size < MinBufferSize) this->size = MinBufferSize; + this->allocate(); + } + + ~entry() + { + if (this->buffer) memory::get_allocator()->free(this->buffer); + this->size = 0; + this->buffer = nullptr; + } + + void allocate() + { + if (this->buffer) memory::get_allocator()->free(this->buffer); + this->buffer = memory::get_allocator()->allocate_array(this->size + 1); + } + + void double_size() + { + this->size *= 2; + this->allocate(); + } + + size_t size; + char* buffer; + }; + + size_t current_buffer_; + entry string_pool_[Buffers]; + }; + + const char* va(const char* fmt, ...); + + std::vector split(const std::string& s, char delim); + + std::string to_lower(std::string text); + std::string to_upper(std::string text); + bool starts_with(const std::string& text, const std::string& substring); + bool ends_with(const std::string& text, const std::string& substring); + + 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); + + bool contains(const std::string& str1, const std::string& str2); +} diff --git a/tools/premake5.exe b/tools/premake5.exe new file mode 100644 index 0000000..c73da1f Binary files /dev/null and b/tools/premake5.exe differ