1 /*
2 * qstat
3 * by Steve Jankowski
4 *
5 * Gamespy query protocol
6 * Copyright 2005 Steven Hartland
7 *
8 * Licensed under the Artistic License, see LICENSE.txt for license terms
9 *
10 */
11
12 #include <sys/types.h>
13 #ifndef _WIN32
14 #include <sys/socket.h>
15 #endif
16 #include <stdlib.h>
17 #include <stdio.h>
18 #include <ctype.h>
19
20 #include "debug.h"
21 #include "qstat.h"
22 #include "packet_manip.h"
23
24 int
gps_max_players(struct qserver * server)25 gps_max_players(struct qserver *server)
26 {
27 struct player *player;
28 int no_players = 0;
29
30 if (0 == server->num_players) {
31 return (0);
32 }
33
34 for (player = server->players; player; player = player->next) {
35 no_players++;
36 }
37
38 return ((no_players < server->num_players) ? 1 : 0);
39 }
40
41
42 int
gps_player_info_key(char * s,char * end)43 gps_player_info_key(char *s, char *end)
44 {
45 static char *keys[] =
46 {
47 "frags_", "team_", "ping_", "species_",
48 "race_", "deaths_", "score_", "enemy_",
49 "player_", "keyhash_", "teamname_",
50 "playername_", "keyhash_", "kills_", "queryid"
51 };
52 int i;
53
54 for (i = 0; i < sizeof(keys) / sizeof(char *); i++) {
55 int len = strlen(keys[i]);
56 if ((s + len < end) && (strncmp(s, keys[i], len) == 0)) {
57 return (len);
58 }
59 }
60 return (0);
61 }
62
63
64 query_status_t
send_gps_request_packet(struct qserver * server)65 send_gps_request_packet(struct qserver *server)
66 {
67 return (send_packet(server, server->type->status_packet, server->type->status_len));
68 }
69
70
71 query_status_t
deal_with_gps_packet(struct qserver * server,char * rawpkt,int pktlen)72 deal_with_gps_packet(struct qserver *server, char *rawpkt, int pktlen)
73 {
74 char *s, *key, *value, *end;
75 struct player *player = NULL;
76 int id_major = 0, id_minor = 0, final = 0, player_num;
77 char tmp[256];
78
79 debug(2, "processing...");
80
81 server->n_servers++;
82 if (server->server_name == NULL) {
83 server->ping_total += time_delta(&packet_recv_time, &server->packet_time1);
84 } else {
85 gettimeofday(&server->packet_time1, NULL);
86 }
87
88 /*
89 * // We're using the saved_data a bit differently here to track received
90 * // packets.
91 * // pkt_id is the id_major from the \queryid\
92 * // pkt_max is the total number of packets expected
93 * // pkt_index is a bit mask of the packets received. The id_minor of
94 * // \queryid\ provides packet numbers (1 through pkt_max).
95 */
96 if (server->saved_data.pkt_index == -1) {
97 server->saved_data.pkt_index = 0;
98 }
99
100 rawpkt[pktlen] = '\0';
101 end = &rawpkt[pktlen];
102
103 s = rawpkt;
104 while (*s) {
105 // find the '\'
106 while (*s && *s == '\\') {
107 s++;
108 }
109
110 if (!*s) {
111 // out of packet
112 break;
113 }
114 // Start of key
115 key = s;
116
117 // while we still have data and its not a '\'
118 while (*s && *s != '\\') {
119 s++;
120 }
121
122 if (!*s) {
123 // out of packet
124 break;
125 }
126
127 // Terminate the key
128 *s++ = '\0';
129
130 // Now for the value
131 value = s;
132
133 // while we still have data and its not a '\'
134 while (*s && *s != '\\') {
135 s++;
136 }
137
138 if (s[0] && s[1]) {
139 //fprintf( stderr, "%s = %s\n", key, value );
140 if (!isalpha((unsigned char)s[1])) {
141 // escape char?
142 s++;
143 // while we still have data and its not a '\'
144 while (*s && *s != '\\') {
145 s++;
146 }
147 } else if (
148 isalpha((unsigned char)s[1]) &&
149 (0 == strncmp(key, "player_", 7)) &&
150 (0 != strcmp(key, "player_flags"))
151 ) {
152 // possible '\' in player name
153 if (!gps_player_info_key(s + 1, end)) {
154 // yep there was an escape in the player name
155 s++;
156 // while we still have data and its not a '\'
157 while (*s && *s != '\\') {
158 s++;
159 }
160 }
161 }
162 }
163
164 if (*s) {
165 *s++ = '\0';
166 }
167
168 //fprintf( stderr, "%s = %s\n", key, value );
169 if (*value == '\0') {
170 if (strcmp(key, "final") == 0) {
171 final = 1;
172 if (id_minor > server->saved_data.pkt_max) {
173 server->saved_data.pkt_max = id_minor;
174 }
175 continue;
176 }
177 }
178
179 /* This must be done before looking for player info because
180 * "queryid" is a valid according to gps_player_info_key().
181 */
182 if (strcmp(key, "queryid") == 0) {
183 sscanf(value, "%d.%d", &id_major, &id_minor);
184 if (server->saved_data.pkt_id == 0) {
185 server->saved_data.pkt_id = id_major;
186 }
187 if (id_major == server->saved_data.pkt_id) {
188 if (id_minor > 0) {
189 // pkt_index is bitmask of packets recieved
190 server->saved_data.pkt_index |= 1 << (id_minor - 1);
191 }
192 if (final && (id_minor > server->saved_data.pkt_max)) {
193 server->saved_data.pkt_max = id_minor;
194 }
195 }
196 continue;
197 }
198
199 if (player == NULL) {
200 int len = gps_player_info_key(key, end);
201 if (len) {
202 // We have player info
203 int player_number = atoi(key + len);
204 player = get_player_by_number(server, player_number);
205
206 // && gps_max_players( server ) due to bf1942 issue
207 // where the actual no players is correct but more player
208 // details are returned
209 if ((player == NULL) && gps_max_players(server)) {
210 player = add_player(server, player_number);
211 if (player) {
212 // init to -1 so we can tell if
213 // we have team info
214 player->team = -1;
215 player->deaths = -999;
216 }
217 }
218 }
219 }
220
221 if ((strcmp(key, "mapname") == 0) && !server->map_name) {
222 server->map_name = strdup(value);
223 } else if ((strcmp(key, "hostname") == 0) && !server->server_name) {
224 server->server_name = strdup(value);
225 } else if (strcmp(key, "hostport") == 0) {
226 change_server_port(server, atoi(value), 0);
227 } else if (strcmp(key, "maxplayers") == 0) {
228 server->max_players = atoi(value);
229 } else if (strcmp(key, "numplayers") == 0) {
230 server->num_players = atoi(value);
231 } else if ((strcmp(key, server->type->game_rule) == 0) && !server->game) {
232 server->game = strdup(value);
233 add_rule(server, key, value, NO_FLAGS);
234 } else if (strcmp(key, "final") == 0) {
235 final = 1;
236 if (id_minor > server->saved_data.pkt_max) {
237 server->saved_data.pkt_max = id_minor;
238 }
239 continue;
240 } else if ((strncmp(key, "player_", 7) == 0) || (strncmp(key, "playername_", 11) == 0)) {
241 int no;
242 if (strncmp(key, "player_", 7) == 0) {
243 no = atoi(key + 7);
244 } else {
245 no = atoi(key + 11);
246 }
247
248 if (player && (player->number == no)) {
249 player->name = strdup(value);
250 player = NULL;
251 } else if (NULL != (player = get_player_by_number(server, no))) {
252 player->name = strdup(value);
253 player = NULL;
254 } else if (gps_max_players(server)) {
255 // gps_max_players( server ) due to bf1942 issue
256 // where the actual no players is correct but more player
257 // details are returned
258 player = add_player(server, no);
259 if (player) {
260 player->name = strdup(value);
261 // init to -1 so we can tell if
262 // we have team info
263 player->team = -1;
264 player->deaths = -999;
265 }
266 }
267 } else if (strncmp(key, "teamname_", 9) == 0) {
268 // Yes plus 1 BF1942 is a silly
269 players_set_teamname(server, atoi(key + 9) + 1, value);
270 } else if (strncmp(key, "team_t", 6) == 0) {
271 players_set_teamname(server, atoi(key + 6), value);
272 } else if (strncmp(key, "frags_", 6) == 0) {
273 player = get_player_by_number(server, atoi(key + 6));
274 if (NULL != player) {
275 player->frags = atoi(value);
276 }
277 } else if (strncmp(key, "kills_", 6) == 0) {
278 player = get_player_by_number(server, atoi(key + 6));
279 if (NULL != player) {
280 player->frags = atoi(value);
281 }
282 } else if (strncmp(key, "team_", 5) == 0) {
283 player = get_player_by_number(server, atoi(key + 5));
284 if (NULL != player) {
285 if (!isdigit((unsigned char)*value)) {
286 player->team_name = strdup(value);
287 } else {
288 player->team = atoi(value);
289 }
290 server->flags |= FLAG_PLAYER_TEAMS;
291 }
292 } else if (strncmp(key, "skin_", 5) == 0) {
293 player = get_player_by_number(server, atoi(key + 5));
294 if (NULL != player) {
295 player->skin = strdup(value);
296 }
297 } else if (strncmp(key, "mesh_", 5) == 0) {
298 player = get_player_by_number(server, atoi(key + 5));
299 if (NULL != player) {
300 player->mesh = strdup(value);
301 }
302 } else if (strncmp(key, "ping_", 5) == 0) {
303 player = get_player_by_number(server, atoi(key + 5));
304 if (NULL != player) {
305 player->ping = atoi(value);
306 }
307 } else if (strncmp(key, "face_", 5) == 0) {
308 player = get_player_by_number(server, atoi(key + 5));
309 if (NULL != player) {
310 player->face = strdup(value);
311 }
312 } else if (strncmp(key, "deaths_", 7) == 0) {
313 player = get_player_by_number(server, atoi(key + 7));
314 if (NULL != player) {
315 player->deaths = atoi(value);
316 }
317 }
318 // isnum( key[6] ) as halo uses score_tX for team scores
319 else if ((strncmp(key, "score_", 6) == 0) && isdigit((unsigned char)key[6])) {
320 player = get_player_by_number(server, atoi(key + 6));
321 if (NULL != player) {
322 player->score = atoi(value);
323 }
324 } else if (player && (strncmp(key, "playertype", 10) == 0)) {
325 player->team_name = strdup(value);
326 } else if (player && (strncmp(key, "charactername", 13) == 0)) {
327 player->face = strdup(value);
328 } else if (player && (strncmp(key, "characterlevel", 14) == 0)) {
329 player->ship = atoi(value);
330 } else if (strncmp(key, "keyhash_", 8) == 0) {
331 // Ensure these dont make it into the rules
332 } else if (2 == sscanf(key, "%255[^_]_%d", tmp, &player_num)) {
333 // arbitary player info
334 player = get_player_by_number(server, player_num);
335 if (NULL != player) {
336 player_add_info(player, tmp, value, NO_FLAGS);
337 } else if (gps_max_players(server)) {
338 // gps_max_players( server ) due to bf1942 issue
339 // where the actual no players is correct but more player
340 // details are returned
341 player = add_player(server, player_num);
342
343 if (player) {
344 player->name = NULL;
345 // init to -1 so we can tell if
346 // we have team info
347 player->team = -1;
348 player->deaths = -999;
349 }
350 player_add_info(player, tmp, value, NO_FLAGS);
351 }
352 } else {
353 player = NULL;
354 add_rule(server, key, value, NO_FLAGS);
355 }
356 }
357
358 debug(2, "final %d\n", final);
359 debug(2, "pkt_id %d\n", server->saved_data.pkt_id);
360 debug(2, "pkt_max %d\n", server->saved_data.pkt_max);
361 debug(2, "pkt_index %x\n", server->saved_data.pkt_index);
362
363 if (
364 (final && (server->saved_data.pkt_id == 0)) ||
365 (server->saved_data.pkt_max && (server->saved_data.pkt_index >= ((1 << (server->saved_data.pkt_max)) - 1))) ||
366 ((server->num_players < 0) && (id_minor >= 3))
367 ) {
368 return (DONE_FORCE);
369 }
370
371 return (INPROGRESS);
372 }
373