aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Hayden K <imaciidz@gmail.com> 2025-02-11 17:48:49 -0500
committerGravatar Michael Smith <mikesmiffy128@gmail.com> 2025-04-06 20:59:36 +0100
commitf03e7c515d285e0efff2a09dacba48120778614a (patch)
tree88a8d7f523e7bc354e2600bf4276f3692aba21a4
parent31d5cd8bb68276353edc4e79e8b58eaf86b61630 (diff)
downloadsst-f03e7c515d285e0efff2a09dacba48120778614a.tar.gz
sst-f03e7c515d285e0efff2a09dacba48120778614a.zip
Add feature to remove the chat rate limit
This probably works for basically every game, but it was only tested for L4D/L4D2/L4D:S/Portal 2.
-rwxr-xr-xcompile1
-rw-r--r--compile.bat1
-rw-r--r--src/chatrate.c88
3 files changed, 90 insertions, 0 deletions
diff --git a/compile b/compile
index a51186a..d3aa259 100755
--- a/compile
+++ b/compile
@@ -55,6 +55,7 @@ src="\
alias.c
autojump.c
bind.c
+ chatrate.c
chunklets/fastspin.c
chunklets/msg.c
clientcon.c
diff --git a/compile.bat b/compile.bat
index 20df825..bcb98f2 100644
--- a/compile.bat
+++ b/compile.bat
@@ -69,6 +69,7 @@ setlocal DisableDelayedExpansion
:+ bind.c
:+ clientcon.c
:+ con_.c
+:+ chatrate.c
:+ chunklets/fastspin.c
:+ chunklets/msg.c
:+ crypto.c
diff --git a/src/chatrate.c b/src/chatrate.c
new file mode 100644
index 0000000..b69b0d5
--- /dev/null
+++ b/src/chatrate.c
@@ -0,0 +1,88 @@
+/*
+ * Copyright © 2025 Hayden K <imaciidz@gmail.com>
+ *
+ * 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 "errmsg.h"
+#include "feature.h"
+#include "intdefs.h"
+#include "langext.h"
+#include "os.h"
+#include "x86.h"
+#include "x86util.h"
+
+FEATURE("chat rate limit removal")
+GAMESPECIFIC(L4Dbased) // Tested/known to work in L4D1/2, L4D:S, and Portal 2.
+
+static uchar *patchedbyte;
+
+// Same method as SAR here. Basically, the game only lets you chat every 0.66s.
+// So, instead of adding 0.66 to the current time, we subtract it, and that
+// means we can always chat immediately.
+
+static inline bool find_ratelimit_insn(struct con_cmd *cmd_say) {
+ // Find the add instruction
+ uchar *insns = (uchar *)cmd_say->cb;
+ for (uchar *p = insns; p - insns < 128;) {
+ // find FADD
+ if (p[0] == X86_FLTBLK5 && p[1] == X86_MODRM(0, 0, 5)) {
+ patchedbyte = p + 1;
+ return true;
+ }
+ // Portal 2, L4D2 2125-2134, L4D:S all use SSE2, so try finding ADDSD
+ if (p[0] == X86_PFX_REPN && p[1] == X86_2BYTE & p[2] == X86_2B_ADD) {
+ patchedbyte = p + 2;
+ return true;
+ }
+ NEXT_INSN(p, "chat rate limit");
+ }
+ return false;
+}
+
+static inline bool patch_ratelimit_insn() {
+ // if FADD replace with FSUB; otherwise it is ADDSD, replace that with SUBSD
+ if (!os_mprot(patchedbyte, 1, PAGE_EXECUTE_READWRITE)) {
+ errmsg_errorsys("failed to patch chat rate limit: "
+ "couldn't make memory writable");
+ return false;
+ }
+ if (*patchedbyte == X86_MODRM(0, 0, 5)) *patchedbyte = X86_MODRM(0, 4, 5);
+ else *patchedbyte = X86_2B_SUB;
+ return true;
+}
+
+static inline void unpatch_ratelimit_insn() {
+ // same logic as above but in reverse
+ if (*patchedbyte == X86_MODRM(0, 4, 5)) *patchedbyte = X86_MODRM(0, 0, 5);
+ else *patchedbyte = X86_2B_ADD;
+}
+
+INIT {
+ struct con_cmd *cmd_say = con_findcmd("say");
+ if_cold (!cmd_say) return false;
+ if (!find_ratelimit_insn(cmd_say)) {
+ errmsg_errorx("couldn't find chat rate limit instruction");
+ return FEAT_INCOMPAT;
+ }
+ if (!patch_ratelimit_insn()) {
+ errmsg_errorx("couldn't patch chat rate limit");
+ return FEAT_FAIL;
+ }
+ return FEAT_OK;
+}
+
+END {
+ unpatch_ratelimit_insn();
+}