From 2faff26f69150bd8b672cb96556c4028a292cdd5 Mon Sep 17 00:00:00 2001 From: cloudwalk Date: Thu, 28 May 2020 13:53:30 +0000 Subject: [PATCH] Implement experimental hook-like system, with working example. This system allows you to call functions that may or may not exist at runtime and compile-time. You create hook_t pointers and register them during initialization, and point them to a function. If they're not registered, no problem. This could potentially allow different subsystems to function independently of each other, such as the client and server. It's using a union to allow functions to have any return type they want. However, for obvious reasons, functions cannot have any parameters they want and must accept a pointer to the union type where the actual args are stored. This is experimental and there may be bugs, but the current working example should be stable. git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@12603 d7cf8633-e32d-0410-b094-e92efae38249 --- cmd.c | 4 ++- csprogs.c | 6 ++-- csprogs.h | 2 +- hook.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++ hook.h | 56 ++++++++++++++++++++++++++++++++ host.c | 4 +++ makefile.inc | 1 + qtypes.h | 2 ++ quakedef.h | 1 + 9 files changed, 164 insertions(+), 4 deletions(-) create mode 100644 hook.c create mode 100644 hook.h diff --git a/cmd.c b/cmd.c index 352d8c5b..a06f39ce 100644 --- a/cmd.c +++ b/cmd.c @@ -2018,6 +2018,8 @@ A complete command line has been parsed, so try to execute it FIXME: lookupnoadd the token to speed search? ============ */ +extern hook_t *csqc_concmd; + void Cmd_ExecuteString (cmd_state_t *cmd, const char *text, cmd_source_t src, qboolean lockmutex) { int oldpos; @@ -2039,7 +2041,7 @@ void Cmd_ExecuteString (cmd_state_t *cmd, const char *text, cmd_source_t src, qb { if (!strcasecmp(cmd->argv[0], func->name)) { - if (func->csqcfunc && CL_VM_ConsoleCommand(text)) //[515]: csqc + if (func->csqcfunc && Hook_Call(csqc_concmd, text)->bval) //[515]: csqc goto done; break; } diff --git a/csprogs.c b/csprogs.c index 4f47741b..c0c4eb42 100644 --- a/csprogs.c +++ b/csprogs.c @@ -502,7 +502,9 @@ qboolean CL_VM_UpdateView (double frametime) return true; } -qboolean CL_VM_ConsoleCommand (const char *cmd) +hook_t *csqc_concmd; + +qboolean CL_VM_ConsoleCommand (hook_val_t *arg) { prvm_prog_t *prog = CLVM_prog; int restorevm_tempstringsbuf_cursize; @@ -515,7 +517,7 @@ qboolean CL_VM_ConsoleCommand (const char *cmd) PRVM_clientglobalfloat(time) = cl.time; PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[cl.playerentity]; restorevm_tempstringsbuf_cursize = prog->tempstringsbuf.cursize; - PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(prog, cmd); + PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(prog, arg->str); prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_ConsoleCommand), "QC function CSQC_ConsoleCommand is missing"); prog->tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; r = CSQC_RETURNVAL != 0; diff --git a/csprogs.h b/csprogs.h index dff44d74..46b71eba 100644 --- a/csprogs.h +++ b/csprogs.h @@ -91,7 +91,7 @@ void CL_VM_ShutDown(void); void CL_VM_UpdateIntermissionState(int intermission); void CL_VM_UpdateShowingScoresState(int showingscores); qboolean CL_VM_InputEvent(int eventtype, float x, float y); -qboolean CL_VM_ConsoleCommand(const char *cmd); +qboolean CL_VM_ConsoleCommand(hook_val_t *arg); void CL_VM_UpdateDmgGlobals(int dmg_take, int dmg_save, vec3_t dmg_origin); void CL_VM_UpdateIntermissionState(int intermission); qboolean CL_VM_Event_Sound(int sound_num, float volume, int channel, float attenuation, int ent, vec3_t pos, int flags, float speed); diff --git a/hook.c b/hook.c new file mode 100644 index 00000000..344f9e9b --- /dev/null +++ b/hook.c @@ -0,0 +1,92 @@ +/* +Copyright (C) 2020 Cloudwalk + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "quakedef.h" +#include "hook.h" + +mempool_t *hooks; + +hook_t *_Hook_Register(hook_t *hook, const char *name, void *func, unsigned int argc) +{ + if (hook) { + Con_Printf("Hook %s already registered\n",hook->name); + } else { + hook = (hook_t *)Mem_Alloc(hooks, sizeof(hook_t)); + hook->name = Mem_Alloc(hooks, strlen(name) + 1); + hook->arg = Mem_Alloc(hooks, sizeof(hook_val_t) * argc); + + memcpy(hook->name, name, strlen(name) + 1); + hook->func = func; + hook->argc = argc; + } + return hook; +} + +// Needs NULL pad to know when va_list ends. +hook_val_t *_Hook_Call(hook_t *hook, ... ) +{ + uintptr_t arg_ptr; // Align to platform size + va_list arg_list; + unsigned int i = 0; + + if(!hook) + return (hook_val_t *)NULL; + + va_start(arg_list, hook); + + arg_ptr = va_arg(arg_list,intptr_t); + + if((void *)arg_ptr && !hook->argc) + goto overflow; + + // Loop until we encounter that NULL pad, but stop if we overflow. + while ((void *)arg_ptr != NULL && i != hook->argc) + { + if (i > hook->argc) + goto overflow; + hook->arg[i].val = arg_ptr; + arg_ptr = va_arg(arg_list,intptr_t); + i++; + } + + va_end(arg_list); + + // Should be fairly obvious why it's bad if args don't match + if(i != hook->argc) + goto underflow; + // Call it + hook->ret.uval = (uintptr_t)hook->func(hook->arg); + + if (hook->ret.val) + return &hook->ret; + return (hook_val_t *)NULL; + +underflow: + Sys_Error("Hook_Call: Attempt to call hook '%s' with incorrect number of arguments. Got %i, expected %i\n", hook->name, i, hook->argc); +overflow: + Sys_Error("Hook_Call: Stack overflow calling hook '%s' (argc = %u)\n", hook->name, hook->argc); + +} + +void Hook_Init(void) +{ + hooks = Mem_AllocPool("hooks",0,NULL); + return; +} \ No newline at end of file diff --git a/hook.h b/hook.h new file mode 100644 index 00000000..d3a56fd8 --- /dev/null +++ b/hook.h @@ -0,0 +1,56 @@ +/* +Copyright (C) 2020 Cloudwalk + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +// hook.h + +#ifndef HOOK_H +#define HOOK_H + +typedef union hook_val_s +{ + intptr_t val; + uintptr_t uval; + void *ptr; + char *str; + int ival; + unsigned int uival; + double fval; + qboolean bval; +} hook_val_t; + +typedef struct hook_s +{ + char *name; + hook_val_t *(*func)(hook_val_t *hook); + hook_val_t *arg; + hook_val_t ret; + unsigned int argc; +} hook_t; + +hook_t *_Hook_Register(hook_t *hook, const char *name, void *func, unsigned int argc); +hook_val_t *_Hook_Call(hook_t *hook, ... ); +void Hook_Init(void); +void Hook_Shutdown(void); + +// For your convenience +#define Hook_Register(hook, func, argc) _Hook_Register(hook, #hook, func, argc) +#define Hook_Call(hook, ... ) _Hook_Call(hook, __VA_ARGS__, NULL) + +#endif \ No newline at end of file diff --git a/host.c b/host.c index 03375f69..46453fd1 100644 --- a/host.c +++ b/host.c @@ -1162,6 +1162,8 @@ void Host_UnlockSession(void) } } +extern hook_t *csqc_concmd; + /* ==================== Host_Init @@ -1229,6 +1231,8 @@ static void Host_Init (void) // initialize memory subsystem cvars/commands Memory_Init_Commands(); + Hook_Init(); + csqc_concmd = Hook_Register(csqc_concmd,CL_VM_ConsoleCommand,1); // initialize console and logging and its cvars/commands Con_Init(); diff --git a/makefile.inc b/makefile.inc index 39976e83..9bcac173 100644 --- a/makefile.inc +++ b/makefile.inc @@ -107,6 +107,7 @@ OBJ_COMMON= \ gl_rsurf.o \ gl_textures.o \ hmac.o \ + hook.o \ host.o \ host_cmd.o \ image.o \ diff --git a/qtypes.h b/qtypes.h index d4fa148e..65218999 100644 --- a/qtypes.h +++ b/qtypes.h @@ -2,6 +2,8 @@ #ifndef QTYPES_H #define QTYPES_H +#include + #ifndef __cplusplus #ifdef _MSC_VER typedef enum {false, true} bool; diff --git a/quakedef.h b/quakedef.h index 8b9ea4d8..d07a93a5 100644 --- a/quakedef.h +++ b/quakedef.h @@ -376,6 +376,7 @@ extern char engineversion[128]; #include "sys.h" #include "vid.h" #include "mathlib.h" +#include "hook.h" #include "r_textures.h" -- 2.39.2