diff options
-rwxr-xr-x | compile | 2 | ||||
-rw-r--r-- | compile.bat | 3 | ||||
-rw-r--r-- | gamedata/vphysics.txt | 10 | ||||
-rw-r--r-- | src/portalisg.c | 152 |
4 files changed, 165 insertions, 2 deletions
@@ -101,7 +101,7 @@ $HOSTCC -O2 -fuse-ld=lld $warnings $stdflags \ -o .build/mkentprops src/build/mkentprops.c src/os.c .build/gluegen `for s in $src; do echo "src/$s"; done` .build/mkgamedata gamedata/engine.txt gamedata/gamelib.txt gamedata/inputsystem.txt \ -gamedata/matchmaking.txt gamedata/vgui2.txt gamedata/vguimatsurface.txt +gamedata/matchmaking.txt gamedata/vgui2.txt gamedata/vguimatsurface.txt gamedata/vphysics.txt .build/mkentprops gamedata/entprops.txt for s in $src; do cc "$s"; done $CC -shared -fpic -fuse-ld=lld -O0 -w -o .build/libtier0.so src/stubs/tier0.c diff --git a/compile.bat b/compile.bat index 543e50b..a9ccf44 100644 --- a/compile.bat +++ b/compile.bat @@ -94,6 +94,7 @@ setlocal DisableDelayedExpansion :+ nosleep.c
:+ os.c
:+ portalcolours.c
+:+ portalisg.c
:+ rinput.c
:+ sst.c
:+ trace.c
@@ -122,7 +123,7 @@ if %host64%==1 ( -L.build %lbcryptprimitives_host% -o .build/mkentprops.exe src/build/mkentprops.c src/os.c || goto :end
.build\gluegen.exe%src% || goto :end
.build\mkgamedata.exe gamedata/engine.txt gamedata/gamelib.txt gamedata/inputsystem.txt ^
-gamedata/matchmaking.txt gamedata/vgui2.txt gamedata/vguimatsurface.txt || goto :end
+gamedata/matchmaking.txt gamedata/vgui2.txt gamedata/vguimatsurface.txt gamedata/vphysics.txt || goto :end
.build\mkentprops.exe gamedata/entprops.txt || goto :end
llvm-rc /FO .build\dll.res src\dll.rc || goto :end
for %%b in (%src%) do ( call :cc %%b || goto :end )
diff --git a/gamedata/vphysics.txt b/gamedata/vphysics.txt new file mode 100644 index 0000000..9c8bfb6 --- /dev/null +++ b/gamedata/vphysics.txt @@ -0,0 +1,10 @@ +# IPhysics +vtidx_CreateEnvironment 5 + +# IPhysicsEnvironment +vtidx_CreatePolyObject 7 + +# IPhysicsObject +vtidx_RecheckCollisionFilter 26 + +# vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/portalisg.c b/src/portalisg.c new file mode 100644 index 0000000..ffa80f2 --- /dev/null +++ b/src/portalisg.c @@ -0,0 +1,152 @@ +/* + * Copyright © Willian Henrique <wsimanbrazil@yahoo.com.br> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include "con_.h" +#include "engineapi.h" +#include "errmsg.h" +#include "feature.h" +#include "gamedata.h" +#include "intdefs.h" +#include "langext.h" +#include "mem.h" +#include "x86.h" +#include "x86util.h" + +FEATURE("Portal \"ISG\" state reset (experimental)") +GAMESPECIFIC(Portal1) +REQUIRE_GAMEDATA(vtidx_CreateEnvironment) +REQUIRE_GAMEDATA(vtidx_CreatePolyObject) +REQUIRE_GAMEDATA(vtidx_RecheckCollisionFilter) + +static bool *isg_flag; +static con_cmdcbv2 disconnect_cb; + +DEF_FEAT_CCMD_HERE(sst_portal_resetisg, + "Remove \"ISG\" state and disconnect from the server", 0) { + struct con_cmdargs disconnect_args = {0}; + disconnect_cb(&disconnect_args); + *isg_flag = false; +} + +static void **find_physenv_vtable(void *CreateEnvironment) { + const uchar *insns = (uchar *)CreateEnvironment; + for (const uchar *p = insns; p - insns < 16;) { + if (*p == X86_CALL) { p = insns = p + 5 + mem_loads32(p + 1); goto _1; } + NEXT_INSN(p, "call to CreateEnvironment"); + } + return 0; +_1: for (const uchar *p = insns; p - insns < 32;) { + // tail call to the constructor + if (*p == X86_JMPIW) { insns = p + 5 + mem_loads32(p + 1); goto _2; } + NEXT_INSN(p, "call to CPhysicsEnvironment constructor"); + } + return 0; +_2: for (const uchar *p = insns; p - insns < 16;) { + // the vtable is loaded pretty early on: + // mov dword ptr [reg], <vtable address> + if (*p == X86_MOVMIW && (p[1] & 0xF8) == 0) return mem_loadptr(p + 2); + NEXT_INSN(p, "CPhysicsEnvironment vtable"); + } + return 0; +} + +static void **find_physobj_vtable(void *CreatePolyObject) { + const uchar *insns = (uchar *)CreatePolyObject; + for (const uchar *p = insns; p - insns < 64;) { + // first thing in the method is a call (after pushing a million params) + if (*p == X86_CALL) { + insns = p + 5 + mem_loads32(p + 1); + goto _1; + } + NEXT_INSN(p, "call to CreatePhysicsObject"); + } + return 0; +_1: for (const uchar *p = insns; p - insns < 768;) { + // there's a call to "new CPhysicsObject" somewhere down the line. + // the (always inlined) constructor calls memset on the obj to init it. + // the obj's vtable being loaded in is interleaved with pushing args + // for memset and the order for all the instructions varies between + // versions. the consistent bit is that `push 72` always happens shortly + // before the vtable is loaded. + if (*p == X86_PUSHI8 && p[1] == 72) { insns = p + 2; goto _2; } + NEXT_INSN(p, "push before CPhysicsObject vtable load"); + } + return 0; +_2: for (const uchar *p = insns; p - insns < 16;) { + // mov dword ptr [reg], <vtable address> + if (*p == X86_MOVMIW && (p[1] & 0xF8) == 0) return mem_loadptr(p + 2); + NEXT_INSN(p, "CPhysicsObject vtable"); + } + return 0; +} + +static bool find_isg_flag(void *RecheckCollisionFilter) { + const uchar *insns = (uchar *)RecheckCollisionFilter, *p = insns; + while (p - insns < 32) { + // besides some flag handling, the only thing this function does is + // call m_pObject->recheck_collision_filter() + if (*p == X86_CALL) { + p = p + 5 + mem_loads32(p + 1); + goto _1; + } + NEXT_INSN(p, "call to RecheckCollisionFilter"); + } + return false; +_1: for (insns = p; p - insns < 32;) { + // recheck_collision_filter pretty much just calls a function + if (*p == X86_CALL) { + p = p + 5 + mem_loads32(p + 1); + goto _2; + } + NEXT_INSN(p, "call to recheck_ov_element"); + } + return false; +_2: for (insns = p; p - insns < 0x300;) { + // mov byte ptr [g_fDeferDeleteMindist] + if (*p == X86_MOVMI8 && p[1] == X86_MODRM(0, 0, 5) && p[6] == 1) { + isg_flag = mem_loadptr(p + 2); + return true; + } + NEXT_INSN(p, "g_fDeferDeleteMindist"); + } + return false; +} + +INIT { + disconnect_cb = con_getcmdcbv2(con_findcmd("disconnect")); + if_cold(!disconnect_cb) return FEAT_INCOMPAT; + void *phys = factory_engine("VPhysics031", 0); + if_cold (phys == 0) { + errmsg_errorx("couldn't get IPhysics interface"); + return FEAT_INCOMPAT; + } + void **vtable = mem_loadptr(phys); + vtable = find_physenv_vtable(vtable[vtidx_CreateEnvironment]); + if_cold (!vtable) { + errmsg_errorx("couldn't find CPhysicsEnvironment vtable"); + return FEAT_INCOMPAT; + } + vtable = find_physobj_vtable(vtable[vtidx_CreatePolyObject]); + if_cold (!vtable) { + errmsg_errorx("couldn't find CPhysicsObject vtable"); + return FEAT_INCOMPAT; + } + if_cold (!find_isg_flag(vtable[vtidx_RecheckCollisionFilter])) { + errmsg_errorx("couldn't find ISG flag"); + return FEAT_INCOMPAT; + } + return FEAT_OK; +} |