VoltMod
C++23 framework for CS2 server plugins
Loading...
Searching...
No Matches
ClientConVars.hpp
Go to the documentation of this file.
1#pragma once
2
9#include <cstddef>
10#include <functional>
11#include <memory>
12#include <string>
13#include <string_view>
14
15namespace VoltMod
16{
17
18/** @brief How the client answered a convar query (CCLCMsg_RespondCvarValue::status_code). */
20{
21 Answered = 0, ///< The client reported the convar's current value.
22 NotFound, ///< The client has no convar by that name.
23 NotAConVar, ///< The name exists on the client but is a command, not a convar.
24 Protected ///< The convar is marked protected; the client withholds its value.
25};
26
27/**
28 * @brief Asks a connected client what one of its own convars is set to.
29 *
30 * The server sends `CSVCMsg_GetCvarValue` with a cookie. The client answers later with
31 * `CCLCMsg_RespondCvarValue`, intercepted through a vtable hook on `CServerSideClient`.
32 * Queries are asynchronous, unordered, and may never complete.
33 *
34 * A modified client can answer with anything, so treat the result as evidence, not proof.
35 *
36 * This optional load step depends on the `CServerSideClient::ProcessRespondCvarValue` vtable slot,
37 * the `CServerSideClientBase::m_nClientSlot` offset, and an RTTI or symbol lookup of the
38 * `CServerSideClient` vtable. These values drift with engine updates. On failure @ref Available
39 * carries the reason.
40 *
41 * @code
42 * runtime.ClientConVars.Query(slot, "sensitivity",
43 * [](int slot, ClientConVarStatus status, std::string_view name, std::string_view value) {
44 * if (status == ClientConVarStatus::Answered)
45 * Log::Info("{} = {}", name, value);
46 * });
47 * @endcode
48 */
50{
51public:
52 /**
53 * Invoked on the game thread when the client answers. @p name and @p value borrow the decoded
54 * message, so copy what you keep. @p value is empty unless @p status is Answered.
55 */
57 std::function<void(int slot, ClientConVarStatus status, std::string_view name, std::string_view value)>;
58
59 /** @p interfaces and @p bindings drive the response hook and the query send path. @p slots
60 * tells the service when a slot changes hands, so an answer can never reach the callback of
61 * whoever held the slot before. All three must outlive it; the Runtime declares them above.
62 * Installs the response hook; a failure leaves the service inert and names itself in
63 * @ref Available. */
66 ClientConVars(const ClientConVars&) = delete;
68
69 /** Why queries cannot be sent: the error the hook install returned. */
70 Status Available() const;
71
72 /**
73 * Ask @p slot for its value of @p cvarName. False when the service is not @ref Available, the
74 * slot holds a bot or nobody, the per-slot pending cap is reached, or the message could not
75 * be sent.
76 *
77 * A convar already in flight for that slot re-targets the outstanding request rather than
78 * sending a second one, so polling cannot flood a client. Pending entries expire silently
79 * after 10 seconds.
80 */
81 bool Query(int slot, std::string_view cvarName, QueryCallback callback);
82
83 /** Number of queries awaiting an answer on @p slot. Diagnostics only. */
84 size_t PendingCount(int slot) const;
85
86 /** @internal Called by the framework's StartupServer hook: drops every pending query. */
87 void OnServerStartup();
88
89private:
90 Status Install();
91
92 /** Deliver one CCLCMsg_RespondCvarValue, the message type the response hook carries. */
93 void OnRespondCvarValue(const EngineClient& client, const CNetMessage& message);
94
95 /** Send a query to one connected human client. */
96 bool Send(int slot, std::string_view cvarName, int cookie);
97
98 Interfaces& _interfaces;
99 const Bindings& _bindings;
100 /** Behind a pointer only so its header stays under src/, where its tests live. */
101 std::unique_ptr<PendingConVarQueries> _pending;
102 INetworkMessageInternal* _getCvarValue = nullptr;
103 Error _failure = Error::NotReady("client convar queries are not initialized");
104 Subscription _hook;
105 /** Declared after _pending so it unregisters before the table its callback clears. */
106 Subscription _slotListener;
107};
108
109} // namespace VoltMod
Asks a connected client what one of its own convars is set to.
ClientConVars(const ClientConVars &)=delete
bool Query(int slot, std::string_view cvarName, QueryCallback callback)
std::function< void(int slot, ClientConVarStatus status, std::string_view name, std::string_view value)> QueryCallback
size_t PendingCount(int slot) const
ClientConVars & operator=(const ClientConVars &)=delete
"The occupant of this slot changed" - raised by PlayerManager, consumed below it.
Owns one registration and releases it on destruction.
@ NotFound
The named thing does not exist (no such player, convar, row).
static std::string ReadFile(const std::filesystem::path &path)
Definition Loader.cpp:56
ClientConVarStatus
How the client answered a convar query (CCLCMsg_RespondCvarValue::status_code).
@ Protected
The convar is marked protected; the client withholds its value.
@ Answered
The client reported the convar's current value.
@ NotAConVar
The name exists on the client but is a command, not a convar.
std::expected< void, Error > Status
Definition Result.hpp:67
@ Send
point the client at AddonDecision::Id and wait for its reconnect
One failure: a code to branch on, text for the log, and an optional translation key.
Definition Result.hpp:42
static Error NotReady(std::string detail)
Definition Result.hpp:50
Centralized holder for all HL2SDK interface pointers.