VoltMod
C++23 framework for CS2 server plugins
Loading...
Searching...
No Matches
ConVars.cpp
Go to the documentation of this file.
8#include <eiface.h>
9#include <engine/igameeventsystem.h>
10#include <format>
11#include <icvar.h>
12#include <networkbasetypes.pb.h>
13#include <networksystem/inetworkmessages.h>
14#include <networksystem/netmessage.h>
15#include <string>
16#include <string_view>
17#include <tier1/convar.h>
18
19// ICvar provides no callback context. Each plugin DLL owns one ConVars instance and callback.
21
22static std::string_view Text(const char* value)
23{
24 return value ? std::string_view(value) : std::string_view{};
25}
26
27static void GlobalConVarChangeCallback(ConVarRefAbstract* ref, CSplitScreenSlot /*slot*/, const char* newValue,
28 const char* oldValue, void* /*unk*/)
29{
30 if (!ref || !g_changeCallback)
31 {
32 return;
33 }
34
36 VoltMod::ConVarChange{.Name = Text(ref->GetName()), .OldValue = Text(oldValue), .NewValue = Text(newValue)});
37}
38
39namespace VoltMod
40{
41
43 : Changed({.OnFirst = [this] { return RouteChanges(); }, .OnLast = [this] { StopRoutingChanges(); }}),
44 _interfaces(interfaces)
45{}
46
48{
49 // Guard against subscriptions that outlive the service.
50 StopRoutingChanges();
51}
52
54{
55 if (!_interfaces.CVar)
56 {
57 return std::unexpected(Error::NotReady("ICvar not available"));
58 }
59 return {};
60}
61
63{
64 auto* engine = _interfaces.Engine;
65 if (!engine)
66 {
67 return;
68 }
69
70 // A newline cannot be quoted away - it ends the line and whatever follows runs as its
71 // own command - so an injected one is refused rather than escaped.
72 if (command.find_first_of("\r\n") != std::string_view::npos)
73 {
74 Log::Warn("ConVars: refused a server command with an embedded newline.");
75 return;
76 }
77
78 // ServerCommand adds no separator between buffered commands.
79 std::string line(command);
80 if (line.empty() || line.back() != '\n')
81 {
82 line.push_back('\n');
83 }
84
85 engine->ServerCommand(line.c_str());
86}
87
88Status ConVars::ExecuteFile(std::string_view path)
89{
90 auto text = ReadAllText(path);
91 if (!text)
92 {
93 return std::unexpected(text.error());
94 }
95
96 std::string_view rest = *text;
97 while (!rest.empty())
98 {
99 const auto end = rest.find('\n');
100 const std::string_view raw = rest.substr(0, end);
101 rest = end == std::string_view::npos ? std::string_view{} : rest.substr(end + 1);
102
103 const std::string line = Strings::Trim(raw.substr(0, raw.find("//")));
104 if (!line.empty())
105 {
107 }
108 }
109 return {};
110}
111
112void ConVars::SetByConsole(std::string_view name, std::string_view value)
113{
114 ExecuteServerCommand(std::format("{} {}", name, Strings::QuoteConsoleArg(value)));
115}
116
117INetworkMessageInternal* ConVars::SetConVarMessage()
118{
119 if (_setConVarMsg)
120 {
121 return _setConVarMsg;
122 }
123
124 auto* messages = _interfaces.NetworkMessages;
125 if (!messages)
126 {
127 return nullptr;
128 }
129
130 _setConVarMsg = messages->FindNetworkMessagePartial("SetConVar");
131 if (!_setConVarMsg)
132 {
133 Log::Warn("ConVars: CNETMsg_SetConVar not found; per-client convar overrides are unavailable.");
134 }
135
136 return _setConVarMsg;
137}
138
139bool ConVars::SendToClient(int slot, std::string_view name, std::string_view value)
140{
141 if (!_interfaces.GameEventSystem || !IsValidSlot(slot) || name.empty())
142 {
143 return false;
144 }
145
146 auto* msgType = SetConVarMessage();
147 if (!msgType)
148 {
149 return false;
150 }
151
152 CNetMessage* msg = msgType->AllocateMessage();
153 if (!msg)
154 {
155 return false;
156 }
157
158 auto* setConVar = msg->ToPB<CNETMsg_SetConVar>();
159 if (setConVar)
160 {
161 auto* cvar = setConVar->mutable_convars()->add_cvars();
162 cvar->set_name(std::string(name));
163 cvar->set_value(std::string(value));
164
165 SingleRecipientFilter filter(slot);
166 _interfaces.GameEventSystem->PostEventAbstract(-1, false, &filter, msgType, msg, 0);
167 }
168
169 _interfaces.NetworkMessages->DeallocateNetMessageAbstract(msgType, msg);
170 return setConVar != nullptr;
171}
172
173bool ConVars::RouteChanges()
174{
175 if (_routingChanges)
176 {
177 return true;
178 }
179
180 auto* cvar = _interfaces.CVar;
181 if (!cvar)
182 {
183 Log::Warn("ConVars: ICvar is not resolved; convar change handlers will not fire.");
184 return false;
185 }
186
187 cvar->InstallGlobalChangeCallback(&GlobalConVarChangeCallback);
188 g_changeCallback = this;
189 _routingChanges = true;
190 return true;
191}
192
193void ConVars::StopRoutingChanges()
194{
195 if (!_routingChanges)
196 {
197 return;
198 }
199
200 if (auto* cvar = _interfaces.CVar)
201 {
202 cvar->RemoveGlobalChangeCallback(&GlobalConVarChangeCallback);
203 }
204
205 g_changeCallback = nullptr;
206 _routingChanges = false;
207}
208
209} // namespace VoltMod
static std::string_view Text(const char *value)
Definition ConVars.cpp:22
static void GlobalConVarChangeCallback(ConVarRefAbstract *ref, CSplitScreenSlot, const char *newValue, const char *oldValue, void *)
Definition ConVars.cpp:27
static VoltMod::ConVars * g_changeCallback
Definition ConVars.cpp:20
void SetByConsole(std::string_view name, std::string_view value)
Assign a convar over the console, quoting the value.
Definition ConVars.cpp:112
Status ExecuteFile(std::string_view path)
Definition ConVars.cpp:88
ConVars(Interfaces &interfaces)
Definition ConVars.cpp:42
Event< const ConVarChange & > Changed
Definition ConVar.hpp:145
void ExecuteServerCommand(std::string_view command)
Definition ConVars.cpp:62
Status Available() const
Definition ConVars.cpp:53
static std::string Trim(std::string_view str)
Definition Strings.cpp:107
static std::string QuoteConsoleArg(std::string_view value)
Definition Strings.cpp:216
void Warn(std::format_string< Args... > fmt, Args &&... args)
Definition Log.hpp:88
static std::string ReadFile(const std::filesystem::path &path)
Definition Loader.cpp:56
constexpr bool IsValidSlot(int slot)
Definition Slot.hpp:13
Result< std::string > ReadAllText(std::string_view path)
Read path (resolved via ResolvePath) into a string.
Definition File.cpp:12
std::expected< void, Error > Status
Definition Result.hpp:67
std::string_view Name
Definition ConVar.hpp:20
static Error NotReady(std::string detail)
Definition Result.hpp:50
Centralized holder for all HL2SDK interface pointers.
IVEngineServer2 * Engine
IGameEventSystem * GameEventSystem
INetworkMessages * NetworkMessages