From 374ae0fbc44db36d9abb6b5b1fe065bc3949e201 Mon Sep 17 00:00:00 2001 From: Matthew Wozniak Date: Thu, 17 Nov 2022 14:34:47 -0300 Subject: 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. --- compile | 3 +- compile.bat | 4 +- gamedata/engine.kv | 26 ++++++ gamedata/vgui2.kv | 7 ++ gamedata/vguimatsurface.kv | 69 ++++++++++++++++ src/hud.c | 194 +++++++++++++++++++++++++++++++++++++++++++++ src/hud.h | 72 +++++++++++++++++ 7 files changed, 373 insertions(+), 2 deletions(-) create mode 100644 gamedata/vgui2.kv create mode 100644 gamedata/vguimatsurface.kv create mode 100644 src/hud.c create mode 100644 src/hud.h 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 + * + * 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 + * + * 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 -- cgit v1.2.3