From 99f7bb3d39043f74d03b29dc7d6a3f08570c544c Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Mon, 1 Dec 2025 21:37:31 +0000 Subject: Allow mutating argv in command callbacks and hooks --- src/con_.c | 6 +++--- src/con_.h | 51 ++++++++++++++++++++++++++++++++------------------- src/sst.c | 8 ++++---- 3 files changed, 39 insertions(+), 26 deletions(-) (limited to 'src') diff --git a/src/con_.c b/src/con_.c index 90cc0fd..23291d7 100644 --- a/src/con_.c +++ b/src/con_.c @@ -186,7 +186,7 @@ static bool VCALLCONV ClampValue_OE(struct con_var *this, float *f) { // global argc/argv. also OE only. extern for use in sst.c plugin_unload hook // as well as in DEF_CCMD_COMPAT_HOOK int *_con_argc; -const char *(*_con_argv)[80]; +const char **_con_argv; // note: points to array of 80 static bool find_argcargv() { const uchar *insns = (const uchar *)VFUNC(engclient, Cmd_Argv); @@ -217,12 +217,12 @@ int VCALLCONV AutoCompleteSuggest(struct con_cmd *this, const char *partial, bool VCALLCONV CanAutoComplete(struct con_cmd *this) { return false; } -void VCALLCONV Dispatch(struct con_cmd *this, const struct con_cmdargs *args) { +void VCALLCONV Dispatch(struct con_cmd *this, struct con_cmdargs *args) { this->cb(args->argc, args->argv); } #ifdef _WIN32 void VCALLCONV Dispatch_OE(struct con_cmd *this) { - this->cb(*_con_argc, *_con_argv); + this->cb(*_con_argc, _con_argv); } #endif diff --git a/src/con_.h b/src/con_.h index 8bef5d0..eb03642 100644 --- a/src/con_.h +++ b/src/con_.h @@ -73,10 +73,10 @@ enum { }; /* A callback function invoked by SST to execute its own commands. */ -typedef void (*con_cmdcb)(int argc, const char *const *argv); +typedef void (*con_cmdcb)(int argc, const char **argv); /* A callback function used by most commands in most versions of the engine. */ -typedef void (*con_cmdcbv2)(const struct con_cmdargs *cmd); +typedef void (*con_cmdcbv2)(struct con_cmdargs *cmd); /* An older style of callback function used by some old commands, and in OE. */ typedef void (*con_cmdcbv1)(); @@ -339,12 +339,12 @@ extern struct _con_vtab_iconvar_wrap { /* * Defines a console command with the handler function body immediately * following the macro (like in Source itself). The function takes the implicit - * arguments `int argc` and `const char *const *argv` for command arguments. + * arguments `int argc` and `const char **argv` for command arguments. */ #define DEF_CCMD_HERE(name, desc, flags) \ - static void _cmdf_##name(int argc, const char *const *argv); \ + static void _cmdf_##name(int argc, const char **argv); \ _DEF_CCMD(name, name, desc, _cmdf_##name, flags) \ - static void _cmdf_##name(int argc, const char *const *argv) \ + static void _cmdf_##name(int argc, const char **argv) \ /* { body here } */ /* @@ -396,11 +396,6 @@ extern struct _con_vtab_iconvar_wrap { * command handler defined with DEF_CCMD_HERE. Calling the original command * handler can be done using orig_##name##_cb, passing through argc and argv. * - * Note that argc and argv MUST remain unmodified, as not all callback - * interfaces pass arguments through the callback and so attempting to change - * these parameters could cause unexpected or inconsistent behaviour across - * engine versions. - * * In some cases, a command will be defined to take no arguments, in which case * argc will be zero and argv will be null. In these cases, the parameters * should still be passed through to the orig_ function, as this ensures @@ -411,10 +406,28 @@ extern struct _con_vtab_iconvar_wrap { con_cmdcbv1 v1; \ con_cmdcbv2 v2; \ } _orig_##name##_cb; \ - static void _orig_##name##_cbv1(int argc, const char *const *argv) { \ - _orig_##name##_cb.v1(); \ + static void _orig_##name##_cbv1(int argc, const char **argv) { \ + extern int *_con_argc; \ + extern const char **_con_argv; \ + int _orig_argc = *_con_argc; \ + *_con_argc = argc; \ + if (argv != _con_argv) { \ + /* args can be passed through as-is, or modified in place, however + here we have a whole different array, so we have to copy it out + and back to avoid confusing side effects for the caller. */ \ + /* XXX: not bothering with the null term here; should we be? */ \ + const char *_orig_argv[80]; \ + memcpy(_orig_argv, _con_argv, _orig_argc * sizeof(*argv)); \ + memcpy(_con_argv, argv, argc * sizeof(*argv)); \ + _orig_##name##_cb.v1(); \ + memcpy(_con_argv, _orig_argv, _orig_argc * sizeof(*argv)); \ + } \ + else { \ + _orig_##name##_cb.v1(); \ + } \ + *_con_argc = _orig_argc; \ } \ - static void _orig_##name##_cbv2(int argc, const char *const *argv) { \ + static void _orig_##name##_cbv2(int argc, const char **argv) { \ struct con_cmdargs args; \ args.argc = argc; \ /* XXX: having to copy argv sucks, but can't see how to avoid without @@ -422,14 +435,14 @@ extern struct _con_vtab_iconvar_wrap { for (int i = 0; i < argc; ++i) args.argv[i] = argv[i]; \ _orig_##name##_cb.v2(&args); \ } \ - static void (*orig_##name##_cb)(int argc, const char *const *argv); \ - static void _hook_##name##_cb(int argc, const char *const *argv); \ + static void (*orig_##name##_cb)(int argc, const char **argv); \ + static void _hook_##name##_cb(int argc, const char **argv); \ static void _hook_##name##_cbv1() { \ extern int *_con_argc; \ - extern const char *(*_con_argv)[80]; \ - _hook_##name##_cb(*_con_argc, *_con_argv); \ + extern const char **_con_argv; \ + _hook_##name##_cb(*_con_argc, _con_argv); \ } \ - static void _hook_##name##_cbv2(const struct con_cmdargs *args) { \ + static void _hook_##name##_cbv2(struct con_cmdargs *args) { \ _hook_##name##_cb(args->argc, args->argv); \ } \ static void hook_##name##_cb(struct con_cmd *cmd) { \ @@ -446,7 +459,7 @@ extern struct _con_vtab_iconvar_wrap { static void unhook_##name##_cb(struct con_cmd *cmd) { \ cmd->cb_v1 = _orig_##name##_cb.v1; \ } \ - static void _hook_##name##_cb(int argc, const char *const *argv) /* ... */ + static void _hook_##name##_cb(int argc, const char **argv) /* ... */ /* * These functions register a command or variable, respectively, defined with diff --git a/src/sst.c b/src/sst.c index 8bdb56c..3ed92ee 100644 --- a/src/sst.c +++ b/src/sst.c @@ -466,7 +466,7 @@ enum unload_action { UNLOAD_SELF, UNLOAD_OTHER }; -static int hook_plugin_unload_common(int argc, const char *const *argv) { +static int hook_plugin_unload_common(int argc, const char **argv) { if (argc > 1) { if (!CHECK_AllowPluginLoading(false)) return UNLOAD_SKIP; if (!*argv[1]) { @@ -515,8 +515,8 @@ static int hook_plugin_unload_common(int argc, const char *const *argv) { static void hook_plugin_unload_cbv1() { extern int *_con_argc; - extern const char *(*_con_argv)[80]; - int action = hook_plugin_unload_common(*_con_argc, *_con_argv); + extern const char **_con_argv; + int action = hook_plugin_unload_common(*_con_argc, _con_argv); switch_exhaust_enum(unload_action, action) { case UNLOAD_SKIP: return; @@ -527,7 +527,7 @@ static void hook_plugin_unload_cbv1() { EMIT_PluginUnloaded(); } } -static void hook_plugin_unload_cbv2(const struct con_cmdargs *args) { +static void hook_plugin_unload_cbv2(struct con_cmdargs *args) { int action = hook_plugin_unload_common(args->argc, args->argv); switch_exhaust_enum(unload_action, action) { case UNLOAD_SKIP: -- cgit v1.2.3-54-g00ecf