diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/asm.h | 39 | ||||
| -rw-r--r-- | src/con_.S | 45 | ||||
| -rw-r--r-- | src/con_.c | 93 | ||||
| -rw-r--r-- | src/hook.S | 26 | ||||
| -rw-r--r-- | src/hook.c | 14 | ||||
| -rw-r--r-- | src/hook.h | 2 | ||||
| -rw-r--r-- | src/sst.c | 4 |
7 files changed, 149 insertions, 74 deletions
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 @@ -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 @@ -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; @@ -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. @@ -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() |
