diff options
| author | 2023-05-05 00:04:43 +0100 | |
|---|---|---|
| committer | 2023-05-05 00:06:17 +0100 | |
| commit | 3ff6d47277fc0e39053584d8889240cab446a72a (patch) | |
| tree | 03c26795061fc472d2224efefa283af6b42fe3e0 | |
| parent | ea752466846c129a0910e47d34d725af1aea5d84 (diff) | |
| download | sst-3ff6d47277fc0e39053584d8889240cab446a72a.tar.gz sst-3ff6d47277fc0e39053584d8889240cab446a72a.zip | |
Implement APIs to control demo recording
This is a surprise tool that will help us later!
| -rw-r--r-- | gamedata/engine.kv | 1 | ||||
| -rw-r--r-- | src/demorec.c | 61 | ||||
| -rw-r--r-- | src/demorec.h | 51 | 
3 files changed, 110 insertions, 3 deletions
| diff --git a/gamedata/engine.kv b/gamedata/engine.kv index 72243b4..58511db 100644 --- a/gamedata/engine.kv +++ b/gamedata/engine.kv @@ -14,6 +14,7 @@ vtidx_CallGlobalChangeCallbacks { default 20 L4Dx 18 Portal2 21 }  vtidx_ConsoleColorPrintf { OrangeBoxbased 23 L4Dx 21 Portal2 24 }  // CDemoRecorder +vtidx_StartRecording 2  vtidx_SetSignonState 3  vtidx_StopRecording 7  vtidx_RecordPacket 11 diff --git a/src/demorec.c b/src/demorec.c index 9c836b4..bbcc317 100644 --- a/src/demorec.c +++ b/src/demorec.c @@ -20,6 +20,7 @@  #include "con_.h"  #include "engineapi.h"  #include "errmsg.h" +#include "event.h"  #include "feature.h"  #include "gamedata.h"  #include "gameinfo.h" @@ -41,7 +42,9 @@ DEF_CVAR(sst_autorecord, "Continuously record demos even after reconnecting", 1,  void *demorecorder;  static int *demonum;  static bool *recording; +const char *demorec_basename;  static bool wantstop = false; +bool demorec_forceauto = false;  #define SIGNONSTATE_NEW 3  #define SIGNONSTATE_SPAWN 5 @@ -78,16 +81,22 @@ static void VCALLCONV hook_StopRecording(void *this) {  	orig_StopRecording(this);  	// If the user didn't specifically request the stop, tell the engine to  	// start recording again as soon as it can. -	if (wasrecording && !wantstop && con_getvari(sst_autorecord)) { +	if (wasrecording && !wantstop && (demorec_forceauto || +			con_getvari(sst_autorecord))) {  		*recording = true;  		*demonum = lastnum;  	}  } +DECL_VFUNC_DYN(void, StartRecording) +  static struct con_cmd *cmd_record, *cmd_stop;  static con_cmdcb orig_record_cb, orig_stop_cb; +DEF_PREDICATE(AllowDemoControl, void) +  static void hook_record_cb(const struct con_cmdargs *args) { +	if (!CHECK_AllowDemoControl()) return;  	bool was = *recording;  	if (!was && args->argc == 2 || args->argc == 3) {  		// safety check: make sure a directory exists, otherwise recording @@ -149,6 +158,7 @@ static void hook_record_cb(const struct con_cmdargs *args) {  }  static void hook_stop_cb(const struct con_cmdargs *args) { +	if (!CHECK_AllowDemoControl()) return;  	wantstop = true;  	orig_stop_cb(args);  	wantstop = false; @@ -198,6 +208,51 @@ static inline bool find_recmembers(void *stoprecording) {  	return false;  } +// This finds "m_szDemoBaseName" using the pointer to the original +// "StartRecording" demorecorder function. +static inline bool find_demoname(void *startrecording) { +#ifdef _WIN32 +	for (uchar *p = (uchar *)startrecording; p - (uchar *)startrecording < 32;) { +		// the function immediately calls Q_strncpy and copies into a buffer +		// offset from `this` - look for a LEA instruction some time *before* +		// the first call takes place +		if (p[0] == X86_CALL) return false; +		if (p[0] == X86_LEA && (p[1] & 0xC0) == 0x80) { +			demorec_basename = mem_offset(demorecorder, mem_load32(p + 2)); +			return true; +		} +		NEXT_INSN(p, "demo basename variable"); +	} +#else +#warning TODO(linux): implement linux equivalent (???) +#endif +	return false; +} + +bool demorec_start(const char *name) { +	bool was = *recording; +	if (was) return false; +	// easiest way to do this, though dumb, is to just call the record command +	// callback that we already have a hold of. note: this args object is very +	// incomplete, but is enough to make the command work +	struct con_cmdargs args = {.argc = 2, .argv = {0, name, 0}}; +	orig_record_cb(&args); +	if (!was && *recording) *demonum = 0; // same logic as in the hook +	return *recording; +} + +int demorec_stop(void) { +	// note: our set-to-0-and-back hack actually has the nice side effect of +	// making this correct when recording and stopping in the menu lol +	int ret = *demonum; +	orig_StopRecording(demorecorder); +	return ret; +} + +bool demorec_recording(void) { +	return *recording; +} +  INIT {  	cmd_record = con_findcmd("record");  	if (!cmd_record) { // can *this* even happen? I hope not! @@ -226,6 +281,10 @@ INIT {  		errmsg_errorx("couldn't find recording state variables");  		return false;  	} +	if (!find_demoname(vtable[vtidx_StartRecording])) { +		errmsg_errorx("couldn't find demo basename variable"); +		return false; +	}  	orig_SetSignonState = (SetSignonState_func)hook_vtable(vtable,  			vtidx_SetSignonState, (void *)&hook_SetSignonState); diff --git a/src/demorec.h b/src/demorec.h index 40f8c38..2de4f24 100644 --- a/src/demorec.h +++ b/src/demorec.h @@ -1,6 +1,6 @@  /*   * Copyright © 2021 Willian Henrique <wsimanbrazil@yahoo.com.br> - * Copyright © 2021 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2023 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 @@ -18,9 +18,56 @@  #ifndef INC_DEMOREC_H  #define INC_DEMOREC_H -/* For internal use by democustom */ +#include "event.h" + +// For internal use by democustom  extern void *demorecorder; +/* + * Whether to ignore the value of the sst_autorecord cvar and just keep + * recording anyway. Will be used to further automate demo recording later. + */ +extern bool demorec_forceauto; + +/* + * The current/last basename for recorded demos - to which _2, _3, etc. is + * appended by the engine. May contain garbage or null bytes if recording hasn't + * been started. + * + * This is currently implemented as a pointer directly inside the engine demo + * recorder instance. + */ +extern const char *demorec_basename; + +/* + * Starts recording a demo with the provided name (or relative path). If a demo + * is already being recorded, or the path was deemed invalid by the game, does + * nothing and returns false. Otherwise returns true. + * + * Assumes any subdirectories already exist - recording may silently fail + * otherwise. + */ +bool demorec_start(const char *name); + +/* + * Stops recording the current demo and returns the number of demos recorded + * (the first will have the original basename + .dem extension; the rest will + * have the _N.dem suffixes). + */ +int demorec_stop(void); + +/* + * Queries whether a demo is currently being recorded. + */ +bool demorec_recording(void); + +/* + * Used to determine whether to allow usage of the normal record and stop + * commands. Code which takes over control of demo recording can use this to + * block the user from interfering. + */ +DECL_PREDICATE(AllowDemoControl) +  #endif  // vi: sw=4 ts=4 noet tw=80 cc=80 | 
