1#include "serverinfo.h"
2
3#include "json.h"
4
5#include <base/math.h>
6#include <base/mem.h>
7#include <base/str.h>
8
9#include <engine/external/json-parser/json.h>
10
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: ClientName);
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 pClient->m_Score = json_int_get(pInteger: &Score);
144 pClient->m_IsPlayer = IsPlayer;
145
146 pClient->m_IsAfk = false;
147 if(IsAfk.type == json_boolean)
148 pClient->m_IsAfk = IsAfk;
149
150 // check if a skin is also available
151 bool HasSkin = false;
152 const json_value &SkinObj = Client["skin"];
153 if(SkinObj.type == json_object)
154 {
155 const json_value &SkinName = SkinObj["name"];
156 const json_value &SkinBodyColor = SkinObj["color_body"];
157 const json_value &SkinFeetColor = SkinObj["color_feet"];
158 // 0.6 skin
159 if(SkinName.type == json_string)
160 {
161 HasSkin = true;
162 str_copy(dst&: pClient->m_aSkin, src: SkinName.u.string.ptr);
163 // if skin json value existed, then always at least default to "default"
164 if(pClient->m_aSkin[0] == '\0')
165 str_copy(dst&: pClient->m_aSkin, src: "default");
166 // if skin also has custom colors, add them
167 if(SkinBodyColor.type == json_integer && SkinFeetColor.type == json_integer)
168 {
169 pClient->m_CustomSkinColors = true;
170 pClient->m_CustomSkinColorBody = SkinBodyColor.u.integer;
171 pClient->m_CustomSkinColorFeet = SkinFeetColor.u.integer;
172 }
173 // else set custom colors off
174 else
175 {
176 pClient->m_CustomSkinColors = false;
177 }
178 }
179 // 0.7 skin
180 else if(SkinObj[SKIN_PART_NAMES[protocol7::SKINPART_BODY]].type == json_object)
181 {
182 for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
183 {
184 const json_value &SkinPartObj = SkinObj[SKIN_PART_NAMES[Part]];
185 if(SkinPartObj.type == json_object)
186 {
187 const json_value &SkinPartName = SkinPartObj["name"];
188 const json_value &SkinPartColor = SkinPartObj["color"];
189 if(SkinPartName.type == json_string)
190 {
191 HasSkin = true;
192 str_copy(dst&: pClient->m_aaSkin7[Part], src: SkinPartName.u.string.ptr);
193 if(pClient->m_aaSkin7[Part][0] == '\0' && Part != protocol7::SKINPART_MARKING && Part != protocol7::SKINPART_DECORATION)
194 str_copy(dst&: pClient->m_aaSkin7[Part], src: "standard");
195 }
196 else
197 {
198 HasSkin = false;
199 }
200 if(SkinPartColor.type == json_integer)
201 {
202 pClient->m_aUseCustomSkinColor7[Part] = true;
203 pClient->m_aCustomSkinColor7[Part] = SkinPartColor.u.integer;
204 }
205 else
206 {
207 pClient->m_aUseCustomSkinColor7[Part] = false;
208 }
209 }
210 else
211 {
212 HasSkin = false;
213 }
214 }
215 }
216 }
217
218 // else make it null terminated
219 if(!HasSkin)
220 {
221 pClient->m_aSkin[0] = '\0';
222 pClient->m_aaSkin7[protocol7::SKINPART_BODY][0] = '\0';
223 }
224 }
225
226 pOut->m_NumClients++;
227 if((bool)IsPlayer)
228 {
229 pOut->m_NumPlayers++;
230 }
231 }
232 return false;
233}
234
235bool CServerInfo2::operator==(const CServerInfo2 &Other) const
236{
237 bool Unequal;
238 Unequal = false;
239 Unequal = Unequal || m_MaxClients != Other.m_MaxClients;
240 Unequal = Unequal || m_NumClients != Other.m_NumClients;
241 Unequal = Unequal || m_MaxPlayers != Other.m_MaxPlayers;
242 Unequal = Unequal || m_NumPlayers != Other.m_NumPlayers;
243 Unequal = Unequal || m_ClientScoreKind != Other.m_ClientScoreKind;
244 Unequal = Unequal || m_Passworded != Other.m_Passworded;
245 Unequal = Unequal || str_comp(a: m_aGameType, b: Other.m_aGameType) != 0;
246 Unequal = Unequal || str_comp(a: m_aName, b: Other.m_aName) != 0;
247 Unequal = Unequal || str_comp(a: m_aMapName, b: Other.m_aMapName) != 0;
248 Unequal = Unequal || str_comp(a: m_aVersion, b: Other.m_aVersion) != 0;
249 Unequal = Unequal || m_RequiresLogin != Other.m_RequiresLogin;
250 if(Unequal)
251 {
252 return false;
253 }
254 for(int i = 0; i < m_NumClients; i++)
255 {
256 Unequal = false;
257 Unequal = Unequal || str_comp(a: m_aClients[i].m_aName, b: Other.m_aClients[i].m_aName) != 0;
258 Unequal = Unequal || str_comp(a: m_aClients[i].m_aClan, b: Other.m_aClients[i].m_aClan) != 0;
259 Unequal = Unequal || m_aClients[i].m_Country != Other.m_aClients[i].m_Country;
260 Unequal = Unequal || m_aClients[i].m_Score != Other.m_aClients[i].m_Score;
261 Unequal = Unequal || m_aClients[i].m_IsPlayer != Other.m_aClients[i].m_IsPlayer;
262 Unequal = Unequal || m_aClients[i].m_IsAfk != Other.m_aClients[i].m_IsAfk;
263 if(Unequal)
264 {
265 return false;
266 }
267 }
268 return true;
269}
270
271CServerInfo2::operator CServerInfo() const
272{
273 CServerInfo Result = {.m_ServerIndex: 0};
274 Result.m_MaxClients = m_MaxClients;
275 Result.m_NumClients = m_NumClients;
276 Result.m_MaxPlayers = m_MaxPlayers;
277 Result.m_NumPlayers = m_NumPlayers;
278 Result.m_ClientScoreKind = m_ClientScoreKind;
279 Result.m_RequiresLogin = m_RequiresLogin;
280 Result.m_Flags = m_Passworded ? SERVER_FLAG_PASSWORD : 0;
281 str_copy(dst&: Result.m_aGameType, src: m_aGameType);
282 str_copy(dst&: Result.m_aName, src: m_aName);
283 str_copy(dst&: Result.m_aMap, src: m_aMapName);
284 str_copy(dst&: Result.m_aVersion, src: m_aVersion);
285
286 for(int i = 0; i < minimum(a: m_NumClients, b: (int)SERVERINFO_MAX_CLIENTS); i++)
287 {
288 str_copy(dst&: Result.m_aClients[i].m_aName, src: m_aClients[i].m_aName);
289 str_copy(dst&: Result.m_aClients[i].m_aClan, src: m_aClients[i].m_aClan);
290 Result.m_aClients[i].m_Country = m_aClients[i].m_Country;
291 Result.m_aClients[i].m_Score = m_aClients[i].m_Score;
292 Result.m_aClients[i].m_Player = m_aClients[i].m_IsPlayer;
293 Result.m_aClients[i].m_Afk = m_aClients[i].m_IsAfk;
294
295 // 0.6 skin
296 str_copy(dst&: Result.m_aClients[i].m_aSkin, src: m_aClients[i].m_aSkin);
297 Result.m_aClients[i].m_CustomSkinColors = m_aClients[i].m_CustomSkinColors;
298 Result.m_aClients[i].m_CustomSkinColorBody = m_aClients[i].m_CustomSkinColorBody;
299 Result.m_aClients[i].m_CustomSkinColorFeet = m_aClients[i].m_CustomSkinColorFeet;
300 // 0.7 skin
301 for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++)
302 {
303 str_copy(dst&: Result.m_aClients[i].m_aaSkin7[Part], src: m_aClients[i].m_aaSkin7[Part]);
304 Result.m_aClients[i].m_aUseCustomSkinColor7[Part] = m_aClients[i].m_aUseCustomSkinColor7[Part];
305 Result.m_aClients[i].m_aCustomSkinColor7[Part] = m_aClients[i].m_aCustomSkinColor7[Part];
306 }
307 }
308
309 Result.m_NumReceivedClients = minimum(a: m_NumClients, b: (int)SERVERINFO_MAX_CLIENTS);
310 Result.m_Latency = -1;
311
312 return Result;
313}
314