diff --git a/addons/sourcemod/gamedata/l4d_vomit_trace_patch.txt b/addons/sourcemod/gamedata/l4d_vomit_trace_patch.txt new file mode 100644 index 0000000000..09f509342e --- /dev/null +++ b/addons/sourcemod/gamedata/l4d_vomit_trace_patch.txt @@ -0,0 +1,196 @@ +"Games" +{ + "#default" + { + "Functions" + { + "CBaseAbility::UpdateAbility" + { + "offset" "CBaseAbility::UpdateAbility" + "hooktype" "entity" + "return" "void" + "this" "entity" + } + + "CBaseEntity::PhysicsSolidMaskForEntity" + { + "offset" "CBaseEntity::PhysicsSolidMaskForEntity" + "hooktype" "entity" + "return" "int" + "this" "ignore" + } + } + } + + "left4dead" + { + "MemPatches" + { + "ShouldHitEntity_MyInfectedPointer" + { + "signature" "CTraceFilterSimpleListNoInfectedOrGhosts_ShouldHitEntity" + "linux" + { + "offset" "101" + + /* + * 8B 06 mov eax, [esi] + * 89 34 24 mov [esp+2Ch+var_2C], esi ; IHandleEntity * + * + * OUR_PATCH: + * FF 90 24 01 00 00 call dword ptr [eax+124h] ; CBaseEntity::MyInfectedPointer(void) + * 85 C0 test eax, eax + * 8B 54 24 0C mov edx, [esp+2Ch+var_20] + * 75 CB jnz short loc_2A614C ; return 0 (no collision) + */ + "verify" "\xFF\x90\x2A\x2A\x2A\x2A\x85\xC0\x2A\x2A\x2A\x2A\x75" + + "preserve" "\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\x00" + + /* + * E8 ? ? ? ? call CBaseEntity::GetTeamNumber(void) + * 83 F8 03 cmp eax, 3 + * 8B 54 24 0C mov edx, [esp+2Ch+var_20] + * 74 CB jz short loc_2A614C ; return 0 (no collision) + */ + "patch" "\xE8\x00\x00\x00\x00\x83\xF8\x03\x90\x90\x90\x90\x74" + } + "windows" + { + "offset" "46" + + /* + * 8B 06 mov eax, [esi] ; IHandleEntity * + * 8B 90 20 01 00 00 mov edx, [eax+120h] ; CBaseEntity::MyInfectedPointer(void) + * 8B CE mov ecx, esi + * FF D2 call edx + * 85 C0 test eax, eax + * 75 0F jnz short loc_102CCC1D + */ + "verify" "\x8B\x06\x8B\x2A\x2A\x2A\x2A\x2A\x8B\xCE\xFF\x2A\x85\xC0\x75" + + /* + * EB 02 jmp short OUR_PATCH + * 66 90 NOP + * + * OUR_PATCH: + * 8B CE mov ecx, esi ; IHandleEntity * + * E8 ? ? ? ? call sub_1003FA90 ; CBaseEntity::GetTeamNumber(void) + * 83 F8 03 cmp eax, 3 + * 74 0F jz short loc_102CCC1D + */ + "patch" "\xEB\x02\x66\x90\x8B\xCE\xE8\x00\x00\x00\x00\x83\xF8\x03\x74" + } + } + + "OnVomitCollide__TraceRayMask_patch" + { + "signature" "OnVomitCollide" + "linux" + { + "offset" "16Eh" + "verify" "\x0B\x40\x00\x46" + "patch" "\x03\x40\x00\x46" + } + "windows" + { + "offset" "5Ch" + "verify" "\x0B\x40\x00\x46" + "patch" "\x03\x40\x00\x46" + } + } + + "OnVomitCollide__ClipRayMask_patch" + { + "signature" "OnVomitCollide" + "linux" + { + "offset" "252h" + "verify" "\x0B\x40\x00\x46" + "patch" "\x03\x40\x00\x46" + } + "windows" + { + "offset" "F1h" + "verify" "\x0B\x40\x00\x46" + "patch" "\x03\x40\x00\x46" + } + } + } + + "Offsets" + { + "CBaseAbility::UpdateAbility" + { + "linux" "190" + "windows" "189" + } + + "PatchOffset" + { + "linux" "1" + "windows" "7" + } + + "CBaseEntity::PhysicsSolidMaskForEntity" + { + "linux" "164" + "windows" "163" + } + } + + "Addresses" + { + "CBaseEntity_GetTeamNumber" + { + "linux" + { + "signature" "CBaseEntity_GetTeamNumber" + } + "windows" + { + "signature" "CBaseEntity_GetTeamNumber" + } + } + } + + "Signatures" + { + "CTraceFilterSimpleListNoInfectedOrGhosts_ShouldHitEntity" + { + "library" "server" + "linux" "@_ZN40CTraceFilterSimpleListNoInfectedOrGhosts15ShouldHitEntityEP13IHandleEntityi" + "windows" "\x53\x8B\x2A\x2A\x2A\x56\x8B\x2A\x2A\x2A\x57\x53\x56\x8B\x2A\xE8\x2A\x2A\x2A\x2A\x84\x2A\x74" + /* 53 8B ? ? ? 56 8B ? ? ? 57 53 56 8B ? E8 ? ? ? ? 84 ? 74 */ + } + + "CBaseEntity_GetTeamNumber" + { + "library" "server" + "linux" "@_ZNK11CBaseEntity13GetTeamNumberEv" + "windows" "\x8B\x81\xEC\x01\x00\x00\xC3" + /* 8B 81 EC 01 00 00 C3 */ + } + + "OnVomitCollide" + { + "library" "server" + "linux" "@_Z14OnVomitCollideP13CTerrorPlayerP11CBaseEntityS2_i6VectorS3_" + "windows" "\x81\xEC\x2A\x2A\x2A\x2A\x8B\x84\x24\x2A\x2A\x2A\x2A\x8B\x8C\x24\x2A\x2A\x2A\x2A\x8B\x94\x24" + /* 81 EC ? ? ? ? 8B 84 24 ? ? ? ? 8B 8C 24 ? ? ? ? 8B 94 24 */ + } + } + } + + "left4dead2" + { + "Offsets" + { + "CBaseEntity::PhysicsSolidMaskForEntity" + { + "linux" "178" + "windows" "177" + } + } + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/l4d_vomit_trace_patch.sp b/addons/sourcemod/scripting/l4d_vomit_trace_patch.sp new file mode 100644 index 0000000000..92a2b60cdc --- /dev/null +++ b/addons/sourcemod/scripting/l4d_vomit_trace_patch.sp @@ -0,0 +1,181 @@ +#pragma semicolon 1 +#pragma newdecls required + +#include +#include +#include +#include + +#define PLUGIN_VERSION "2.5" + +public Plugin myinfo = +{ + name = "[L4D & 2] Vomit Trace Patch", + author = "Forgetest", + description = "Fix vomit stuck on Infected teammates & allow stricter collision test.", + version = PLUGIN_VERSION, + url = "https://github.com/Target5150/MoYu_Server_Stupid_Plugins" +} + +methodmap GameDataWrapper < GameData { + public GameDataWrapper(const char[] file) { + GameData gd = new GameData(file); + if (!gd) SetFailState("Missing gamedata \"%s\"", file); + return view_as(gd); + } + property GameData Super { + public get() { return view_as(this); } + } + public int GetOffset(const char[] key) { + int offset = this.Super.GetOffset(key); + if (offset == -1) SetFailState("Missing offset \"%s\"", key); + return offset; + } + public Address GetAddress(const char[] key) { + Address ptr = this.Super.GetAddress(key); + if (ptr == Address_Null) SetFailState("Missing address \"%s\"", key); + return ptr; + } + public MemoryPatch CreatePatchOrFail(const char[] name, bool enable = false) { + MemoryPatch hPatch = MemoryPatch.CreateFromConf(this, name); + if (!(enable ? hPatch.Enable() : hPatch.Validate())) + SetFailState("Failed to patch \"%s\"", name); + return hPatch; + } + public DynamicHook CreateDHookOrFail(const char[] name) { + DynamicHook hSetup = DynamicHook.FromConf(this, name); + if (!hSetup) + SetFailState("Missing dhook setup \"%s\"", name); + return hSetup; + } +} + +#define OP_CALL_SIZE 5 + +bool g_bLeft4Dead2; + +MemoryPatch g_hPatch; +DynamicHook g_hDHook; +int g_iPatchOffs, g_iFuncOffs; + +DynamicHook g_hDHook_PhysicsSolidMaskForEntity; + +ConVar g_cvStrictCollide; + +public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) +{ + switch (GetEngineVersion()) + { + case Engine_Left4Dead: { g_bLeft4Dead2 = false; } + case Engine_Left4Dead2: { g_bLeft4Dead2 = true; } + default: { strcopy(error, err_max, "Plugin supports L4D & 2 only."); return APLRes_SilentFailure; } + } + return APLRes_Success; +} + +public void OnPluginStart() +{ + GameDataWrapper conf = new GameDataWrapper("l4d_vomit_trace_patch"); + + g_hDHook_PhysicsSolidMaskForEntity = conf.CreateDHookOrFail("CBaseEntity::PhysicsSolidMaskForEntity"); + + if (!g_bLeft4Dead2) + { + g_hPatch = conf.CreatePatchOrFail("ShouldHitEntity_MyInfectedPointer", false); + g_hDHook = conf.CreateDHookOrFail("CBaseAbility::UpdateAbility"); + + Address pGetTeamNumberFuncAddr = conf.GetAddress("CBaseEntity_GetTeamNumber"); + g_iPatchOffs = conf.GetOffset("PatchOffset"); + g_iFuncOffs = + view_as(pGetTeamNumberFuncAddr) - (view_as(g_hPatch.Address) + (g_iPatchOffs - 1) + OP_CALL_SIZE); + + conf.CreatePatchOrFail("OnVomitCollide__TraceRayMask_patch", true); + conf.CreatePatchOrFail("OnVomitCollide__ClipRayMask_patch", true); + } + + delete conf; + + g_cvStrictCollide = CreateConVar("vomit_collide_strict", "1", "Stricter vomit collision against hitbox instead of bounding box.", FCVAR_NONE, true, 0.0, true, 1.0); +} + +void ApplyPatch(bool patch) +{ + static bool patched = false; + if (patch && !patched) + { + if (!g_hPatch.Enable()) + SetFailState("Failed to enable patch \"ShouldHitEntity_MyInfectedPointer\""); + + StoreToAddress(g_hPatch.Address + view_as
(g_iPatchOffs), g_iFuncOffs, NumberType_Int32); + patched = true; + } + else if (!patch && patched) + { + g_hPatch.Disable(); + patched = false; + } +} + +public void OnEntityCreated(int entity, const char[] classname) +{ + if (classname[0] == 'a') + { + if (!strcmp(classname, "ability_vomit")) + { + if (!g_bLeft4Dead2) + { + g_hDHook.HookEntity(Hook_Pre, entity, CVomit_UpdateAbility); + g_hDHook.HookEntity(Hook_Post, entity, CVomit_UpdateAbility_Post); + } + + g_hDHook_PhysicsSolidMaskForEntity.HookEntity(Hook_Post, entity, CVomit_PhysicsSolidMaskForEntity_Post); + } + } + else if (classname[0] == 'v') + { + if (!strcmp(classname, "vomit_particle")) + { + g_hDHook_PhysicsSolidMaskForEntity.HookEntity(Hook_Post, entity, CVomitParticle_PhysicsSolidMaskForEntity_Post); + } + } +} + +MRESReturn CVomit_UpdateAbility(int pThis) +{ + if (GetEntProp(pThis, Prop_Send, "m_isSpraying")) + { + ApplyPatch(true); + } + + return MRES_Ignored; +} + +MRESReturn CVomit_UpdateAbility_Post(int pThis) +{ + ApplyPatch(false); + + return MRES_Ignored; +} + +MRESReturn CVomit_PhysicsSolidMaskForEntity_Post(DHookReturn hReturn) +{ + // (L4D1) 0x200400B -> MASK_SOLID + // (L4D2) 0x2004003 -> MASK_SOLID & ~CONTENTS_GRATE + + int flags = hReturn.Value; + + flags &= (~CONTENTS_GRATE); // Unnecessary for L4D2 though + + if (g_cvStrictCollide.BoolValue) + { + flags |= CONTENTS_HITBOX; + } + + hReturn.Value = flags; + return MRES_Override; +} + +MRESReturn CVomitParticle_PhysicsSolidMaskForEntity_Post(DHookReturn hReturn) +{ + return CVomit_PhysicsSolidMaskForEntity_Post(hReturn); +} \ No newline at end of file diff --git a/cfg/generalfixes.cfg b/cfg/generalfixes.cfg index 222e28b352..47b6148856 100644 --- a/cfg/generalfixes.cfg +++ b/cfg/generalfixes.cfg @@ -73,6 +73,7 @@ sm plugins load fixes/l4d_prop_touching_rules.smx sm plugins load fixes/l4d2_fix_tank_rock_handoff.smx sm plugins load fixes/l4d_fix_prop_los.smx sm plugins load fixes/l4d2_incap_fire_fix.smx +sm plugins load fixes/l4d_vomit_trace_patch.smx // Anti-Cheat. sm plugins load optional/l4d2_block_autoaim.smx // Sort of a cheat..?