summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorWillian Henrique <wsimanbrazil@yahoo.com.br>2023-05-08 18:38:48 -0300
committerMichael Smith <mikesmiffy128@gmail.com>2023-05-21 22:11:16 +0100
commitc2bb37238d02e1634242607a9079c5033138ced7 (patch)
tree0f5eea5b7feaa0ce5511231627019f7c143dcbe9
parent892d99184d3969faf143d3741007b108253fa82c (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-xcompile1
-rw-r--r--compile.bat1
-rw-r--r--gamedata/gamelib.kv6
-rw-r--r--src/l4d2vote.c134
4 files changed, 142 insertions, 0 deletions
diff --git a/compile b/compile
index 3c35ed1..a3d07a8 100755
--- a/compile
+++ b/compile
@@ -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