summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMatthew Wozniak <sirtomato999@gmail.com>2022-11-17 14:34:47 -0300
committerMichael Smith <mikesmiffy128@gmail.com>2023-12-19 16:29:08 +0000
commit374ae0fbc44db36d9abb6b5b1fe065bc3949e201 (patch)
tree04b1a66a5898bdd420c9cd523cc40a0062511aef
parente3d7cc9c80159849289a8bab03eca088b758941b (diff)
Add VGUI HUD overlay drawing feature
Currently only supports Orange Box and Left 4 Dead branches. There's quite a large amount of gamedata involved in making this work, and figuring it out for the likes of Portal 2 doesn't seem like a major priority at the moment.
-rwxr-xr-xcompile3
-rw-r--r--compile.bat4
-rw-r--r--gamedata/engine.kv26
-rw-r--r--gamedata/vgui2.kv7
-rw-r--r--gamedata/vguimatsurface.kv69
-rw-r--r--src/hud.c194
-rw-r--r--src/hud.h72
7 files changed, 373 insertions, 2 deletions
diff --git a/compile b/compile
index c7014e8..abdd5a7 100755
--- a/compile
+++ b/compile
@@ -71,6 +71,7 @@ src="\
gameinfo.c
gameserver.c
hook.c
+ hud.c
kvsys.c
l4dmm.c
l4dreset.c
@@ -92,7 +93,7 @@ $HOSTCC -O2 -fuse-ld=lld $warnings -D_FILE_OFFSET_BITS=64 -include stdbool.h \
-o .build/mkentprops src/build/mkentprops.c src/kv.c
.build/codegen `for s in $src; do echo "src/$s"; done`
.build/mkgamedata gamedata/engine.kv gamedata/gamelib.kv gamedata/inputsystem.kv \
- gamedata/matchmaking.kv
+gamedata/matchmaking.kv gamedata/vgui2.kv gamedata/vguimatsurface.kv
.build/mkentprops gamedata/entprops.kv
for s in $src; do cc "$s"; done
$CC -shared -fpic -fuse-ld=lld -O0 -w -o .build/libtier0.so src/stubs/tier0.c
diff --git a/compile.bat b/compile.bat
index 8db2231..97bbb6f 100644
--- a/compile.bat
+++ b/compile.bat
@@ -78,6 +78,7 @@ setlocal DisableDelayedExpansion
:+ gameinfo.c
:+ gameserver.c
:+ hook.c
+:+ hud.c
:+ kvsys.c
:+ l4dmm.c
:+ l4dreset.c
@@ -100,7 +101,8 @@ if "%dbg%"=="0" set src=%src% src/wincrt.c
%HOSTCC% -fuse-ld=lld -municode -O2 %warnings% -D_CRT_SECURE_NO_WARNINGS -include stdbool.h -ladvapi32 ^
-o .build/mkentprops.exe src/build/mkentprops.c src/kv.c || exit /b
.build\codegen.exe%src% || exit /b
-.build\mkgamedata.exe gamedata/engine.kv gamedata/gamelib.kv gamedata/inputsystem.kv gamedata/matchmaking.kv || exit /b
+.build\mkgamedata.exe gamedata/engine.kv gamedata/gamelib.kv gamedata/inputsystem.kv ^
+gamedata/matchmaking.kv gamedata/vgui2.kv gamedata/vguimatsurface.kv || exit /b
.build\mkentprops.exe gamedata/entprops.kv || exit /b
llvm-rc /FO .build\dll.res src\dll.rc || exit /b
%CC% -fuse-ld=lld -shared -O0 -w -o .build/tier0.dll src/stubs/tier0.c
diff --git a/gamedata/engine.kv b/gamedata/engine.kv
index 9501221..b23bab5 100644
--- a/gamedata/engine.kv
+++ b/gamedata/engine.kv
@@ -63,6 +63,31 @@ sz_edict {
L4Dbased 16 // see engineapi.h comment
}
+// vgui::Panel
+vtidx_SetPaintEnabled {
+ default 67
+ Client013 {
+ L4D1 68
+ L4D2 {
+ default 71
+ L4D2_2147plus 72
+ }
+ }
+ Client014 {
+ L4D2 70
+ }
+}
+vtidx_Paint {
+ default 123
+ Client014 { L4D2 126 } // 2000
+ Client013 {
+ L4D2 {
+ default 127 // 2045
+ L4D2_2147plus 128
+ }
+ }
+}
+
// SendProp
sz_SendProp {
// wrapping all these in 005 for right now.
@@ -104,6 +129,7 @@ vtidx_GetSpawnCount {
}
// IEngineVGuiInternal/CEngineVGui
+vtidx_GetPanel NVDTOR
vtidx_VGuiConnect { // note: the actual name is Connect() but that's too generic
default "3 + NVDTOR"
L4Dbased {
diff --git a/gamedata/vgui2.kv b/gamedata/vgui2.kv
new file mode 100644
index 0000000..6fd55a3
--- /dev/null
+++ b/gamedata/vgui2.kv
@@ -0,0 +1,7 @@
+// = vgui2 library =
+
+// ISchemeManager
+vtidx_GetIScheme 8
+
+// IScheme
+vtidx_GetFont 3
diff --git a/gamedata/vguimatsurface.kv b/gamedata/vguimatsurface.kv
new file mode 100644
index 0000000..19857be
--- /dev/null
+++ b/gamedata/vguimatsurface.kv
@@ -0,0 +1,69 @@
+// = vguimatsurface library =
+
+// ISurface
+vtidx_DrawSetColor {
+ OrangeBoxbased 10
+ L4D 10
+}
+vtidx_DrawFilledRect {
+ OrangeBoxbased 12
+ L4D 12
+}
+vtidx_DrawOutlinedRect {
+ OrangeBoxbased 14
+ L4D 14
+}
+vtidx_DrawLine {
+ OrangeBoxbased 15
+ L4D 15
+}
+vtidx_DrawPolyLine {
+ OrangeBoxbased 16
+ L4D 16
+}
+vtidx_DrawSetTextFont {
+ OrangeBoxbased 17
+ L4D 17
+}
+vtidx_DrawSetTextColor {
+ OrangeBoxbased 18
+ L4D 18
+}
+vtidx_DrawSetTextPos {
+ OrangeBoxbased 20
+ L4D 20
+}
+vtidx_DrawPrintText {
+ OrangeBoxbased 22
+ L4D 22
+}
+vtidx_GetScreenSize {
+ OrangeBoxbased 37
+ L4D {
+ default 37
+ L4D2_2147plus 35
+ }
+}
+// Unused: currently no good way to create custom fonts without leaking them
+//vtidx_CreateFont {
+// OrangeBoxbased 64
+// L4D {
+// default 64
+// L4D2_2147plus 63
+// }
+//}
+//vtidx_SetFontGlyphSet {
+// OrangeBoxbased 65
+// L4D {
+// default 65
+// L4D2_2147plus 64
+// }
+//}
+vtidx_GetFontTall {
+ OrangeBoxbased 67
+ L4D 67
+}
+vtidx_GetCharacterWidth {
+ OrangeBoxbased 71
+ L4D 71
+}
diff --git a/src/hud.c b/src/hud.c
new file mode 100644
index 0000000..8176d63
--- /dev/null
+++ b/src/hud.c
@@ -0,0 +1,194 @@
+/*
+ * Copyright © 2022 Matthew Wozniak <sirtomato999@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
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+ * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "engineapi.h"
+#include "errmsg.h"
+#include "event.h"
+#include "feature.h"
+#include "gamedata.h"
+#include "gametype.h"
+#include "hook.h"
+#include "hud.h"
+#include "intdefs.h"
+#include "mem.h"
+#include "os.h"
+#include "sst.h"
+#include "vcall.h"
+#include "x86.h"
+#include "x86util.h"
+
+FEATURE()
+REQUIRE_GLOBAL(factory_engine)
+REQUIRE_GLOBAL(vgui)
+// ISurface
+REQUIRE_GAMEDATA(vtidx_DrawSetColor)
+REQUIRE_GAMEDATA(vtidx_DrawFilledRect)
+REQUIRE_GAMEDATA(vtidx_DrawOutlinedRect)
+REQUIRE_GAMEDATA(vtidx_DrawLine)
+REQUIRE_GAMEDATA(vtidx_DrawPolyLine)
+REQUIRE_GAMEDATA(vtidx_DrawSetTextFont)
+REQUIRE_GAMEDATA(vtidx_DrawSetTextColor)
+REQUIRE_GAMEDATA(vtidx_DrawSetTextPos)
+REQUIRE_GAMEDATA(vtidx_DrawPrintText)
+REQUIRE_GAMEDATA(vtidx_GetScreenSize)
+REQUIRE_GAMEDATA(vtidx_GetFontTall)
+REQUIRE_GAMEDATA(vtidx_GetCharacterWidth)
+// CEngineVGui
+REQUIRE_GAMEDATA(vtidx_GetPanel)
+// vgui::Panel
+REQUIRE_GAMEDATA(vtidx_SetPaintEnabled)
+REQUIRE_GAMEDATA(vtidx_Paint)
+// ISchemeManager
+REQUIRE_GAMEDATA(vtidx_GetIScheme)
+// IScheme
+REQUIRE_GAMEDATA(vtidx_GetFont)
+
+DEF_EVENT(HudPaint, void)
+
+// we just use ulongs for API, but keep a struct for vcalls to ensure we get the
+// right calling convention (x86 Windows/MSVC is funny about passing structs...)
+struct handlewrap { ulong x; };
+
+// CEngineVGui
+DECL_VFUNC_DYN(unsigned int, GetPanel, int)
+
+// vgui::ISchemeManager
+DECL_VFUNC_DYN(void *, GetIScheme, struct handlewrap)
+// vgui::IScheme
+DECL_VFUNC_DYN(struct handlewrap, GetFont, const char *, bool)
+
+// vgui::ISurface
+DECL_VFUNC_DYN(void, DrawSetColor, struct rgba)
+DECL_VFUNC_DYN(void, DrawFilledRect, int, int, int, int)
+DECL_VFUNC_DYN(void, DrawOutlinedRect, int, int, int, int)
+DECL_VFUNC_DYN(void, DrawLine, int, int, int, int)
+DECL_VFUNC_DYN(void, DrawPolyLine, int *, int *, int)
+DECL_VFUNC_DYN(void, DrawSetTextFont, struct handlewrap)
+DECL_VFUNC_DYN(void, DrawSetTextColor, struct rgba)
+DECL_VFUNC_DYN(void, DrawSetTextPos, int, int)
+DECL_VFUNC_DYN(void, DrawPrintText, ushort *, int, int)
+DECL_VFUNC_DYN(void, GetScreenSize, int *, int *)
+DECL_VFUNC_DYN(int, GetFontTall, struct handlewrap)
+DECL_VFUNC_DYN(int, GetCharacterWidth, struct handlewrap, int)
+
+// vgui::Panel
+DECL_VFUNC_DYN(void, SetPaintEnabled, bool)
+
+static void *matsurf, *toolspanel, *scheme;
+
+typedef void (*VCALLCONV Paint_func)(void *);
+static Paint_func orig_Paint;
+void VCALLCONV hook_Paint(void *this) {
+ if (this == toolspanel) EMIT_HudPaint();
+ orig_Paint(this);
+}
+
+ulong hud_getfont(const char *name, bool proportional) {
+ return GetFont(scheme, name, proportional).x;
+}
+
+void hud_drawrect(int x0, int y0, int x1, int y1, struct rgba colour,
+ bool fill) {
+ DrawSetColor(matsurf, colour);
+ if (fill) DrawFilledRect(matsurf, x0, y0, x1, y1);
+ else DrawOutlinedRect(matsurf, x0, y0, x1, y1);
+}
+
+void hud_drawline(int x0, int y0, int x1, int y1, struct rgba colour) {
+ DrawSetColor(matsurf, colour);
+ DrawLine(matsurf, x0, y0, x1, y1);
+}
+
+void hud_drawpolyline(int *x, int *y, int npoints, struct rgba colour) {
+ DrawSetColor(matsurf, colour);
+ DrawPolyLine(matsurf, x, y, npoints);
+}
+
+void hud_drawtext(ulong font, int x, int y, struct rgba colour, ushort *str,
+ int len) {
+ DrawSetTextFont(matsurf, (struct handlewrap){font});
+ DrawSetTextPos(matsurf, x, y);
+ DrawSetTextColor(matsurf, colour);
+ DrawPrintText(matsurf, str, len, /*FONT_DRAW_DEFAULT*/ 0);
+}
+
+void hud_screensize(int *width, int *height) {
+ GetScreenSize(matsurf, width, height);
+}
+
+int hud_fontheight(ulong font) {
+ return GetFontTall(matsurf, (struct handlewrap){font});
+}
+
+int hud_charwidth(ulong font, int ch) {
+ return GetCharacterWidth(matsurf, (struct handlewrap){font}, ch);
+}
+
+static bool find_toolspanel(void *enginevgui) {
+ const uchar *insns = (const uchar *)VFUNC(enginevgui, GetPanel);
+ for (const uchar *p = insns; p - insns < 16;) {
+ // first CALL instruction in GetPanel calls GetRootPanel, which gives a
+ // pointer to the specified panel
+ if (p[0] == X86_CALL) {
+ typedef void *(*VCALLCONV GetRootPanel_func)(void *this, int);
+ int off = mem_load32(p + 1);
+ GetRootPanel_func GetRootPanel = (GetRootPanel_func)(p + 5 + off);
+ toolspanel = GetRootPanel(enginevgui, /*PANEL_TOOLS*/ 3);
+ return true;
+ }
+ NEXT_INSN(p, "GetRootPanel function");
+ }
+ return false;
+}
+
+INIT {
+ matsurf = factory_engine("MatSystemSurface006", 0);
+ if (!matsurf) {
+ errmsg_errorx("couldn't get MatSystemSurface006 interface");
+ return false;
+ }
+ void *schememgr = factory_engine("VGUI_Scheme010", 0);
+ if (!schememgr) {
+ errmsg_errorx("couldn't get VGUI_Scheme010 interface");
+ return false;
+ }
+ if (!find_toolspanel(vgui)) {
+ errmsg_errorx("couldn't find engine tools panel");
+ return false;
+ }
+ void **vtable = *(void ***)toolspanel;
+ if (!os_mprot(vtable + vtidx_Paint, sizeof(void *),
+ PAGE_READWRITE)) {
+ errmsg_errorsys("couldn't make virtual table writable");
+ return false;
+ }
+ orig_Paint = (Paint_func)hook_vtable(vtable, vtidx_Paint,
+ (void *)&hook_Paint);
+ SetPaintEnabled(toolspanel, true);
+ // 1 is the default, first loaded scheme. should always be sourcescheme.res
+ scheme = GetIScheme(schememgr, (struct handlewrap){1});
+ return true;
+}
+
+END {
+ // don't unhook toolspanel if exiting, it's already long gone!
+ if (sst_userunloaded) {
+ unhook_vtable(*(void ***)toolspanel, vtidx_Paint, (void *)orig_Paint);
+ SetPaintEnabled(toolspanel, false);
+ }
+}
+
+// vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/src/hud.h b/src/hud.h
new file mode 100644
index 0000000..8ea0877
--- /dev/null
+++ b/src/hud.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright © 2022 Matthew Wozniak <sirtomato999@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
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+ * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef INC_HUD_H
+#define INC_HUD_H
+
+#include "event.h"
+#include "engineapi.h"
+#include "intdefs.h"
+
+/*
+ * Emitted when the game HUD is being drawn. Allows features to draw their own
+ * additional overlays atop the game's standard HUD.
+ */
+DECL_EVENT(HudPaint, void)
+
+/* Font style flags */
+#define HUD_FONT_ITALIC 1
+#define HUD_FONT_UNDERLINE 2
+#define HUD_FONT_STRIKE 4
+#define HUD_FONT_SYMBOL 8
+#define HUD_FONT_AA 16
+#define HUD_FONT_GAUSSBLUR 32
+#define HUD_FONT_ROTARY 64
+#define HUD_FONT_DROPSHADOW 128
+#define HUD_FONT_ADDITIVE 256
+#define HUD_FONT_OUTLINE 512
+#define HUD_FONT_CUSTOM 1024
+#define HUD_FONT_BITMAP 2048
+
+/* Gets a font handle by its name in sourcescheme.res. */
+ulong hud_getfont(const char *name, bool proportional);
+
+/* Sets the drawing pen colour for subsequent HUD drawing calls (below). */
+void hud_setcolour(struct rgba colour);
+
+/* Draws a rectangle on top of the HUD. */
+void hud_drawrect(int x0, int y0, int x1, int y1, struct rgba colour, bool fill);
+
+/* Draws a line on top of the HUD. */
+void hud_drawline(int x0, int y0, int x1, int y1, struct rgba colour);
+
+/* Draws an arbitrary series of lines between an array of points. */
+void hud_drawpolyline(int *xs, int *ys, int npoints, struct rgba colour);
+
+/* Draws text using a given font handle. */
+void hud_drawtext(ulong font, int x, int y, struct rgba colour, ushort *str,
+ int len);
+
+/* Returns the width and height of the game window in pixels. */
+void hud_screensize(int *width, int *height);
+
+/* Returns the height of a font, in pixels. */
+int hud_fontheight(ulong font);
+
+/* Returns the width of a font character, in pixels. */
+int hud_getcharwidth(ulong font, int ch);
+
+#endif