VoltMod
C++23 framework for CS2 server plugins
Loading...
Searching...
No Matches
ButtonPressMessage.cpp
Go to the documentation of this file.
2
3#include <cstddef>
4#include <format>
5#include <utility>
6
7namespace VoltMod
8{
9
10/** Protobuf wire types; only these can appear ahead of the fields we want. */
11static constexpr uint32_t WireVarint = 0;
12static constexpr uint32_t WireFixed64 = 1;
13static constexpr uint32_t WireLengthDelimited = 2;
14static constexpr uint32_t WireFixed32 = 5;
15
16/** A varint is at most ten bytes; more is malformed rather than merely large. */
17static constexpr int MaxVarintBytes = 10;
18
19static Result<uint64_t> ReadVarint(std::string_view bytes, size_t& at)
20{
21 uint64_t value = 0;
22 for (int byte = 0; byte < MaxVarintBytes; ++byte)
23 {
24 if (at >= bytes.size())
25 {
26 return std::unexpected(Error::Invalid("a varint runs past the end of the payload"));
27 }
28
29 const auto part = static_cast<uint8_t>(bytes[at++]);
30 value |= static_cast<uint64_t>(part & 0x7Fu) << (7 * byte);
31 if ((part & 0x80u) == 0)
32 {
33 return value;
34 }
35 }
36 return std::unexpected(Error::Invalid("a varint is longer than ten bytes"));
37}
38
39static Status SkipField(std::string_view bytes, size_t& at, uint32_t wireType)
40{
41 switch (wireType)
42 {
43 case WireVarint:
44 {
45 auto skipped = ReadVarint(bytes, at);
46 return skipped ? Status{} : std::unexpected(skipped.error());
47 }
48 case WireFixed64:
49 at += 8;
50 break;
51 case WireFixed32:
52 at += 4;
53 break;
55 {
56 auto length = ReadVarint(bytes, at);
57 if (!length)
58 {
59 return std::unexpected(length.error());
60 }
61
62 // Compared against the bytes left, not added first: a length near 2^64 would wrap the cursor.
63 if (*length > bytes.size() - at)
64 {
65 return std::unexpected(Error::Invalid("a field runs past the end of the payload"));
66 }
67
68 at += static_cast<size_t>(*length);
69 break;
70 }
71 default:
72 // Groups are gone from proto3 and nothing else is legal, so the rest cannot be found.
73 return std::unexpected(Error::Invalid(std::format("unknown protobuf wire type {}", wireType)));
74 }
75
76 if (at > bytes.size())
77 {
78 return std::unexpected(Error::Invalid("a field runs past the end of the payload"));
79 }
80 return {};
81}
82
84{
86 bool haveButton = false;
87
88 size_t at = 0;
89 while (at < bytes.size())
90 {
91 auto key = ReadVarint(bytes, at);
92 if (!key)
93 {
94 return std::unexpected(key.error());
95 }
96
97 const auto field = static_cast<uint32_t>(*key >> 3);
98 const auto wireType = static_cast<uint32_t>(*key & 0x7u);
99
100 if (field == 2 && wireType == WireLengthDelimited)
101 {
102 auto length = ReadVarint(bytes, at);
103 if (!length)
104 {
105 return std::unexpected(length.error());
106 }
107 if (*length > bytes.size() - at)
108 {
109 return std::unexpected(Error::Invalid("the button id runs past the end of the payload"));
110 }
111
112 out.ButtonId.assign(bytes, at, static_cast<size_t>(*length));
113 at += static_cast<size_t>(*length);
114 haveButton = true;
115 continue;
116 }
117
119 {
120 return std::unexpected(skipped.error());
121 }
122 }
123
124 if (!haveButton)
125 {
126 return std::unexpected(Error::Invalid("the payload carries no button id"));
127 }
128
129 return out;
130}
131
132} // namespace VoltMod
static constexpr uint32_t WireLengthDelimited
static constexpr uint32_t WireFixed32
static constexpr uint32_t WireVarint
static Result< uint64_t > ReadVarint(std::string_view bytes, size_t &at)
static std::string ReadFile(const std::filesystem::path &path)
Definition Loader.cpp:56
static constexpr uint32_t WireFixed64
static constexpr int MaxVarintBytes
static Status SkipField(std::string_view bytes, size_t &at, uint32_t wireType)
std::expected< void, Error > Status
Definition Result.hpp:67
std::expected< T, Error > Result
Definition Result.hpp:64
The button id a custom HUD Button press carries, read out of a user message's msg_data.
static Result< ButtonPressMessage > Parse(std::string_view bytes)
static Error Invalid(std::string detail)
Definition Result.hpp:51