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