VoltMod
C++23 framework for CS2 server plugins
Loading...
Searching...
No Matches
ScriptBindings.cpp
Go to the documentation of this file.
2
4
5#include <cstdint>
6#include <cstring>
7#include <entity2/entityinstance.h>
8#include <format>
9#include <khook.hpp>
10#include <vscript/ivscript.h>
11
12namespace VoltMod
13{
14
15// The SDK's ScriptFunctionBinding_t predates the class pointer CS2 added after the descriptor.
24
25static const ScriptFunctionBinding* FindBinding(const ScriptClassDesc_t* description, std::string_view name)
26{
27 for (; description; description = description->m_pBaseDesc)
28 {
29 const auto& bindings =
30 reinterpret_cast<const CUtlVector<ScriptFunctionBinding>&>(description->m_FunctionBindings);
31 for (int i = 0; i < bindings.Count(); ++i)
32 {
33 const char* scriptName = bindings[i].Descriptor.m_pszScriptName;
34 if (scriptName && name == scriptName)
35 {
36 return &bindings[i];
37 }
38 }
39 }
40 return nullptr;
41}
42
43/** The vtable index @p function dispatches through, or -1 when it is not virtual. */
44static int VirtualIndex(const void* function)
45{
46#ifdef _WIN32
47 // MSVC points a virtual member at a thunk: mov rax, [rcx]; jmp [rax + disp].
48 const auto* code = static_cast<const uint8_t*>(function);
49 if (!IsReadableAddress(code, 9) || code[0] != 0x48 || code[1] != 0x8B || code[2] != 0x01 || code[3] != 0xFF)
50 {
51 return -1;
52 }
53 switch (code[4])
54 {
55 case 0x20:
56 return 0;
57 case 0x60:
58 return static_cast<int8_t>(code[5]) / static_cast<int>(sizeof(void*));
59 case 0xA0:
60 {
62 std::memcpy(&displacement, code + 5, sizeof(displacement));
63 return displacement / static_cast<int>(sizeof(void*));
64 }
65 default:
66 return -1;
67 }
68#else
69 // Itanium stores a virtual member as one plus its byte offset in the table.
70 const auto value = reinterpret_cast<uintptr_t>(function);
71 return (value & 1) != 0 ? static_cast<int>((value - 1) / sizeof(void*)) : -1;
72#endif
73}
74
76{
77 // Call the slot, not a virtual on a fake object: GCC drops that as undefined. `this` is ignored.
78 using GetScriptDescFn = void* (*)(void* self);
79 const int slot = KHook::GetVtableIndex(&CEntityInstance::GetScriptDesc);
80 if (slot < 0)
81 {
82 return std::unexpected(Error::NotFound("no vtable slot for GetScriptDesc"));
83 }
84 const auto getScriptDesc = reinterpret_cast<GetScriptDescFn>(static_cast<void**>(classTable)[slot]);
85 const auto* description = static_cast<const ScriptClassDesc_t*>(getScriptDesc(&classTable));
86 if (!description)
87 {
88 return std::unexpected(Error::NotFound("the class has no VScript description"));
89 }
90
91 const ScriptFunctionBinding* binding = FindBinding(description, name);
92 if (!binding || !binding->Function)
93 {
94 return std::unexpected(Error::NotFound(std::format("no VScript binding '{}'", name)));
95 }
96
97 const int index = VirtualIndex(binding->Function);
98 return index >= 0 ? ScriptTarget{.Index = index} : ScriptTarget{.Address = binding->Function};
99}
100
101} // namespace VoltMod
static int VirtualIndex(const void *function)
static std::string ReadFile(const std::filesystem::path &path)
Definition Loader.cpp:56
bool IsReadableAddress(const void *address, size_t bytes)
Result< ScriptTarget > FindScriptBinding(void *classTable, std::string_view name)
static const ScriptFunctionBinding * FindBinding(const ScriptClassDesc_t *description, std::string_view name)
std::expected< T, Error > Result
Definition Result.hpp:64
static Error NotFound(std::string detail)
Definition Result.hpp:49
ScriptFuncDescriptor_t Descriptor