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