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 | |
10 | static 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 | |
23 | bool 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 | |
39 | bool 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 | |
49 | bool 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 | |
59 | bool 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 | |
192 | bool 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 | |
228 | CServerInfo2::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 | |