1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
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;
}
|