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