diff options
Diffstat (limited to 'src/hl2h.cpp')
| -rw-r--r-- | src/hl2h.cpp | 307 |
1 files changed, 307 insertions, 0 deletions
diff --git a/src/hl2h.cpp b/src/hl2h.cpp new file mode 100644 index 0000000..0261ff2 --- /dev/null +++ b/src/hl2h.cpp | |||
| @@ -0,0 +1,307 @@ | |||
| 1 | // hl2h.cpp — core: logging, vector math, globals, feature logic, setup | ||
| 2 | #include "hl2h.h" | ||
| 3 | #include <cmath> | ||
| 4 | #include <cstdarg> | ||
| 5 | |||
| 6 | // --------------------------------------------------------------------------- | ||
| 7 | // logging | ||
| 8 | // --------------------------------------------------------------------------- | ||
| 9 | |||
| 10 | void logf(const char *fmt, ...) { | ||
| 11 | va_list ap, ap2; | ||
| 12 | va_start(ap, fmt); | ||
| 13 | va_copy(ap2, ap); | ||
| 14 | fprintf(stderr, "hl2hake: "); | ||
| 15 | vfprintf(stderr, fmt, ap2); | ||
| 16 | fprintf(stderr, "\n"); | ||
| 17 | fflush(stderr); | ||
| 18 | va_end(ap2); | ||
| 19 | if (FILE *f = fopen("/tmp/hl2hake.log", "a")) { | ||
| 20 | fprintf(f, "hl2hake: "); | ||
| 21 | vfprintf(f, fmt, ap); | ||
| 22 | fprintf(f, "\n"); | ||
| 23 | fclose(f); | ||
| 24 | } | ||
| 25 | va_end(ap); | ||
| 26 | } | ||
| 27 | |||
| 28 | // --------------------------------------------------------------------------- | ||
| 29 | // globals | ||
| 30 | // --------------------------------------------------------------------------- | ||
| 31 | |||
| 32 | Module g_client, g_engine; | ||
| 33 | GameInterfaces g_iface; | ||
| 34 | NetVars g_net; | ||
| 35 | uintptr_t g_input = 0; | ||
| 36 | |||
| 37 | // --------------------------------------------------------------------------- | ||
| 38 | // vector math | ||
| 39 | // --------------------------------------------------------------------------- | ||
| 40 | |||
| 41 | static constexpr float PI_F = 3.14159265358979323846f; | ||
| 42 | |||
| 43 | QAngle vector_angles(const Vector &forward) { | ||
| 44 | QAngle a{}; | ||
| 45 | a.z = 0; | ||
| 46 | if (forward.y == 0 && forward.x == 0) { | ||
| 47 | a.y = 0; | ||
| 48 | a.x = (forward.z > 0) ? -90.f : 90.f; | ||
| 49 | } else { | ||
| 50 | a.y = atan2f(forward.y, forward.x) * 180.f / PI_F; | ||
| 51 | if (a.y < 0) | ||
| 52 | a.y += 360.f; | ||
| 53 | a.x = atan2f(-forward.z, | ||
| 54 | sqrtf(forward.x * forward.x + forward.y * forward.y)) * | ||
| 55 | 180.f / PI_F; | ||
| 56 | if (a.x < 0) | ||
| 57 | a.x += 360.f; | ||
| 58 | } | ||
| 59 | return a; | ||
| 60 | } | ||
| 61 | |||
| 62 | Vector angle_forward_vector(const QAngle &angles) { | ||
| 63 | auto DEG2RAD = [](float x) { return x * (PI_F / 180.f); }; | ||
| 64 | float sp = sinf(DEG2RAD(angles.x)), cp = cosf(DEG2RAD(angles.x)); | ||
| 65 | float sy = sinf(DEG2RAD(angles.y)), cy = cosf(DEG2RAD(angles.y)); | ||
| 66 | return {cp * cy, cp * sy, -sp}; | ||
| 67 | } | ||
| 68 | |||
| 69 | float getfov(const QAngle &my_view, const QAngle &aim_ang) { | ||
| 70 | Vector aim = angle_forward_vector(my_view); | ||
| 71 | Vector ang = angle_forward_vector(aim_ang); | ||
| 72 | float len = sqrtf(aim.x * aim.x + aim.y * aim.y + aim.z * aim.z); | ||
| 73 | if (len <= 0) | ||
| 74 | return 0; | ||
| 75 | float dot = (aim.x * ang.x + aim.y * ang.y + aim.z * ang.z) / len; | ||
| 76 | if (dot > 1) | ||
| 77 | dot = 1; | ||
| 78 | if (dot < -1) | ||
| 79 | dot = -1; | ||
| 80 | return acosf(dot) * (180.f / PI_F); | ||
| 81 | } | ||
| 82 | |||
| 83 | // --------------------------------------------------------------------------- | ||
| 84 | // feature logic (bhop, ABH, climb) | ||
| 85 | // --------------------------------------------------------------------------- | ||
| 86 | |||
| 87 | static int g_modified_logged = 0; | ||
| 88 | |||
| 89 | void note_modified(const CUserCmd *cmd, int flags, const float *vel) { | ||
| 90 | // recompute usercmd CRC after modification | ||
| 91 | fix_verified_crc(cmd->command_number, usercmd_checksum(cmd)); | ||
| 92 | |||
| 93 | if (g_modified_logged < 5) { | ||
| 94 | logf("cmd modified: seq=%d flags=0x%X buttons=0x%X fw=%.2f " | ||
| 95 | "vel=(%.1f,%.1f,%.1f)", | ||
| 96 | cmd->command_number, flags, cmd->buttons, cmd->forwardmove, vel[0], | ||
| 97 | vel[1], vel[2]); | ||
| 98 | g_modified_logged++; | ||
| 99 | } | ||
| 100 | } | ||
| 101 | |||
| 102 | // menu state (defined in menu.cpp, referenced here) | ||
| 103 | |||
| 104 | static bool g_logged_local = false; | ||
| 105 | |||
| 106 | static int engine_get_local_player() { | ||
| 107 | using Fn = int (*)(void *); | ||
| 108 | return vfunc<Fn>(g_iface.engine, 12)(g_iface.engine); | ||
| 109 | } | ||
| 110 | |||
| 111 | static void *entitylist_get(int idx) { | ||
| 112 | using Fn = void *(*)(void *, int); | ||
| 113 | return vfunc<Fn>(g_iface.entitylist, 3)(g_iface.entitylist, idx); | ||
| 114 | } | ||
| 115 | |||
| 116 | |||
| 117 | |||
| 118 | |||
| 119 | static void do_move(int seq) { | ||
| 120 | if (!g_input || !g_net.ok()) | ||
| 121 | return; | ||
| 122 | |||
| 123 | int localIdx = engine_get_local_player(); | ||
| 124 | if (localIdx <= 0 || localIdx > 64) | ||
| 125 | return; | ||
| 126 | if (!g_logged_local) { | ||
| 127 | logf("local player index %d (in-game)", localIdx); | ||
| 128 | g_logged_local = true; | ||
| 129 | } | ||
| 130 | |||
| 131 | void *ent = entitylist_get(localIdx); | ||
| 132 | if (!ent) | ||
| 133 | return; | ||
| 134 | |||
| 135 | CUserCmd *cmd = get_usercmd(seq); | ||
| 136 | if (!cmd) | ||
| 137 | return; | ||
| 138 | |||
| 139 | uintptr_t e = (uintptr_t)ent; | ||
| 140 | uint8_t movetype = *(uint8_t *)(e + g_net.movetype); | ||
| 141 | int flags = *(int *)(e + g_net.flags); | ||
| 142 | bool onground = (flags & FL_ONGROUND) != 0; | ||
| 143 | bool inwater = (flags & FL_INWATER) != 0; | ||
| 144 | |||
| 145 | if (movetype == MOVETYPE_NOCLIP || movetype == MOVETYPE_LADDER) | ||
| 146 | return; | ||
| 147 | |||
| 148 | static bool lastground = false; | ||
| 149 | |||
| 150 | if (middle_mouse_down()) { | ||
| 151 | if (cmd->command_number % 2) { | ||
| 152 | cmd->buttons &= ~IN_JUMP; | ||
| 153 | cmd->buttons |= IN_USE; | ||
| 154 | } else { | ||
| 155 | cmd->buttons &= ~IN_USE; | ||
| 156 | cmd->buttons |= IN_JUMP; | ||
| 157 | } | ||
| 158 | note_modified(cmd, flags, (float *)(e + g_net.velocity)); | ||
| 159 | } else if (cmd->buttons & IN_JUMP) { | ||
| 160 | if (inwater && (cmd->buttons & IN_DUCK)) { | ||
| 161 | lastground = onground; | ||
| 162 | return; | ||
| 163 | } | ||
| 164 | const float *vel = (const float *)(e + g_net.velocity); | ||
| 165 | float speed = sqrtf(vel[0] * vel[0] + vel[1] * vel[1] + vel[2] * vel[2]); | ||
| 166 | bool moving = speed > 0.1f; | ||
| 167 | bool backmove = false; | ||
| 168 | if (moving) { | ||
| 169 | QAngle moveang = vector_angles(Vector{vel[0], vel[1], vel[2]}); | ||
| 170 | moveang.x = 0.f; | ||
| 171 | QAngle viewang{cmd->viewangles[0], cmd->viewangles[1], | ||
| 172 | cmd->viewangles[2]}; | ||
| 173 | viewang.x = 0.f; | ||
| 174 | backmove = getfov(viewang, moveang) > 90.f; | ||
| 175 | } | ||
| 176 | if (!onground) { | ||
| 177 | lastground = false; | ||
| 178 | cmd->buttons &= ~IN_JUMP; | ||
| 179 | note_modified(cmd, flags, vel); | ||
| 180 | } else if (!lastground) { | ||
| 181 | lastground = true; | ||
| 182 | if (!backmove) | ||
| 183 | cmd->forwardmove = -0.1f; | ||
| 184 | note_modified(cmd, flags, vel); | ||
| 185 | } | ||
| 186 | } | ||
| 187 | } | ||
| 188 | |||
| 189 | // --------------------------------------------------------------------------- | ||
| 190 | // CreateMove hook (cdecl — game virtuals use cdecl, not __thiscall) | ||
| 191 | // --------------------------------------------------------------------------- | ||
| 192 | |||
| 193 | using CreateMoveFn = bool (*)(void *, int, float, bool); | ||
| 194 | static CreateMoveFn oCreateMove = nullptr; | ||
| 195 | |||
| 196 | static bool hkdCreateMove(void *self, int seq, float frametime, bool active) { | ||
| 197 | bool ret = oCreateMove(self, seq, frametime, active); | ||
| 198 | do_move(seq); | ||
| 199 | return ret; | ||
| 200 | } | ||
| 201 | |||
| 202 | static void install_hook() { | ||
| 203 | void **vt = VTABLE(g_iface.client); | ||
| 204 | constexpr size_t SLOTS = 256; | ||
| 205 | void **newvt = new void *[SLOTS]; | ||
| 206 | memcpy(newvt, vt, SLOTS * sizeof(void *)); | ||
| 207 | oCreateMove = (CreateMoveFn)vt[21]; | ||
| 208 | newvt[21] = (void *)&hkdCreateMove; | ||
| 209 | VTABLE(g_iface.client) = newvt; | ||
| 210 | logf("hook installed: CreateMove %p -> %p (orig %p)", vt[21], | ||
| 211 | (void *)&hkdCreateMove, (void *)oCreateMove); | ||
| 212 | } | ||
| 213 | |||
| 214 | // --------------------------------------------------------------------------- | ||
| 215 | // setup thread | ||
| 216 | // --------------------------------------------------------------------------- | ||
| 217 | |||
| 218 | extern void find_modules(); | ||
| 219 | extern void resolve_netvars(); | ||
| 220 | extern bool find_input_global(); | ||
| 221 | extern void init_x11(); | ||
| 222 | extern void init_conmsg(); | ||
| 223 | extern void install_menu(); | ||
| 224 | |||
| 225 | static bool is_hl2_process() { | ||
| 226 | char buf[512]; | ||
| 227 | ssize_t n = readlink("/proc/self/exe", buf, sizeof(buf) - 1); | ||
| 228 | if (n <= 0) | ||
| 229 | return false; | ||
| 230 | buf[n] = 0; | ||
| 231 | for (ssize_t i = n - 1; i >= 0; i--) | ||
| 232 | if (buf[i] == '/') | ||
| 233 | return strcmp(buf + i + 1, "hl2_linux") == 0; | ||
| 234 | return false; | ||
| 235 | } | ||
| 236 | |||
| 237 | static void *setup_thread(void *) { | ||
| 238 | for (int i = 0; i < 6000; i++) { | ||
| 239 | find_modules(); | ||
| 240 | if (g_client.valid() && g_engine.valid()) | ||
| 241 | break; | ||
| 242 | usleep(200 * 1000); | ||
| 243 | } | ||
| 244 | if (!g_client.valid() || !g_engine.valid()) { | ||
| 245 | logf("client.so/engine.so never appeared"); | ||
| 246 | return nullptr; | ||
| 247 | } | ||
| 248 | logf("client.so: %d mapped range(s), %s", (int)g_client.ranges.size(), | ||
| 249 | g_client.path.c_str()); | ||
| 250 | logf("engine.so: %d mapped range(s), %s", (int)g_engine.ranges.size(), | ||
| 251 | g_engine.path.c_str()); | ||
| 252 | |||
| 253 | // resolve factories via dlopen handles | ||
| 254 | void *ch = dlopen(g_client.path.c_str(), RTLD_NOLOAD | RTLD_LAZY | RTLD_GLOBAL); | ||
| 255 | void *eh = dlopen(g_engine.path.c_str(), RTLD_NOLOAD | RTLD_LAZY | RTLD_GLOBAL); | ||
| 256 | auto cf = (CreateInterfaceFn)dlsym(ch, "CreateInterface"); | ||
| 257 | auto ef = (CreateInterfaceFn)dlsym(eh, "CreateInterface"); | ||
| 258 | if (!cf || !ef) { | ||
| 259 | logf("CreateInterface not found"); | ||
| 260 | return nullptr; | ||
| 261 | } | ||
| 262 | |||
| 263 | for (int i = 0; i < 300; i++) { | ||
| 264 | if (!g_iface.client) | ||
| 265 | g_iface.client = cf("VClient017", nullptr); | ||
| 266 | if (!g_iface.entitylist) | ||
| 267 | g_iface.entitylist = cf("VClientEntityList003", nullptr); | ||
| 268 | if (!g_iface.engine) | ||
| 269 | g_iface.engine = ef("VEngineClient014", nullptr); | ||
| 270 | if (g_iface.client && g_iface.entitylist && g_iface.engine) | ||
| 271 | break; | ||
| 272 | usleep(100 * 1000); | ||
| 273 | } | ||
| 274 | if (!g_iface.client || !g_iface.entitylist || !g_iface.engine) { | ||
| 275 | logf("interfaces unresolved: VClient017=%p VClientEntityList003=%p " | ||
| 276 | "VEngineClient014=%p", | ||
| 277 | g_iface.client, g_iface.entitylist, g_iface.engine); | ||
| 278 | return nullptr; | ||
| 279 | } | ||
| 280 | logf("interfaces: VClient017=%p VClientEntityList003=%p " | ||
| 281 | "VEngineClient014=%p", | ||
| 282 | g_iface.client, g_iface.entitylist, g_iface.engine); | ||
| 283 | |||
| 284 | resolve_netvars(); | ||
| 285 | if (!find_input_global()) | ||
| 286 | return nullptr; | ||
| 287 | |||
| 288 | init_conmsg(); | ||
| 289 | install_hook(); | ||
| 290 | install_menu(); | ||
| 291 | return nullptr; | ||
| 292 | } | ||
| 293 | |||
| 294 | // --------------------------------------------------------------------------- | ||
| 295 | // constructor | ||
| 296 | // --------------------------------------------------------------------------- | ||
| 297 | |||
| 298 | __attribute__((constructor)) static void hl2hake_load() { | ||
| 299 | if (!is_hl2_process()) | ||
| 300 | return; | ||
| 301 | logf("loaded (pid %d)", getpid()); | ||
| 302 | pthread_t t; | ||
| 303 | if (pthread_create(&t, nullptr, setup_thread, nullptr) == 0) | ||
| 304 | pthread_detach(t); | ||
| 305 | else | ||
| 306 | logf("pthread_create failed"); | ||
| 307 | } | ||
