summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Michael Smith <mikesmiffy128@gmail.com> 2025-12-18 21:35:50 +0000
committerGravatar Michael Smith <mikesmiffy128@gmail.com> 2026-02-16 19:11:24 +0000
commit7a07c08abe46f97f2412f06c77204e9015f97a47 (patch)
tree581b80f55d7d66592fda98fb492d7465cc33f191
parent186aea515dd773bb11464714886ca5203d2be2ab (diff)
downloadsst-7a07c08abe46f97f2412f06c77204e9015f97a47.tar.gz
sst-7a07c08abe46f97f2412f06c77204e9015f97a47.zip
Move to a shared static RWX section
Does away with the need to mark a region as executable on load. Also allows the self-modified stuff and hook trampolines to fit in the same page, so we take up ever-so-slightly less memory. Unfortunately I had issues getting the test binary to build correctly and eventually decided to give up as it wasn't testing much of value anyway. Also note that the build now allows for .c and .S files with the same basename, which required renaming the .o files, so you might want to clean out your .build/ directory before building this one.
-rwxr-xr-xcompile14
-rw-r--r--compile.bat29
-rw-r--r--src/asm.h39
-rw-r--r--src/con_.S45
-rw-r--r--src/con_.c93
-rw-r--r--src/hook.S26
-rw-r--r--src/hook.c14
-rw-r--r--src/hook.h2
-rw-r--r--src/sst.c4
-rw-r--r--test/hook.test.c71
10 files changed, 172 insertions, 165 deletions
diff --git a/compile b/compile
index 6e36880..67eebdf 100755
--- a/compile
+++ b/compile
@@ -35,8 +35,8 @@ fi
objs=
cc() {
_bn="`basename "$1"`"
- objs="$objs .build/${_bn%%.c}.o"
- _mn=" -DMODULE_NAME=${_bn%%.c}"
+ objs="$objs .build/$_bn.o"
+ _mn=" -DMODULE_NAME=${_bn%%.*}"
# ugly annoying special case
if [ "$_mn" = " -DMODULE_NAME=con_" ]; then _mn=" -DMODULE_NAME=con"
elif [ "$_mn" = "-DMODULE_NAME=sst" ]; then _mn=; fi
@@ -61,6 +61,7 @@ src="\
chunklets/x86.c
clientcon.c
con_.c
+ con_.S
crypto.c
democustom.c
demorec.c
@@ -75,6 +76,7 @@ src="\
gameserver.c
hexcolour.c
hook.c
+ hook.S
hud.c
inputhud.c
kvsys.c
@@ -99,7 +101,8 @@ $HOSTCC -O2 -fuse-ld=lld $warnings $stdflags \
-o .build/mkgamedata src/build/mkgamedata.c src/os.c
$HOSTCC -O2 -fuse-ld=lld $warnings $stdflags \
-o .build/mkentprops src/build/mkentprops.c src/os.c
-.build/gluegen `for s in $src; do echo "src/$s"; done`
+# pass only .c files to gluegen (ignore .S)
+.build/gluegen `for s in $src; do if [ "${s##*.}" = "c" ]; then echo "src/$s"; fi; done`
.build/mkgamedata gamedata/engine.txt gamedata/gamelib.txt gamedata/inputsystem.txt \
gamedata/matchmaking.txt gamedata/vgui2.txt gamedata/vguimatsurface.txt gamedata/vphysics.txt
.build/mkentprops gamedata/entprops.txt
@@ -110,11 +113,6 @@ ld
$HOSTCC -O2 -g3 $warnings $stdflags -include test/test.h -o .build/bitbuf.test test/bitbuf.test.c
.build/bitbuf.test
-# XXX: skipping this test on linux for now but should enable when we can test it
-#$HOSTCC -m32 -O2 -g3 -include test/test.h -o .build/hook.test test/hook.test.c
-#.build/hook.test
-$HOSTCC -O2 -g3 $warnings $stdflags -include test/test.h -o .build/kv.test test/kv.test.c
-.build/kv.test
$HOSTCC -O2 -g3 $warnings $stdflags -include test/test.h -o .build/x86.test test/x86.test.c
.build/x86.test
diff --git a/compile.bat b/compile.bat
index 0affd05..3cd8c84 100644
--- a/compile.bat
+++ b/compile.bat
@@ -37,17 +37,17 @@ set objs=
goto :main
:cc
-for /F %%b in ("%1") do set basename=%%~nb
+for /F %%f in ("%1") do (
+ set basename=%%~nf
+ set ext=%%~xf
+)
set dmodname= -DMODULE_NAME=%basename%
:: ugly annoying special cases
if "%dmodname%"==" -DMODULE_NAME=con_" set dmodname= -DMODULE_NAME=con
if "%dmodname%"==" -DMODULE_NAME=sst" set dmodname=
-set objs=%objs% .build/%basename%.o
+set objs=%objs% .build/%basename%%ext%.o
%CC% -c -flto -mno-stack-arg-probe %cflags% %warnings% %stdflags% -I.build/include ^
--D_DLL%dmodname% -o .build/%basename%.o %1 || goto :end
-goto :eof
-
-:src
+-D_DLL%dmodname% -o .build/%basename%%ext%.o %1 || goto :end
goto :eof
:main
@@ -55,9 +55,15 @@ goto :eof
set stdflags=-std=c2x -D_CRT_SECURE_NO_WARNINGS -DWIN32_LEAN_AND_MEAN -DNOMINMAX
set src=
+:: metasrc has C sources only -> we don't attempt to run gluegen over asm files.
+:: we could also leave out chunklets, but that doesn't really matter that much.
+set metasrc=
:: funny hack to build a list conveniently, lol.
setlocal EnableDelayedExpansion
-for /f "tokens=2" %%f in ('findstr /B /C:":+ " "%~nx0"') do set src=!src! src/%%f
+for /f "tokens=2" %%f in ('findstr /B /C:":+ " "%~nx0"') do (
+ set src=!src! src/%%f
+ if "%%~xf"==".c" set metasrc=!metasrc! src/%%f
+)
setlocal DisableDelayedExpansion
:+ ac.c
:+ alias.c
@@ -65,6 +71,7 @@ setlocal DisableDelayedExpansion
:+ bind.c
:+ clientcon.c
:+ con_.c
+:+ con_.S
:+ chatrate.c
:+ chunklets/fastspin.c
:+ chunklets/msg.c
@@ -83,6 +90,7 @@ setlocal DisableDelayedExpansion
:+ gameserver.c
:+ hexcolour.c
:+ hook.c
+:+ hook.S
:+ hud.c
:+ inputhud.c
:+ kvsys.c
@@ -121,7 +129,7 @@ if %host64%==1 (
-L.build %lbcryptprimitives_host% -o .build/mkgamedata.exe src/build/mkgamedata.c src/os.c || goto :end
%HOSTCC% -fuse-ld=lld -O2 %warnings% %stdflags% -include stdbool.h ^
-L.build %lbcryptprimitives_host% -o .build/mkentprops.exe src/build/mkentprops.c src/os.c || goto :end
-.build\gluegen.exe%src% || goto :end
+.build\gluegen.exe%metasrc% || goto :end
.build\mkgamedata.exe gamedata/engine.txt gamedata/gamelib.txt gamedata/inputsystem.txt ^
gamedata/matchmaking.txt gamedata/vgui2.txt gamedata/vguimatsurface.txt gamedata/vphysics.txt || goto :end
.build\mkentprops.exe gamedata/entprops.txt || goto :end
@@ -135,7 +143,7 @@ if "%dbg%"=="1" (
) else (
set clibs=-lucrt
)
-%CC% -fuse-ld=lld -shared -flto %ldflags% -Wl,/IMPLIB:.build/sst.lib,/Brepro,/nodefaultlib ^
+%CC% -fuse-ld=lld -shared -flto %ldflags% -Wl,-implib:.build/sst.lib,-Brepro,-nodefaultlib,-safeseh:no ^
-L.build %clibs% -lkernel32 -luser32 -lbcryptprimitives -lshlwapi -ld3d9 -ldsound ^
-ltier0 -lvstdlib -lntdll -o .build/sst.dll%objs% .build/dll.res || goto :end
:: get rid of another useless file (can we just not create this???)
@@ -160,9 +168,6 @@ if exist .build\sst.old.dll del .build\sst.old.dll >nul 2>nul
%HOSTCC% -fuse-ld=lld -O2 -g %warnings% %stdflags% -include test/test.h -o .build/bitbuf.test.exe test/bitbuf.test.c || goto :end
.build\bitbuf.test.exe || goto :end
-:: special case: test must be 32-bit
-%HOSTCC% -fuse-ld=lld -m32 -O2 -g %warnings% %stdflags% -L.build -lbcryptprimitives -include test/test.h -o .build/hook.test.exe test/hook.test.c || goto :end
-.build\hook.test.exe || goto :end
%HOSTCC% -fuse-ld=lld -O2 -g %warnings% %stdflags% -include test/test.h -o .build/x86.test.exe test/x86.test.c || goto :end
.build\x86.test.exe || goto :end
diff --git a/src/asm.h b/src/asm.h
new file mode 100644
index 0000000..72d5041
--- /dev/null
+++ b/src/asm.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright © Michael Smith <mikesmiffy128@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.
+ */
+
+#ifndef INC_ASM_H
+#define INC_ASM_H
+
+/*
+ * 32-bit Windows-specific symbol mangling for global definitions. Note: does
+ * not apply to stdcall things; which have additional decorations.
+ */
+#if defined(_WIN32) && !defined(_WIN64)
+#define ASM_MANGLE(x) _##x
+#define ASM_MANGLE_STR(x) "_" x
+#else
+#define ASM_MANGLE(x) x
+#define ASM_MANGLE_STR(x) x
+#endif
+
+/* Special section definition for hook trampolines, self-modifying code, etc. */
+#define ASM_RWX_SECTION rwx
+#define ASM_RWX_SECTION_STR "rwx"
+#define ASM_RWX_SECTION_FLAGS "bwx"
+
+#endif
+
+// vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/src/con_.S b/src/con_.S
new file mode 100644
index 0000000..0c6ec2d
--- /dev/null
+++ b/src/con_.S
@@ -0,0 +1,45 @@
+/*
+ * Copyright © Michael Smith <mikesmiffy128@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 "asm.h"
+
+// On Windows, _con_colourmsg is subject to selfmod() in con_.c which overwrites
+// it with another implementation on OE; put it in rwx. On Linux, .text is fine.
+#ifdef _WIN32
+.section ASM_RWX_SECTION, ASM_RWX_SECTION_FLAGS
+#else
+.section .text, "xr"
+#endif
+.globl ASM_MANGLE(_con_colourmsg)
+.p2align 4
+ASM_MANGLE(_con_colourmsg):
+ // For some reason the assembler arbitrarily refuses to put instructions in
+ // a section marked as containing uninitialised data. So, we have to list
+ // out the instruction bytes manually here:
+ // mov eax, [_con_iface]
+ .byte 0xA1
+ .long ASM_MANGLE(_con_iface)
+ // mov [esp + 4], eax // put coniface in the empty stack slot
+ .byte 0x89
+ .byte 0x44
+ .byte 0x24
+ .byte 0x04
+ // jmp dword ptr _con_colourmsgf // jump to the real function
+ .byte 0xFF
+ .byte 0x25
+ .long ASM_MANGLE(_con_colourmsgf)
+
+// vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/src/con_.c b/src/con_.c
index ff71383..6d5ce03 100644
--- a/src/con_.c
+++ b/src/con_.c
@@ -75,26 +75,11 @@ DECL_VFUNC(struct ICvar, void *, FindVar_nonp2, 12, const char *)
DECL_VFUNC(struct ICvar, void *, FindVar_OE, 7, const char *)
#endif
-static struct ICvar *coniface;
-static void *colourmsgf;
+struct ICvar *_con_iface; // extern for con_.S
+void *_con_colourmsgf; // "
-#ifdef _WIN32
-#pragma section("selfmod", execute)
-__attribute((used, section("selfmod"), noinline))
-#endif
-asm_only void _con_colourmsg(void *dummy, const struct rgba *c,
- const char *fmt, ...) {
- // NE: ConsoleColorPrintf is virtual, so the dummy param is a carve-out for
- // `this` (which is coniface).
- __asm volatile (
- "mov eax, %0\n"
- "mov [esp + 4], eax\n" // put coniface in the empty stack slot
- "jmp dword ptr %1\n" // jump to the real function
- :
- : "m" (coniface), "m" (colourmsgf)
- : "eax", "memory"
- );
-}
+// this is defined in con_.S, in order to set the proper section attributes.
+void _con_colourmsg(void *dummy, const struct rgba *c, const char *fmt, ...);
#ifdef _WIN32
// this function is defined as data because we'll be using it to self-modify the
@@ -114,23 +99,11 @@ asm_only static void _con_colourmsg_OE(void *dummy, const struct rgba *c,
"sub esp, 4\n" // pad the stack back out for the caller
"jmp ebx\n" // return to saved address
:
- : "m" (coniface), "m" (colourmsgf)
+ : "m" (_con_iface), "m" (_con_colourmsgf)
: "eax", "ebx", "memory"
);
}
#define SELFMOD_LEN 15 // above instructions assemble to this many bytes!
-
-static bool selfmod() {
- if (!os_mprot((void *)_con_colourmsg, SELFMOD_LEN, PAGE_EXECUTE_READWRITE)) {
- errmsg_errorsys("couldn't make memory writable");
- return false;
- }
- memcpy((void *)&_con_colourmsg, (void *)&_con_colourmsg_OE, SELFMOD_LEN);
- if (!os_mprot((void *)_con_colourmsg, SELFMOD_LEN, PAGE_EXECUTE_READ)) {
- errmsg_warnsys("couldn't restore self-modified page to read-only");
- }
- return true;
-}
#endif
static void VCALLCONV dtor(void *_) {} // we don't use constructors/destructors
@@ -244,13 +217,13 @@ static void VCALLCONV ChangeStringValue(struct con_var *this, const char *s,
float oldf) {
char *old = alloca(this->v2.strlen);
ChangeStringValue_common(this, &this->v2, old, s);
- CallGlobalChangeCallbacks(coniface, this, old, oldf);
+ CallGlobalChangeCallbacks(_con_iface, this, old, oldf);
}
#ifdef _WIN32
static void VCALLCONV ChangeStringValue_OE(struct con_var *this, const char *s) {
char *old = alloca(this->v1.strlen);
ChangeStringValue_common(this, &this->v1, old, s);
- CallGlobalChangeCallbacks_OE(coniface, this, old);
+ CallGlobalChangeCallbacks_OE(_con_iface, this, old);
}
#endif
@@ -446,7 +419,7 @@ void con_regvar(struct con_var *v) {
struct con_var_common *c = con_getvarcommon(v);
c->strval = extmalloc(c->strlen); // note: _DEF_CVAR() sets strlen member
memcpy(c->strval, c->defaultval, c->strlen);
- RegisterConCommand(coniface, v);
+ RegisterConCommand(_con_iface, v);
}
void con_regcmd(struct con_cmd *c) {
@@ -454,7 +427,7 @@ void con_regcmd(struct con_cmd *c) {
if_hot (!GAMETYPE_MATCHES(OE)) if (c->base.flags & CON_INIT_HIDDEN) {
c->base.flags = (c->base.flags & ~CON_INIT_HIDDEN) | _CON_NE_HIDDEN;
}
- RegisterConCommand(coniface, c);
+ RegisterConCommand(_con_iface, c);
}
void con_hide(struct con_cmdbase *b) {
@@ -504,7 +477,7 @@ static bool find_Con_ColorPrintf() {
for (uchar *p = insns; p - insns < 320;) {
if (p[0] == X86_PUSHECX && p[1] == X86_PUSHIW && p[6] == X86_CALL &&
p[11] == X86_ALUMI8S && p[12] == X86_MODRM(3, 0, 4)) {
- colourmsgf = p + 11 + mem_loads32(p + 7);
+ _con_colourmsgf = p + 11 + mem_loads32(p + 7);
return true;
}
NEXT_INSN(p, "Con_ColorPrintf function");
@@ -529,39 +502,39 @@ static void badver() {
}
bool con_detect(int pluginver) {
- if (coniface = factory_engine("VEngineCvar007", 0)) {
+ if (_con_iface = factory_engine("VEngineCvar007", 0)) {
// GENIUS HACK (BUT STILL BAD): Portal 2 has everything in ICvar shifted
// down 3 places due to the extra stuff in IAppSystem. This means that
// if we look up the Portal 2-specific cvar using FindCommandBase, it
// *actually* calls the const-overloaded FindVar on other branches,
// which just happens to still work fine. From there, we can figure out
// the actual ABI to use to avoid spectacular crashes.
- if (FindCommandBase_p2(coniface, "portal2_square_portals")) {
+ if (FindCommandBase_p2(_con_iface, "portal2_square_portals")) {
_gametype_tag |= _gametype_tag_Portal2;
return true;
}
- if (FindCommand_nonp2(coniface, "l4d2_snd_adrenaline")) {
+ if (FindCommand_nonp2(_con_iface, "l4d2_snd_adrenaline")) {
// while we're here, also distinguish Survivors, the stupid Japanese
// arcade game a few people seem to care about for some reason
// (which for some other reason also has some vtable changes)
- if (FindVar_nonp2(coniface, "avatarbasemodel")) {
+ if (FindVar_nonp2(_con_iface, "avatarbasemodel")) {
_gametype_tag |= _gametype_tag_L4DS;
}
else {
_gametype_tag |= _gametype_tag_L4D2;
}
- if (FindVar_nonp2(coniface, "sv_zombie_touch_trigger_delay")) {
+ if (FindVar_nonp2(_con_iface, "sv_zombie_touch_trigger_delay")) {
_gametype_tag |= _gametype_tag_L4D2_2125plus;
}
- if (FindVar_nonp2(coniface, "director_cs_weapon_spawn_chance")) {
+ if (FindVar_nonp2(_con_iface, "director_cs_weapon_spawn_chance")) {
_gametype_tag |= _gametype_tag_TheLastStand;
}
return true;
}
- if (FindVar_nonp2(coniface, "z_difficulty")) {
+ if (FindVar_nonp2(_con_iface, "z_difficulty")) {
_gametype_tag |= _gametype_tag_L4D1;
// Crash Course update
- if (FindCommand_nonp2(coniface, "director_log_scavenge_items")) {
+ if (FindCommand_nonp2(_con_iface, "director_log_scavenge_items")) {
_gametype_tag |= _gametype_tag_L4D1_1015plus;
// seems there was some code shuffling in the Mac update (1022).
// this update came out like 2-3 weeks after The Sacrifice
@@ -575,43 +548,45 @@ bool con_detect(int pluginver) {
helpuserhelpus(pluginver, '7');
return false;
}
- if (coniface = factory_engine("VEngineCvar004", 0)) {
+ if (_con_iface = factory_engine("VEngineCvar004", 0)) {
// TODO(compat): are there any cases where 004 is incompatible? could
// this crash? find out!
if (pluginver == 3) _gametype_tag |= _gametype_tag_2013;
else _gametype_tag |= _gametype_tag_OrangeBox;
// detect Portal 1 versions while we're here...
- if (FindCommand_nonp2(coniface, "upgrade_portalgun")) {
+ if (FindCommand_nonp2(_con_iface, "upgrade_portalgun")) {
_gametype_tag |= _gametype_tag_Portal1;
- if (!FindVar_nonp2(coniface, "tf_escort_score_rate")) {
+ if (!FindVar_nonp2(_con_iface, "tf_escort_score_rate")) {
_gametype_tag |= _gametype_tag_Portal1_3420;
}
}
- else if (FindCommand_nonp2(coniface, "phys_swap")) {
+ else if (FindCommand_nonp2(_con_iface, "phys_swap")) {
_gametype_tag |= _gametype_tag_HL2series;
}
return true;
}
- if (coniface = factory_engine("VEngineCvar003", 0)) {
+ if (_con_iface = factory_engine("VEngineCvar003", 0)) {
#ifdef _WIN32 // there's no OE on linux!
_gametype_tag |= _gametype_tag_OE;
// for deletion/unlinking on unload, we need an indirect linked list
// pointer. calling GetCommands gives us a direct pointer. so we have to
// actually pull out the indirect pointer from the actual asm lol.
- if (!find_linkedlist((uchar *)VFUNC(coniface, GetCommands_OE))) {
+ if (!find_linkedlist((uchar *)VFUNC(_con_iface, GetCommands_OE))) {
errmsg_errorx("couldn't find command list pointer");
return false;
}
if (!find_argcargv()) return false;
if (!find_Con_ColorPrintf()) return false;
- if (!selfmod()) return false;
+ // note: _con_colourmsg is already RWX, just overwrite it.
+ memcpy((void *)&_con_colourmsg, (void *)&_con_colourmsg_OE,
+ SELFMOD_LEN);
// NOTE: the default static struct layout is for NE; immediately after
// engineapi init finishes, the generated glue code will shunt
// everything along for OE if required, in shuntvars(). since all the
// gluegen code is currently hooked up in sst.c this is a little bit
// annoyingly removed from here. not sure how to do it better, sorry.
off_cvar_common = offsetof(struct con_var, v1);
- if (FindVar_OE(coniface, "mm_ai_facehugger_enablehugeattack")) {
+ if (FindVar_OE(_con_iface, "mm_ai_facehugger_enablehugeattack")) {
_gametype_tag |= _gametype_tag_DMoMM;
}
return true;
@@ -632,7 +607,7 @@ bool con_detect(int pluginver) {
}
static int *find_host_initialized() {
- const uchar *insns = colourmsgf;
+ const uchar *insns = _con_colourmsgf;
for (const uchar *p = insns; p - insns < 32;) {
// cmp byte ptr [<pointer>], <value>
if (p[0] == X86_ALUMI8 && p[1] == X86_MODRM(0, 7, 5)) {
@@ -653,8 +628,8 @@ void con_init() {
if (host_initialized && *host_initialized == 0) *host_initialized = 1;
}
else {
- colourmsgf = coniface->vtable[vtidx_ConsoleColorPrintf];
- dllid = AllocateDLLIdentifier(coniface);
+ _con_colourmsgf = _con_iface->vtable[vtidx_ConsoleColorPrintf];
+ dllid = AllocateDLLIdentifier(_con_iface);
}
void **pc = _con_vtab_cmd + 3 + NVDTOR, **pv = _con_vtab_var + 3 + NVDTOR,
@@ -770,11 +745,11 @@ void con_disconnect() {
return;
}
#endif
- UnregisterConCommands(coniface, dllid);
+ UnregisterConCommands(_con_iface, dllid);
}
struct con_var *con_findvar(const char *name) {
- return FindVar(coniface, name);
+ return FindVar(_con_iface, name);
}
struct con_cmd *con_findcmd(const char *name) {
@@ -790,7 +765,7 @@ struct con_cmd *con_findcmd(const char *name) {
return 0;
}
#endif
- return FindCommand(coniface, name);
+ return FindCommand(_con_iface, name);
}
// NOTE: getters here still go through the parent pointer although we stopped
diff --git a/src/hook.S b/src/hook.S
new file mode 100644
index 0000000..7baa856
--- /dev/null
+++ b/src/hook.S
@@ -0,0 +1,26 @@
+/*
+ * Copyright © Michael Smith <mikesmiffy128@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 "asm.h"
+#include "rwx.h"
+
+#define SPACE 2048 // NOTE: MUST match SPACE in hook.c!
+
+.section RWX_SECTION, RWX_SECTION_FLAGS
+.globl ASM_MANGLE(_hook_trampolinespc)
+.comm ASM_MANGLE(_hook_trampolinespc), SPACE
+
+// vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/src/hook.c b/src/hook.c
index 0355c18..baaa710 100644
--- a/src/hook.c
+++ b/src/hook.c
@@ -28,14 +28,9 @@
// Almost certainly breaks in some weird cases. Oh well! Most of the time,
// vtable hooking is more reliable, this is only for, uh, emergencies.
-static _Alignas(4096) uchar trampolines[4096];
-static uchar *curtrampoline = trampolines;
-
-bool hook_init() {
- // PE doesn't support rwx sections, not sure about ELF. Meh, just set it
- // here instead.
- return os_mprot(trampolines, 4096, PAGE_EXECUTE_READWRITE);
-}
+#define SPACE 2048 // NOTE: MUST match SPACE in hook.S!
+extern uchar _hook_trampolinespc[]; // defined in hook.S
+static uchar *curtrampoline = _hook_trampolinespc;
struct hook_inline_prep_ret hook_inline_prep(void *func, void **trampoline) {
uchar *p = func;
@@ -61,7 +56,8 @@ struct hook_inline_prep_ret hook_inline_prep(void *func, void **trampoline) {
len += ilen;
if (len >= 5) {
// we should have statically made trampoline buffer size big enough
- assume(curtrampoline - trampolines < sizeof(trampolines) - len - 6);
+ assume(curtrampoline - (uchar *)_hook_trampolinespc <
+ SPACE - len - 6);
*curtrampoline = len; // stuff length in there for quick unhooking
uchar *newtrampoline = curtrampoline + 1;
curtrampoline += len + 6;
diff --git a/src/hook.h b/src/hook.h
index 0aeae73..26796e2 100644
--- a/src/hook.h
+++ b/src/hook.h
@@ -22,8 +22,6 @@
#include "feature.h"
#include "langext.h"
-bool hook_init();
-
/*
* Replaces a vtable entry with a target function and returns the original
* function.
diff --git a/src/sst.c b/src/sst.c
index 17e020f..64dd303 100644
--- a/src/sst.c
+++ b/src/sst.c
@@ -540,10 +540,6 @@ static void hook_plugin_unload_cbv2(struct con_cmdargs *args) {
}
static bool do_load(ifacefactory enginef, ifacefactory serverf) {
- if_cold (!hook_init()) {
- errmsg_warnsys("couldn't set up memory for function hooking");
- return false;
- }
factory_engine = enginef; factory_server = serverf;
if_cold (!engineapi_init(ifacever)) return false;
if (GAMETYPE_MATCHES(OE)) shuntvars(); // see also comment in con_detect()
diff --git a/test/hook.test.c b/test/hook.test.c
deleted file mode 100644
index 6a9a7b4..0000000
--- a/test/hook.test.c
+++ /dev/null
@@ -1,71 +0,0 @@
-/* This file is dedicated to the public domain. */
-
-{.desc = "inline function hooking"};
-
-#ifdef _WIN32
-
-#include "../src/chunklets/x86.c"
-#include "../src/hook.c"
-#include "../src/os.c"
-
-#include <stdarg.h>
-#include <stdio.h>
-#include <string.h>
-
-// stub
-void con_warn(const char *msg, ...) {
- va_list l;
- va_start(l, msg);
- vfprintf(stderr, msg, l);
- va_end(l);
-}
-
-typedef int (*testfunc)(int, int);
-
-__attribute__((noinline)) static int func1(int a, int b) { return a + b; }
-static int (*orig_func1)(int, int);
-static int hook1(int a, int b) { return orig_func1(a, b) + 5; }
-
-__attribute__((noinline)) static int func2(int a, int b) { return a - b; }
-static int (*orig_func2)(int, int);
-static int hook2(int a, int b) { return orig_func2(a, b) + 5; }
-
-// basic reimplementation of old API to support existing test cases.
-// XXX: we could probably have tests at the boundaries of the new API too,
-// although the current tests are only testing for regressions in x86 jmp logic.
-static inline void *test_hook_inline(void *func, void *target) {
- void *trampoline;
- struct hook_inline_prep_ret prep = hook_inline_prep(func, &trampoline);
- if (prep.err) return 0;
- if (!hook_inline_mprot(prep.prologue)) return 0;
- hook_inline_commit(prep.prologue, target);
- return trampoline;
-}
-
-TEST("Inline hooks should be able to wrap the original function") {
- if (!hook_init()) return false;
- orig_func1 = (testfunc)test_hook_inline((void *)&func1, (void *)&hook1);
- if (!orig_func1) return false;
- return func1(5, 5) == 15;
-}
-
-TEST("Inline hooks should be removable again") {
- if (!hook_init()) return false;
- orig_func1 = (testfunc)test_hook_inline((void *)&func1, (void *)&hook1);
- if (!orig_func1) return false;
- unhook_inline((void *)orig_func1);
- return func1(5, 5) == 10;
-}
-
-TEST("Multiple functions should be able to be inline-hooked at once") {
- if (!hook_init()) return false;
- orig_func1 = (testfunc)test_hook_inline((void *)&func1, (void *)&hook1);
- if (!orig_func1) return false;
- orig_func2 = (testfunc)test_hook_inline((void *)&func2, (void *)&hook2);
- if (!orig_func2) return false;
- return func2(5, 5) == 5;
-}
-
-#endif
-
-// vi: sw=4 ts=4 noet tw=80 cc=80