From 2d0ac38fa0e023d29b32a867240df7fc38c5afaa Mon Sep 17 00:00:00 2001 From: Willian Henrique Date: Sun, 19 May 2024 17:14:55 -0300 Subject: Fix compatibility with latest L4D2 update --- src/kvsys.c | 52 +++++++++++++++++++++++++++++++++++++++++++--------- src/kvsys.h | 17 +++++++++++++++-- 2 files changed, 58 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/kvsys.c b/src/kvsys.c index 25344da..1d09b0f 100644 --- a/src/kvsys.c +++ b/src/kvsys.c @@ -1,5 +1,6 @@ /* * Copyright © 2023 Michael Smith + * Copyright © 2024 Willian Henrique * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -25,29 +26,35 @@ #include "mem.h" #include "os.h" #include "vcall.h" +#include "x86.h" FEATURE() IMPORT void *KeyValuesSystem(void); // vstlib symbol static void *kvs; -DECL_VFUNC(int, GetSymbolForString, 3, const char *, bool) -DECL_VFUNC(const char *, GetStringForSymbol, 4, int) +static int vtidx_GetSymbolForString = 3, vtidx_GetStringForSymbol = 4; +static bool iskvv2 = false; +DECL_VFUNC_DYN(int, GetSymbolForString, const char *, bool) +DECL_VFUNC_DYN(const char *, GetStringForSymbol, int) const char *kvsys_symtostr(int sym) { return GetStringForSymbol(kvs, sym); } int kvsys_strtosym(const char *s) { return GetSymbolForString(kvs, s, true); } struct KeyValues *kvsys_getsubkey(struct KeyValues *kv, int sym) { - for (kv = kv->child; kv; kv = kv->next) if (kv->keyname == sym) return kv; + for (kv = iskvv2 ? kv->v2.child : kv->v1.child; kv; + kv = iskvv2 ? kv->v2.next : kv->v1.next) { + if (kv->keyname == sym) return kv; + } return 0; } // this is trivial for now, but may need expansion later; see header comment -const char *kvsys_getstrval(struct KeyValues *kv) { return kv->strval; } +const char *kvsys_getstrval(const struct KeyValues *kv) { return kv->strval; } void kvsys_free(struct KeyValues *kv) { while (kv) { - kvsys_free(kv->child); - struct KeyValues *next = kv->next; + kvsys_free(iskvv2 ? kv->v2.child : kv->v1.child); + struct KeyValues *next = iskvv2 ? kv->v2.next : kv->v1.next; // NOTE! could (should?) call the free function in IKeyValuesSystem but // we instead assume pooling is compiled out in favour of the IMemAlloc // stuff, and thus call the latter directly for less overhead @@ -68,12 +75,38 @@ static const char *VCALLCONV hook_GetStringForSymbol(void *this, int s) { return ret; } +// XXX: 5th instance of this in the codebase, should REALLY tidy up soon +#ifdef _WIN32 +#define NVDTOR 1 +#else +#define NVDTOR 2 +#endif + +static void detectabichange(void **kvsvt) { + // When no virtual destructor is present, the 6th function in the KVS vtable + // is AddKeyValuesToMemoryLeakList, which is a nop in release builds. + // L4D2 2.2.3.8 (14th May 2024) adds a virtual destructor which pushes + // everything down, so the 6th function is not a nop any more. This + // coincides with changes to the KeyValues struct layout (see above). + uchar *insns = kvsvt[5]; + // should be RETI16 on Windows (thiscall, callee cleanup) and RET on Linux + // (cdecl, caller cleanup) but let's just check both to be thorough + if (insns[0] != X86_RETI16 && insns[0] != X86_RET) { + iskvv2 = true; + vtidx_GetSymbolForString += NVDTOR; + vtidx_GetStringForSymbol += NVDTOR; + } +} + INIT { kvs = KeyValuesSystem(); - // NOTE: this is technically redundant for early versions but I CBA writing - // a version check; it's easier to just do this unilaterally. + // NOTE: this hook is technically redundant for early versions but I CBA + // writing a version check; it's easier to just do this unilaterally. The + // kvs ABI check is probably relevant for other games, but none that we + // currently actively support if (GAMETYPE_MATCHES(L4D2x)) { void **kvsvt = mem_loadptr(kvs); + detectabichange(kvsvt); if (!os_mprot(kvsvt + vtidx_GetStringForSymbol, sizeof(void *), PAGE_READWRITE)) { errmsg_warnx("couldn't make KeyValuesSystem vtable writable"); @@ -90,7 +123,8 @@ INIT { END { if (orig_GetStringForSymbol) { - unhook_vtable(*(void ***)kvs, 4, (void *)orig_GetStringForSymbol); + unhook_vtable(*(void ***)kvs, vtidx_GetStringForSymbol, + (void *)orig_GetStringForSymbol); } } diff --git a/src/kvsys.h b/src/kvsys.h index 0c8217d..de97b01 100644 --- a/src/kvsys.h +++ b/src/kvsys.h @@ -1,5 +1,6 @@ /* * Copyright © 2023 Michael Smith + * Copyright © 2024 Willian Henrique * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -32,7 +33,19 @@ struct KeyValues { bool hasescapes; bool evalcond; //char unused; - struct KeyValues *next, *child, *chain; + union { + struct { + struct KeyValues *next, *child, *chain; + } v1; + struct { + void *kvsys; + bool haskvsys; // wasting 3 bytes for no reason, brilliant + struct KeyValues *next, *child, *chain; + } v2; + }; + // this was supposedly added here at some point but we don't use it: + // typedef bool (*GetSymbolProc_t)(const char *pKey); + // GetSymbolProc_t m_pExpressionGetSymbolProc; }; /* Wraps the engine IKeyValuesSystem::GetStringForSymbol() call. */ @@ -49,7 +62,7 @@ struct KeyValues *kvsys_getsubkey(struct KeyValues *kv, int sym); * IMPORTANT: currently does not automatically coerce types like the engine * does. This can be added later if actually required. */ -const char *kvsys_getstrval(struct KeyValues *kv); +const char *kvsys_getstrval(const struct KeyValues *kv); /* Free a KV object and all its subkeys. */ void kvsys_free(struct KeyValues *kv); -- cgit v1.2.3