diff options
author | Willian Henrique <wsimanbrazil@yahoo.com.br> | 2023-05-08 18:38:48 -0300 |
---|---|---|
committer | Michael Smith <mikesmiffy128@gmail.com> | 2023-05-21 22:11:16 +0100 |
commit | c2bb37238d02e1634242607a9079c5033138ced7 (patch) | |
tree | 0f5eea5b7feaa0ce5511231627019f7c143dcbe9 | |
parent | 892d99184d3969faf143d3741007b108253fa82c (diff) |
Add command to reset L4D2 vote cooldowns
In most versions of L4D2, players cannot call a vote (e.g. switch
campaign, change difficulty, restart campaign) more than once every 3
minutes. This makes resetting on a first map a pain, usually requiring
reloading the map to reset the vote state.
This new sst_l4d_vote_cooldown_reset command empties the list of
structures tracking vote callers, allowing all players to vote again
immediately. This should make resetting runs a lot easier on versions
without unlimited votes.
-rwxr-xr-x | compile | 1 | ||||
-rw-r--r-- | compile.bat | 1 | ||||
-rw-r--r-- | gamedata/gamelib.kv | 6 | ||||
-rw-r--r-- | src/l4d2vote.c | 134 |
4 files changed, 142 insertions, 0 deletions
@@ -67,6 +67,7 @@ src="\ gameinfo.c hook.c kv.c + l4d2vote.c l4dwarp.c nosleep.c portalcolours.c diff --git a/compile.bat b/compile.bat index c275cb0..60dba8f 100644 --- a/compile.bat +++ b/compile.bat @@ -72,6 +72,7 @@ setlocal DisableDelayedExpansion :+ gameinfo.c
:+ hook.c
:+ kv.c
+:+ l4d2vote.c
:+ l4dwarp.c
:+ nomute.c
:+ nosleep.c
diff --git a/gamedata/gamelib.kv b/gamedata/gamelib.kv index cebcec5..8de1013 100644 --- a/gamedata/gamelib.kv +++ b/gamedata/gamelib.kv @@ -22,6 +22,12 @@ vtidx_GetBaseEntity "4 + NVDTOR" // CBaseEntity or CBasePlayer or something off_netprop_statechanged { L4D 88 } off_simtime { L4D 128 } +vtidx_Spawn { + L4D2 { + default "22 + NVDTOR" + TheLastStand "23 + NVDTOR" + } +} vtidx_Teleport { L4D "104 + NVDTOR" L4D2 { diff --git a/src/l4d2vote.c b/src/l4d2vote.c new file mode 100644 index 0000000..06c61b8 --- /dev/null +++ b/src/l4d2vote.c @@ -0,0 +1,134 @@ +/* + * Copyright © 2023 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 "ent.h" +#include "errmsg.h" +#include "feature.h" +#include "gamedata.h" +#include "gametype.h" +#include "intdefs.h" +#include "mem.h" +#include "x86.h" +#include "x86util.h" + +FEATURE("Left 4 Dead 2 vote cooldown resetting") +REQUIRE_GAMEDATA(vtidx_Spawn) + +static void **votecontroller = 0; +static int off_callerrecords = 0; + +// Note: the vote callers vector contains these as elements. We don't currently +// do anything with the structure, but keeping it here for reference. +/*struct CallerRecord { + u32 steamid_trunc; + float last_time; + int votes_passed; + int votes_failed; + int last_issueidx; + bool last_passed; +};*/ + +DEF_CCMD_HERE_UNREG(sst_l4d2_vote_cooldown_reset, + "Reset vote cooldown for all players", CON_CHEAT) { + if (!*votecontroller) { + con_warn("vote controller not initialised\n"); + return; + } + // Basically equivalent to CUtlVector::RemoveAll. The elements don't need + // to be destructed. This state is equivalent to when no one has voted yet + struct CUtlVector *recordvector = mem_offset(*votecontroller, + off_callerrecords); + recordvector->sz = 0; +} + +PREINIT { + // note: L4D1 has sv_vote_creation_timer but it doesn't actually do anything + return GAMETYPE_MATCHES(L4D2) && !!con_findvar("sv_vote_creation_timer"); +} + +static inline bool find_votecontroller(con_cmdcbv1 listissues_cb) { + const uchar *insns = (const uchar *)listissues_cb; +#ifdef _WIN32 + // The "listissues" command calls CVoteController::ListIssues, loading + // g_voteController into ECX + for (const uchar *p = insns; p - insns < 32;) { + if (p[0] == X86_MOVRMW && p[1] == X86_MODRM(0, 1, 5)) { + votecontroller = mem_loadptr(p + 2); + return true; + } + NEXT_INSN(p, "g_voteController variable"); + } +#else +#warning TODO(linux): this will be different +#endif + return false; +} + +// This finds the caller record vector using a pointer to the +// CVoteController::Spawn function +static inline bool find_votecallers(void *votectrlspawn) { + const uchar *insns = (const uchar *)votectrlspawn; + for (const uchar *p = insns; p - insns < 64;) { + // Unsure what the member on this offset actually is (the game seems to + // want it to be set to 0 to allow votes to happen), but the vector we + // want seems to consistently be 8 bytes after whatever this is + // "mov dword ptr [<reg> + off], 0", mod == 0b11 + if (p[0] == X86_MOVMIW && (p[1] & 0xC0) == 0x80 && + mem_load32(p + 6) == 0) { + off_callerrecords = mem_load32(p + 2) + 8; + return true; + } + NEXT_INSN(p, "vote caller record vector"); + } + return false; +} + +INIT { + struct con_cmd *cmd_listissues = con_findcmd("listissues"); + if (!cmd_listissues) { + errmsg_errorx("couldn't find \"listissues\" command"); + return false; + } + con_cmdcbv1 listissues_cb = con_getcmdcbv1(cmd_listissues); + if (!find_votecontroller(listissues_cb)) { + errmsg_errorx("couldn't find vote controller instance"); + return false; + } + + // g_voteController may have not been initialized yet so we get the vtable + // from the ent factory + const struct CEntityFactory *factory = ent_getfactory("vote_controller"); + if (!factory) { + errmsg_errorx("couldn't find vote controller entity factory"); + return false; + } + void **vtable = ent_findvtable(factory, "CVoteController"); + if (!vtable) { + errmsg_errorx("couldn't find CVoteController vtable"); + return false; + } + if (!find_votecallers(vtable[vtidx_Spawn])) { + errmsg_errorx("couldn't find vote callers vector offset"); + return false; + } + + con_reg(sst_l4d2_vote_cooldown_reset); + return true; +} + +// vi: sw=4 ts=4 noet tw=80 cc=80 |