@@ -33,6 +33,27 @@ namespace patches
3333
3434 namespace
3535 {
36+ constexpr std::size_t k_huffman_max_decoded_bytes = 0x20000 ;
37+ constexpr std::size_t k_huffman_max_compressed_bytes = k_huffman_max_decoded_bytes / 8 ;
38+ constexpr std::size_t k_ui_replace_directive_max_len = 0x100 ;
39+ constexpr std::size_t k_party_member_join_max_message_bytes = 0x4000 ;
40+
41+ std::size_t bounded_length (const char * value, const std::size_t max_len)
42+ {
43+ if (!value)
44+ {
45+ return 0 ;
46+ }
47+
48+ std::size_t length = 0 ;
49+ while (length < max_len && value[length] != ' \0 ' )
50+ {
51+ ++length;
52+ }
53+
54+ return length;
55+ }
56+
3657 std::string build_shortversion_string ()
3758 {
3859 std::string version = VERSION_PRODUCT ;
@@ -90,6 +111,85 @@ namespace patches
90111 return 1 ;
91112 }
92113
114+ using cl_parse_server_message_huffman_t = unsigned int (__cdecl*)(int , _DWORD*);
115+ utils::hook::detour cl_parse_server_message_huffman_hook;
116+ unsigned int __cdecl CL_ParseServerMessage_huffman_guard (int a1, _DWORD* a2)
117+ {
118+ const auto * const original = reinterpret_cast <cl_parse_server_message_huffman_t >(cl_parse_server_message_huffman_hook.get_original ());
119+
120+ if (!a2)
121+ {
122+ return original (a1, a2);
123+ }
124+
125+ if (a2[5 ] < a2[7 ])
126+ {
127+ game::Com_Error (
128+ (int )" .\\ cl_parse_mp.cpp" ,
129+ 1243 ,
130+ 1 ,
131+ (char *)" Huffman compressed msg cursor underflow detected\n " );
132+ return 0 ;
133+ }
134+
135+ const auto compressed_bytes = static_cast <std::size_t >(a2[5 ] - a2[7 ]);
136+ if (compressed_bytes > k_huffman_max_compressed_bytes)
137+ {
138+ game::Com_Error (
139+ (int )" .\\ cl_parse_mp.cpp" ,
140+ 1243 ,
141+ 1 ,
142+ (char *)" Huffman compressed msg exceeded safe decode limit (%zu > %zu)\n " ,
143+ compressed_bytes,
144+ k_huffman_max_compressed_bytes);
145+ return 0 ;
146+ }
147+
148+ return original (a1, a2);
149+ }
150+
151+ using ui_replace_directive_t = char *(__fastcall*)(int , char *, int , unsigned __int8);
152+ utils::hook::detour ui_replace_directive_hook;
153+ char * __fastcall UI_ReplaceDirective_guard (int ArgList, char * a2, int a3, unsigned __int8 a4)
154+ {
155+ const auto * const original = reinterpret_cast <ui_replace_directive_t >(ui_replace_directive_hook.get_original ());
156+ const auto * const arg_list = reinterpret_cast <const char *>(ArgList);
157+ if (bounded_length (arg_list, k_ui_replace_directive_max_len + 1 ) > k_ui_replace_directive_max_len
158+ || bounded_length (a2, k_ui_replace_directive_max_len + 1 ) > k_ui_replace_directive_max_len)
159+ {
160+ game::Com_Printf (0 , " UI_ReplaceDirective: rejected oversized directive input\n " );
161+ return a2;
162+ }
163+
164+ return original (ArgList, a2, a3, a4);
165+ }
166+
167+ using party_atomic_host_handle_member_join_t = int (__cdecl*)(char , _DWORD*, int , __int64, int , _DWORD*);
168+ utils::hook::detour party_atomic_host_handle_member_join_hook;
169+ int __cdecl PartyAtomicHost_HandleMemberJoin_guard (char a1, _DWORD* a2, int a3, __int64 a4, int a5, _DWORD* a6)
170+ {
171+ const auto * const original = reinterpret_cast <party_atomic_host_handle_member_join_t >(party_atomic_host_handle_member_join_hook.get_original ());
172+ if (!a2 || !a6)
173+ {
174+ return original (a1, a2, a3, a4, a5, a6);
175+ }
176+
177+ if (a6[5 ] < a6[7 ])
178+ {
179+ game::Com_Printf (0 , " PartyAtomicHost_HandleMemberJoin: rejected malformed message cursor\n " );
180+ return 0 ;
181+ }
182+
183+ const auto unread_bytes = static_cast <std::size_t >(a6[5 ] - a6[7 ]);
184+ if (unread_bytes > k_party_member_join_max_message_bytes)
185+ {
186+ game::Com_Printf (0 , " PartyAtomicHost_HandleMemberJoin: rejected oversized message (%zu bytes)\n " , unread_bytes);
187+ return 0 ;
188+ }
189+
190+ return original (a1, a2, a3, a4, a5, a6);
191+ }
192+
93193 bool dvar_enabled (const char * name)
94194 {
95195 const auto * const dvar = game::Dvar_FindVar (name);
@@ -497,6 +597,10 @@ namespace patches
497597#endif
498598 }, scheduler::main);
499599
600+ cl_parse_server_message_huffman_hook.create (game::game_offset (0x1030D960 ), CL_ParseServerMessage_huffman_guard);
601+ ui_replace_directive_hook.create (game::game_offset (0x102BB870 ), UI_ReplaceDirective_guard);
602+ party_atomic_host_handle_member_join_hook.create (game::game_offset (0x103087B0 ), PartyAtomicHost_HandleMemberJoin_guard);
603+
500604 scheduler::loop ([]
501605 {
502606 make_dvar_saved_and_writable (" sv_cheats" );
0 commit comments