summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMichael Smith <mikesmiffy128@gmail.com>2023-05-05 00:04:43 +0100
committerMichael Smith <mikesmiffy128@gmail.com>2023-05-05 00:06:17 +0100
commit3ff6d47277fc0e39053584d8889240cab446a72a (patch)
tree03c26795061fc472d2224efefa283af6b42fe3e0
parentea752466846c129a0910e47d34d725af1aea5d84 (diff)
Implement APIs to control demo recording
This is a surprise tool that will help us later!
-rw-r--r--gamedata/engine.kv1
-rw-r--r--src/demorec.c61
-rw-r--r--src/demorec.h51
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