summaryrefslogtreecommitdiffhomepage
path: root/src/ac.c
diff options
context:
space:
mode:
authorMichael Smith <mikesmiffy128@gmail.com>2023-07-29 14:32:06 +0100
committerMichael Smith <mikesmiffy128@gmail.com>2023-08-02 21:02:31 +0100
commit9a0d8730fa977f666b5c12e4c5901e7d0391e245 (patch)
tree87eebcdcef04ae1e7348ef80e972c08aa4783649 /src/ac.c
parentd337b09936ecd90bad07b28b48b7103395d97ce5 (diff)
Make various preparations for upcoming features
A lot of this is random WIP from a while back, at least a month ago, and is being committed now to get it out of the way so that other patches can be brought in and integrated against it without causing headaches. Also rolled into this commit is a way to distinguish plugin_unload from exiting the game. This is required for another soon-to-be-integrated feature to avoid crashing on exit, and could in theory also be used to speed up unloading on exit in future. While we're at it, this also avoids the need to linearly scan through the plugin list to do the old branch unloading fix, because we can. Rough summary of the other smaller stuff I can remember doing: - Rework bitbuf a bit - Add some cryptographic nonsense in ac.c (not final at all) - Introduce the first couple of "chunklets" libraries as a sort-of subproject of this one - Tidy up random small bits and bobs - Add source for a small keypair generation tool - Rework democustom to be very marginally more useful
Diffstat (limited to 'src/ac.c')
-rw-r--r--src/ac.c257
1 files changed, 199 insertions, 58 deletions
diff --git a/src/ac.c b/src/ac.c
index 605a2be..263e114 100644
--- a/src/ac.c
+++ b/src/ac.c
@@ -20,14 +20,21 @@
#include <immintrin.h>
#endif
+#include "alias.h"
#include "bind.h"
+#include "chunklets/fastspin.h"
+#include "chunklets/msg.h"
#include "con_.h"
+#include "crypto.h"
+#include "democustom.h"
+#include "demorec.h"
#include "hook.h"
#include "engineapi.h"
#include "errmsg.h"
#include "event.h"
#include "feature.h"
#include "gamedata.h"
+#include "gametype.h"
#include "intdefs.h"
#include "mem.h"
#include "os.h"
@@ -37,33 +44,91 @@
#include "x86.h"
#include "x86util.h"
+#ifdef _WIN32
+#include <werapi.h> // must be after Windows.h (via os.h)
+#endif
+
FEATURE()
REQUIRE(bind)
REQUIRE(democustom)
REQUIRE_GAMEDATA(vtidx_GetDesktopResolution)
REQUIRE_GAMEDATA(vtidx_DispatchAllStoredGameMessages)
+REQUIRE_GLOBAL(pluginhandler)
+
+static bool enabled = false;
-static bool lockdown = false;
+// mild overkill: 1 page of memory that won't be coredumped or swapped to disk
+static struct keybox {
+ union { uchar prv[32], shr[32]; };
+ uchar tmp[32], pub[32], lbpub[32]; // NOTE: these 3 must be kept contiguous!
+ union { u64 nonce; uchar nonce_bytes[8]; };
+ crypto_rng_ctx rng; // NOTE: keep this at the end, for wipesessionkeys()
+} *keybox;
+
+enum {
+ LBPK_L4D
+};
+const uchar lbpubkeys[1][32] = {
+ // L4D series (PLACEHOLDERS for now; this whole thing is unfinished!)
+ 0x4A, 0xF3, 0xE2, 0xFC, 0x9C, 0x4E, 0xCB, 0xF9,
+ 0xBD, 0xB8, 0xA9, 0xFC, 0x0E, 0xF7, 0x93, 0x9C,
+ 0xC3, 0x09, 0x43, 0xB2, 0x6E, 0x7B, 0x1F, 0x19,
+ 0x40, 0x05, 0xE9, 0x60, 0x43, 0xE8, 0xE2, 0x03
+};
+
+static void newsessionkeys(void) {
+ crypto_rng_read(&keybox->rng, keybox->prv, sizeof(keybox->prv));
+ crypto_x25519_public_key(keybox->pub, keybox->prv);
+ crypto_x25519(keybox->tmp, keybox->prv, keybox->lbpub);
+ // dumbest, safest possible key derivation, because I'm not a cryptographer.
+ // future versions of the custom demo protocol COULD get something faster
+ // (like something with hchacha20, if only I could find enough info on that)
+ crypto_blake2b(keybox->shr, sizeof(keybox->tmp), keybox->tmp, 96);
+ crypto_wipe(keybox->tmp, sizeof(keybox->tmp));
+ keybox->nonce = 0;
+}
+
+static void wipesessionkeys(void) {
+ crypto_wipe(keybox->prv, offsetof(struct keybox, rng));
+}
+
+HANDLE_EVENT(DemoRecordStarting, void) { if (enabled) newsessionkeys(); }
+HANDLE_EVENT(DemoRecordStopped, int ndemos) { if (enabled) wipesessionkeys(); }
#ifdef _WIN32
static void *gamewin, *inhookwin, *inhookthr;
static ulong inhooktid;
-// UINT_PTR is a **stupid** typedef, but whatever.
-static ssize __stdcall kproc(int code, UINT_PTR wp, ssize lp) {
+static ssize __stdcall kproc(int code, usize wp, ssize lp) {
KBDLLHOOKSTRUCT *data = (KBDLLHOOKSTRUCT *)lp;
- if (lockdown && data->flags & LLKHF_INJECTED &&
+ if (enabled && data->flags & LLKHF_INJECTED &&
GetForegroundWindow() == gamewin) {
- return 1;
+ // maybe this input is reasonable, but log it for closer inspection
+ // TODO(rta): figure out what to do with this stuff
+ // something like the following, but with a proper abstraction...
+ //uchar buf[28 + 16], *p = buf;
+ //msg_putasz4(p, 2); p += 1;
+ // msg_putssz5(p, 8); memcpy(p + 1, "FakeKey", 7); p += 8;
+ // msg_putmsz4(p, 2); p += 1;
+ // msg_putssz5(p, 3); memcpy(p + 1, "vk", 2); p += 3;
+ // p += msg_putu32(p, data->vkCode);
+ // msg_putssz5(p, 3); memcpy(p + 1, "scan", 4); p += 5;
+ // p += msg_putu32(p, data->scanCode);
+ //++keybox->nonce;
+ //// append mac at end of message
+ //crypto_aead_lock_djb(buf, p, keybox->shr, keybox->nonce_bytes, 0, 0,
+ // buf, p - buf);
+ //democustom_write(buf, p - buf + 16);
}
return CallNextHookEx(0, code, wp, lp);
}
-static ssize __stdcall mproc(int code, UINT_PTR wp, ssize lp) {
+static ssize __stdcall mproc(int code, usize wp, ssize lp) {
MSLLHOOKSTRUCT *data = (MSLLHOOKSTRUCT *)lp;
- if (lockdown && data->flags & LLMHF_INJECTED &&
+ if (enabled && data->flags & LLMHF_INJECTED &&
GetForegroundWindow() == gamewin) {
+ // no way this input would ever be reasonable. just discard it
return 1;
}
return CallNextHookEx(0, code, wp, lp);
@@ -72,46 +137,48 @@ static ssize __stdcall mproc(int code, UINT_PTR wp, ssize lp) {
// this is its own thread to meet the strict timing deadline, otherwise the
// hook gets silently removed. plus, we don't wanna incur latency anyway.
static ulong __stdcall inhookthrmain(void *param) {
- volatile u32 *sig = param;
- if (!SetWindowsHookExW(WH_KEYBOARD_LL, &kproc, 0, 0) ||
- !SetWindowsHookExW(WH_MOUSE_LL, &mproc, 0, 0)) {
- *sig = 2;
+ volatile int *sig = param;
+ if (!SetWindowsHookExW(WH_KEYBOARD_LL, (HOOKPROC)&kproc, 0, 0) ||
+ !SetWindowsHookExW(WH_MOUSE_LL, (HOOKPROC)&mproc, 0, 0)) {
+ fastspin_raise(sig, 2);
return -1;
}
- *sig = 1;
+ fastspin_raise(sig, 1);
MSG m; int ret;
while ((ret = GetMessageW(&m, inhookwin, 0, 0)) > 0) DispatchMessage(&m);
return ret;
}
-static WNDPROC orig_wndproc;
-static ssize __stdcall hook_wndproc(void *wnd, uint msg, UINT_PTR wp, ssize lp) {
- if (msg == WM_COPYDATA && lockdown) return DefWindowProcW(wnd, msg, wp, lp);
- return orig_wndproc(wnd, msg, wp, lp);
+static ssize orig_wndproc;
+static ssize __stdcall hook_wndproc(void *wnd, uint msg, usize wp, ssize lp) {
+ if (msg == WM_COPYDATA && enabled) return DefWindowProcW(wnd, msg, wp, lp);
+ return CallWindowProcA((WNDPROC)orig_wndproc, wnd, msg, wp, lp);
}
static bool win32_init(void) {
- gamewin = FindWindowW(L"Valve001", 0);
+ // note: using A instead of W to avoid some weirdness with handles...
+ gamewin = FindWindowA("Valve001", 0);
// note: error messages here are a bit cryptic on purpose, but easy to find
// in the code. in other words, we're hiding in plain sight :-)
if (!gamewin) {
errmsg_errorsys("failed to find window");
return false;
}
- orig_wndproc = (WNDPROC)SetWindowLongPtrW(gamewin, GWLP_WNDPROC,
- (ssize)hook_wndproc);
- if (!orig_wndproc) {
+ orig_wndproc = SetWindowLongPtrA(gamewin, GWLP_WNDPROC,
+ (ssize)&hook_wndproc);
+ if (!orig_wndproc) { // XXX: assuming 0 won't be legitimately returned
errmsg_errorsys("failed to attach message handler");
+ return false;
}
return true;
}
static void win32_end(void) {
// no error handling here because we'd crash either way. good luck!
- SetWindowLongW(gamewin, GWLP_WNDPROC, (ssize)orig_wndproc);
+ SetWindowLongPtrA(gamewin, GWLP_WNDPROC, orig_wndproc);
}
-static void inhook_start(volatile u32 *sig) {
+static void inhook_start(volatile int *sig) {
inhookwin = CreateWindowW(L"sst-eventloop", L"sst-eventloop", WS_DISABLED,
0, 0, 0, 0, HWND_MESSAGE, 0, 0, 0);
inhookthr = CreateThread(0, 0, &inhookthrmain, (u32 *)sig, 0, &inhooktid);
@@ -127,7 +194,7 @@ static void inhook_check(void) {
// won't matter in practice but... this kind of sucks.
con_warn("** sst: ERROR in message loop, abandoning RTA mode! **");
// TODO(rta): stop demos, and stuff.
- lockdown = false;
+ enabled = false;
}
}
}
@@ -138,12 +205,14 @@ static void inhook_stop(void) {
errmsg_warnsys("couldn't wait for thread, status unknown");
// XXX: now what!?
}
- // assume WAIT_OBJECT_0
- ulong status;
- GetExitCodeThread(inhookthr, &status);
- if (status) {
- // not much else we can do now!
- con_warn("warning: RTA mode message loop had an error during shutdown");
+ else {
+ // assume WAIT_OBJECT_0
+ ulong status;
+ GetExitCodeThread(inhookthr, &status);
+ if (status) {
+ // not much else we can do now!
+ errmsg_errorx("message loop didn't shut down cleanly\n");
+ }
}
CloseHandle(inhookthr);
}
@@ -155,22 +224,18 @@ static void inhook_stop(void) {
#endif
bool ac_enable(void) {
- if (lockdown) return true;
+ if (enabled) return true;
#ifdef _WIN32
- // and now for some frivolously microoptimised spinlocking nonsense
- volatile u32 sig = 0; // paranoid volatile to ensure no loop misopt...
+ volatile int sig = 0;
inhook_start(&sig);
- register u32 x; // avoid double-reading the volatile
- // pausing in the middle here seems to produce shorter asm with gcc -O2 and
- // clang -O3 in godbolt (avoids unrolling of head which is unlikely to help)
- while (x = sig, _mm_pause(), !x);
- if (x == 2) { // else 1 for success
+ fastspin_wait(&sig);
+ if (sig == 2) { // else 1 for success
con_warn("** sst: ERROR starting message loop, can't continue! **");
CloseHandle(inhookthr);
return false;
}
#endif
- lockdown = true;
+ enabled = true;
return true;
}
@@ -178,16 +243,16 @@ HANDLE_EVENT(Tick, bool simulating) {
#ifdef _WIN32
static uint fewticks = 0;
// just check this every so often (roughly 0.1-0.3s depending on game)
- if (lockdown && !(++fewticks & 7)) inhook_check();
+ if (enabled && !(++fewticks & 7)) inhook_check();
#endif
}
void ac_disable(void) {
- if (!lockdown) return;
+ if (!enabled) return;
#ifdef _WIN32
inhook_stop();
#endif
- lockdown = false;
+ enabled = false;
}
enum /* from InputEventType_t - terser names used here */ {
@@ -216,15 +281,22 @@ typedef void (*VCALLCONV DispatchInputEvent_func)(void *, struct inputevent *);
static DispatchInputEvent_func orig_DispatchInputEvent;
static void VCALLCONV hook_DispatchInputEvent(void *this,
struct inputevent *ev) {
- // TODO(rta): do something here! (here's a quick reference/example)
- //switch (ev->type) {
- // CASES(BTNDOWN, BTNUP, BTNDOUBLECLICK):
- // const char *desc[] = {"DOWN", "UP", "DOUBLE"};
- // const char *binding = bind_get(ev->data);
- // if (!binding) binding = "[unbound]";
- // con_msg("key %d %s => %s\n", ev->data, desc[ev->type - BTNDOWN],
- // binding);
- //}
+ //const char *desc[] = {"DOWN", "UP", "DBL"};
+ //const char desclen[] = {4, 2, 3};
+ switch (ev->type) {
+ CASES(BTNDOWN, BTNUP, BTNDOUBLECLICK):;
+ // TODO(rta): do something interesting with button data
+ //uchar buf[28], *p = buf;
+ //msg_putasz4(p, 2); p += 1;
+ // msg_putssz5(p, 8); memcpy(p + 1, "KeyInput", 8); p += 9;
+ // msg_putmsz4(p, 2); p += 1;
+ // msg_putssz5(p, 3); memcpy(p + 1, "key", 3); p += 4;
+ // p += msg_puts32(p, ev->data);
+ // msg_putssz5(p, 3); memcpy(p + 1, "btn", 3); p += 4;
+ // int idx = ev->type - BTNDOWN;
+ // msg_putssz5(p++, desclen[idx]);
+ // memcpy(p, desc[idx], desclen[idx]); p += desclen[idx];
+ }
orig_DispatchInputEvent(this, ev);
}
@@ -242,7 +314,7 @@ static bool find_DispatchInputEvent(void) {
return false;
}
void *cgame;
- const uchar *insns = (const uchar*)VFUNC(gameuifuncs, GetDesktopResolution);
+ const uchar *insns = (const uchar *)VFUNC(gameuifuncs, GetDesktopResolution);
for (const uchar *p = insns; p - insns < 16;) {
if (p[0] == X86_MOVRMW && p[1] == X86_MODRM(0, 1, 5)) {
void **indirect = mem_loadptr(p + 2);
@@ -273,12 +345,27 @@ ok: insns = (const uchar *)VFUNC(cgame, DispatchAllStoredGameMessages);
return false;
}
+HANDLE_EVENT(AllowPluginLoading, bool loading) {
+ if (enabled && demorec_demonum() != -1) {
+ con_warn("sst: plugins cannot be %s while recording a run\n",
+ loading ? "loaded" : "unloaded");
+ return false;
+ }
+ return true;
+}
+
+HANDLE_EVENT(PluginLoaded, void) {
+ // TODO(rta): do something with plugin list here
+}
+HANDLE_EVENT(PluginUnloaded, void) {
+ // TODO(rta): do something with plugin list here
+}
+
+PREINIT {
+ return GAMETYPE_MATCHES(L4D); // TODO(compat): add more here obviously
+}
+
INIT {
-#if defined(_WIN32)
- if (!win32_init()) return false;
-#elif defined(__linux__)
- // TODO(linux): call init things
-#endif
if (!find_DispatchInputEvent()) return false;
orig_DispatchInputEvent = (DispatchInputEvent_func)hook_inline(
(void *)orig_DispatchInputEvent, (void *)&hook_DispatchInputEvent);
@@ -286,16 +373,70 @@ INIT {
errmsg_errorsys("couldn't hook DispatchInputEvent function");
return false;
}
+
+#ifdef _WIN32
+ keybox = VirtualAlloc(0, 4096, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
+ if (!keybox) {
+ errmsg_errorsys("couldn't allocate memory for session state");
+ return false;
+ }
+ if (!VirtualLock(keybox, 4096)) {
+ errmsg_errorsys("couldn't secure session state");
+ goto e2;
+ }
+ if (WerRegisterExcludedMemoryBlock(keybox, 4096) != S_OK) {
+ // FIXME: stringify errors properly here
+ errmsg_errorx("couldn't secure session state");
+ goto e2;
+ }
+ if (!win32_init()) goto e;
+#else
+ keybox = mmap(0, 4096, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
+ if (keybox == MAP_FAILED) {
+ errmsg_errorstd("couldn't allocate memory for session state");
+ return false;
+ }
+ // linux-specific madvise stuff (there are some equivalents in OpenBSD and
+ // FreeBSD, if anyone's wondering, but we don't need to worry about those)
+ if (madvise(keybox, 4096, MADV_DONTFORK) == -1 ||
+ madvise(keybox, 4096, MADV_DONTDUMP) == - 1 ||
+ mlock(keybox, 4096) == -1) {
+ errormsg_errorstd("couldn't secure session state");
+ goto e;
+ }
+ // TODO(linux): call other init things
+#endif
+
+ uchar seed[32];
+ os_randombytes(seed, sizeof(seed));
+ crypto_rng_init(&keybox->rng, seed);
+ if (GAMETYPE_MATCHES(L4D)) {
+ // copy into the keybox so key derivation blake2 gets a nice contiguous
+ // run of bytes
+ memcpy(keybox->lbpub, lbpubkeys[LBPK_L4D], 32);
+ }
return true;
+
+#ifdef _WIN32
+e: WerUnregisterExcludedMemoryBlock(keybox); // this'd better not fail!
+e2: VirtualFree(keybox, 4096, MEM_RELEASE);
+#elif
+e: munmap(keybox, 4096);
+#endif
+ unhook_inline((void *)orig_DispatchInputEvent);
+ return false;
}
END {
+ ac_disable();
#if defined(_WIN32)
+ WerUnregisterExcludedMemoryBlock(keybox); // this'd better not fail!
+ VirtualFree(keybox, 4096, MEM_RELEASE);
win32_end();
#elif defined(__linux__)
- // TODO(linux): call cleanup things
+ munmap(keybox, 4096);
+ // TODO(linux): call other cleanup things
#endif
- ac_disable();
unhook_inline((void *)orig_DispatchInputEvent);
}