1#include "serverinfo.h"
2
3#include "json.h"
4
5#include <base/mem.h>
6#include <base/str.h>
7
8#include <engine/external/json-parser/json.h>
9
10#include <algorithm>
11#include <cstdio>
12
13static bool IsAllowedHex(char c)
14{
15 static const char ALLOWED[] = "0123456789abcdefABCDEF";
16 for(int i = 0; i < (int)sizeof(ALLOWED) - 1; i++)
17 {
18 if(c == ALLOWED[i])
19 {
20 return true;
21 }
22 }
23 return false;
24}
25
26bool ParseCrc(unsigned int *pResult, const char *pString)
27{
28 if(str_length(str: pString) != 8)
29 {
30 return true;
31 }
32 for(int i = 0; i < 8; i++)
33 {
34 if(!IsAllowedHex(c: pString[i]))
35 {
36 return true;
37 }
38 }
39 return sscanf(s: pString, format: "%08x", pResult) != 1;
40}
41
42bool CServerInfo2::FromJson(CServerInfo2 *pOut, const json_value *pJson)
43{
44 bool Result = FromJsonRaw(pOut, pJson);
45 if(Result)
46 {
47 return Result;
48 }
49 return pOut->Validate();
50}
51
52bool CServerInfo2::Validate() const
53{
54 bool Error = false;
55 Error = Error || m_MaxClients < m_MaxPlayers;
56 Error = Error || m_NumClients < m_NumPlayers;
57 Error = Error || m_MaxClients < m_NumClients;
58 Error = Error || m_MaxPlayers < m_NumPlayers;
59 return Error;
60}
61
62bool CServerInfo2::FromJsonRaw(CServerInfo2 *pOut, const json_value *pJson)
63{
64 static constexpr const char *SKIN_PART_NAMES[protocol7::NUM_SKINPARTS] = {"body", "marking", "decoration", "hands", "feet", "eyes"};
65 mem_zero(block: pOut, size: sizeof(*pOut));
66 bool Error;
67
68 const json_value &ServerInfo = *pJson;
69 const json_value &MaxClients = ServerInfo["max_clients"];
70 const json_value &MaxPlayers = ServerInfo["max_players"];
71 const json_value &ClientScoreKind = ServerInfo["client_score_kind"];
72 const json_value &Passworded = ServerInfo["passworded"];
73 const json_value &GameType = ServerInfo["game_type"];
74 const json_value &Name = ServerInfo["name"];
75 const json_value &MapName = ServerInfo["map"]["name"];
76 const json_value &Version = ServerInfo["version"];
77 const json_value &Clients = ServerInfo["clients"];
78 const json_value &RequiresLogin = ServerInfo["requires_login"];
79
80 Error = false;
81 Error = Error || MaxClients.type != json_integer;
82 Error = Error || MaxPlayers.type != json_integer;
83 Error = Error || Passworded.type != json_boolean;
84 Error = Error || (ClientScoreKind.type != json_none && ClientScoreKind.type != json_string);
85 Error = Error || GameType.type != json_string || str_has_cc(str: GameType);
86 Error = Error || Name.type != json_string || str_has_cc(str: Name);
87 Error = Error || MapName.type != json_string || str_has_cc(str: MapName);
88 Error = Error || Version.type != json_string || str_has_cc(str: Version);
89 Error = Error || Clients.type != json_array;
90 if(Error)
91 {
92 return true;
93 }
94 pOut->m_MaxClients = json_int_get(pInteger: &MaxClients);
95 pOut->m_MaxPlayers = json_int_get(pInteger: &MaxPlayers);
96 pOut->m_ClientScoreKind = CServerInfo::CLIENT_SCORE_KIND_UNSPECIFIED;
97 if(ClientScoreKind.type == json_string && str_startswith(str: ClientScoreKind, prefix: "points"))
98 {
99 pOut->m_ClientScoreKind = CServerInfo::CLIENT_SCORE_KIND_POINTS;
100 }
101 else if(ClientScoreKind.type == json_string && str_startswith(str: ClientScoreKind, prefix: "time"))
102 {
103 pOut->m_ClientScoreKind = CServerInfo::CLIENT_SCORE_KIND_TIME;
104 }
105 pOut->m_RequiresLogin = false;
106 if(RequiresLogin.type == json_boolean)
107 {
108 pOut->m_RequiresLogin = RequiresLogin;
109 }
110 pOut->m_Passworded = Passworded;
111 str_copy(dst&: pOut->m_aGameType, src: GameType);
112 str_copy(dst&: pOut->m_aName, src: Name);
113 str_copy(dst&: pOut->m_aMapName, src: MapName);
114 str_copy(dst&: pOut->m_aVersion, src: Version);
115
116 pOut->m_NumClients = 0;
117 pOut->m_NumPlayers = 0;
118 for(unsigned i = 0; i < Clients.u.array.length; i++)
119 {
120 const json_value &Client = Clients[i];
121 const json_value &ClientName = Client["name"];
122 const json_value &Clan = Client["clan"];
123 const json_value &Country = Client["country"];
124 const json_value &Score = Client["score"];
125 const json_value &IsPlayer = Client["is_player"];
126 const json_value &IsAfk = Client["afk"];
127 Error = false;
128 Error = Error || ClientName.type != json_string || str_has_cc(str: ClientName);
129 Error = Error || Clan.type != json_string || str_has_cc(str: Clan);
130 Error = Error || Country.type != json_integer;
131 Error = Error || Score.type != json_integer;
132 Error = Error || IsPlayer.type != json_boolean;
133 if(Error)
134 {
135 return true;
136 }
137 if(i < SERVERINFO_MAX_CLIENTS)
138 {
139 CClient *pClient = &pOut->m_aClients[i];
140 str_copy(dst&: pClient->m_aName, src: ClientName);
141 str_copy(dst&: pClient->m_aClan, src: Clan);
142 pClient->m_Country = json_int_get(pInteger: &Country);
143 if(!in_range(a: pClient->m_Country, lower: CountryCode::MINIMUM, upper: CountryCode::MAXIMUM))
144 {
145 pClient->m_Country = CountryCode::DEFAULT;
146 }
147 pClient->m_Score = json_int_get(pInteger: &Score);
148 pClient->m_IsPlayer = IsPlayer;
149
150 pClient->m_IsAfk = false;
151 if(IsAfk.type == json_boolean)
152 pClient->m_IsAfk = IsAfk;
153
154 // check if a skin is also available
155 bool HasSkin = false;
156 const json_value &SkinObj = Client["skin"];
157 if(SkinObj.type == json_object)
158 {
159 const json_value &SkinName = SkinObj["name"];
160 const json_value &SkinBodyColor = SkinObj["color_body"];
161 const json_value &SkinFeetColor = SkinObj["color_feet"];
162 // 0.6 skin
163 if(SkinName.type == json_string && !str_has_cc(str: SkinName.u.string.ptr))
164 {
165 HasSkin = true;
166 str_copy(dst&: pClient->m_aSkin, src: SkinName.u.string.ptr);
167 // if skin json value existed, then always at least default to "default"
168 if(pClient->m_aSkin[0] == '\0')
169 str_copy(dst&: pClient->m_aSkin, src: "default");
170 // if skin also has custom colors, add them
171 if(SkinBodyColor.type == json_integer && SkinFeetColor.type == json_integer)
172 {
173 pClient->m_CustomSkinColors = true;
174 pClient->m_CustomSkinColorBody = SkinBodyColor.u.integer;
175 pClient->m_CustomSkinColorFeet = SkinFeetColor.u.integer;
176 }
177 // else set custom colors off
178 else
179 {
180 pClient->m_CustomSkinColors = false;
181 }
182 }
183 // 0.7 skin
184 else if(SkinObj[SKIN_PART_NAMES[protocol7::SKINPART_BODY]].type == json_object)
185 {
186 for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
187 {
188 const json_value &SkinPartObj = SkinObj[SKIN_PART_NAMES[Part]];
189 if(SkinPartObj.type == json_object)
190 {
191 const json_value &SkinPartName = SkinPartObj["name"];
192 const json_value &SkinPartColor = SkinPartObj["color"];
193 if(SkinPartName.type == json_string && !str_has_cc(str: SkinPartName.u.string.ptr))
194 {
195 HasSkin = true;
196 str_copy(dst&: pClient->m_aaSkin7[Part], src: SkinPartName.u.string.ptr);
197 if(pClient->m_aaSkin7[Part][0] == '\0' && Part != protocol7::SKINPART_MARKING && Part != protocol7::SKINPART_DECORATION)
198 str_copy(dst&: pClient->m_aaSkin7[Part], src: "standard");
199 }
200 else
201 {
202 HasSkin = false;
203 }
204 if(SkinPartColor.type == json_integer)
205 {
206 pClient->m_aUseCustomSkinColor7[Part] = true;
207 pClient->m_aCustomSkinColor7[Part] = SkinPartColor.u.integer;
208 }
209 else
210 {
211 pClient->m_aUseCustomSkinColor7[Part] = false;
212 }
213 }
214 else
215 {
216 HasSkin = false;
217 }
218 }
219 }
220 }
221
222 // else make it null terminated
223 if(!HasSkin)
224 {
225 pClient->m_aSkin[0] = '\0';
226 pClient->m_aaSkin7[protocol7::SKINPART_BODY][0] = '\0';
227 }
228 }
229
230 pOut->m_NumClients++;
231 if((bool)IsPlayer)
232 {
233 pOut->m_NumPlayers++;
234 }
235 }
236 return false;
237}
238
239bool CServerInfo2::operator==(const CServerInfo2 &Other) const
240{
241 bool Unequal;
242 Unequal = false;
243 Unequal = Unequal || m_MaxClients != Other.m_MaxClients;
244 Unequal = Unequal || m_NumClients != Other.m_NumClients;
245 Unequal = Unequal || m_MaxPlayers != Other.m_MaxPlayers;
246 Unequal = Unequal || m_NumPlayers != Other.m_NumPlayers;
247 Unequal = Unequal || m_ClientScoreKind != Other.m_ClientScoreKind;
248 Unequal = Unequal || m_Passworded != Other.m_Passworded;
249 Unequal = Unequal || str_comp(a: m_aGameType, b: Other.m_aGameType) != 0;
250 Unequal = Unequal || str_comp(a: m_aName, b: Other.m_aName) != 0;
251 Unequal = Unequal || str_comp(a: m_aMapName, b: Other.m_aMapName) != 0;
252 Unequal = Unequal || str_comp(a: m_aVersion, b: Other.m_aVersion) != 0;
253 Unequal = Unequal || m_RequiresLogin != Other.m_RequiresLogin;
254 if(Unequal)
255 {
256 return false;
257 }
258 for(int i = 0; i < std::min(a: m_NumClients, b: (int)SERVERINFO_MAX_CLIENTS); i++)
259 {
260 Unequal = false;
261 Unequal = Unequal || str_comp(a: m_aClients[i].m_aName, b: Other.m_aClients[i].m_aName) != 0;
262 Unequal = Unequal || str_comp(a: m_aClients[i].m_aClan, b: Other.m_aClients[i].m_aClan) != 0;
263 Unequal = Unequal || m_aClients[i].m_Country != Other.m_aClients[i].m_Country;
264 Unequal = Unequal || m_aClients[i].m_Score != Other.m_aClients[i].m_Score;
265 Unequal = Unequal || m_aClients[i].m_IsPlayer != Other.m_aClients[i].m_IsPlayer;
266 Unequal = Unequal || m_aClients[i].m_IsAfk != Other.m_aClients[i].m_IsAfk;
267 if(Unequal)
268 {
269 return false;
270 }
271 }
272 return true;
273}
274
275CServerInfo2::operator CServerInfo() const
276{
277 CServerInfo Result = {.m_ServerIndex: 0};
278 Result.m_MaxClients = m_MaxClients;
279 Result.m_NumClients = m_NumClients;
280 Result.m_MaxPlayers = m_MaxPlayers;
281 Result.m_NumPlayers = m_NumPlayers;
282 Result.m_ClientScoreKind = m_ClientScoreKind;
283 Result.m_RequiresLogin = m_RequiresLogin;
284 Result.m_Flags = m_Passworded ? SERVER_FLAG_PASSWORD : 0;
285 str_copy(dst&: Result.m_aGameType, src: m_aGameType);
286 str_copy(dst&: Result.m_aName, src: m_aName);
287 str_copy(dst&: Result.m_aMap, src: m_aMapName);
288 str_copy(dst&: Result.m_aVersion, src: m_aVersion);
289
290 for(int i = 0; i < std::min(a: m_NumClients, b: (int)SERVERINFO_MAX_CLIENTS); i++)
291 {
292 str_copy(dst&: Result.m_aClients[i].m_aName, src: m_aClients[i].m_aName);
293 str_copy(dst&: Result.m_aClients[i].m_aClan, src: m_aClients[i].m_aClan);
294 Result.m_aClients[i].m_Country = m_aClients[i].m_Country;
295 Result.m_aClients[i].m_Score = m_aClients[i].m_Score;
296 Result.m_aClients[i].m_Player = m_aClients[i].m_IsPlayer;
297 Result.m_aClients[i].m_Afk = m_aClients[i].m_IsAfk;
298
299 // 0.6 skin
300 str_copy(dst&: Result.m_aClients[i].m_aSkin, src: m_aClients[i].m_aSkin);
301 Result.m_aClients[i].m_CustomSkinColors = m_aClients[i].m_CustomSkinColors;
302 Result.m_aClients[i].m_CustomSkinColorBody = m_aClients[i].m_CustomSkinColorBody;
303 Result.m_aClients[i].m_CustomSkinColorFeet = m_aClients[i].m_CustomSkinColorFeet;
304 // 0.7 skin
305 for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
306 {
307 str_copy(dst&: Result.m_aClients[i].m_aaSkin7[Part], src: m_aClients[i].m_aaSkin7[Part]);
308 Result.m_aClients[i].m_aUseCustomSkinColor7[Part] = m_aClients[i].m_aUseCustomSkinColor7[Part];
309 Result.m_aClients[i].m_aCustomSkinColor7[Part] = m_aClients[i].m_aCustomSkinColor7[Part];
310 }
311 }
312
313 Result.m_NumReceivedClients = std::min(a: m_NumClients, b: (int)SERVERINFO_MAX_CLIENTS);
314 Result.m_Latency = -1;
315
316 return Result;
317}
318