1 /*
2 * qstat
3 * by Steve Jankowski
4 *
5 * Crysis query protocol
6 * Copyright 2012 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 #include <netinet/in.h>
16 #include <arpa/inet.h>
17 #else
18 #include <winsock.h>
19 #endif
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <ctype.h>
23
24 #include "debug.h"
25 #include "utils.h"
26 #include "qstat.h"
27 #include "md5.h"
28 #include "packet_manip.h"
29
30 char *
decode_farmsim_val(char * val)31 decode_farmsim_val(char *val)
32 {
33 // Very basic html conversion
34 val = str_replace(val, """, "\"");
35 return (str_replace(val, "&", "&"));
36 }
37
38
39 query_status_t
send_farmsim_request_packet(struct qserver * server)40 send_farmsim_request_packet(struct qserver *server)
41 {
42 char buf[256], *code;
43
44 server->saved_data.pkt_max = -1;
45 code = get_param_value(server, "code", "");
46 sprintf(buf, "GET /feed/dedicated-server-stats.xml?code=%s HTTP/1.1\015\012User-Agent: qstat\015\012\015\012", code);
47
48 return (send_packet(server, buf, strlen(buf)));
49 }
50
51
52 query_status_t
valid_farmsim_response(struct qserver * server,char * rawpkt,int pktlen)53 valid_farmsim_response(struct qserver *server, char *rawpkt, int pktlen)
54 {
55 char *s;
56 int len;
57 int cnt = packet_count(server);
58
59 if ((0 == cnt) && (0 != strncmp("HTTP/1.1 200 OK", rawpkt, 15))) {
60 // not valid response
61 debug(2, "Invalid");
62 return (REQ_ERROR);
63 }
64
65 s = strnstr(rawpkt, "Content-Length: ", pktlen);
66 if (s == NULL) {
67 // not valid response
68 debug(2, "Invalid (no content-length)");
69 return (INPROGRESS);
70 }
71 s += 16;
72
73 // TODO: remove this bug work around
74 if (*s == ':') {
75 s += 2;
76 }
77 if (sscanf(s, "%d", &len) != 1) {
78 debug(2, "Invalid (no length)");
79 return (INPROGRESS);
80 }
81
82 s = strnstr(rawpkt, "\015\012\015\012", pktlen);
83 if (s == NULL) {
84 debug(2, "Invalid (no end of header");
85 return (INPROGRESS);
86 }
87
88 s += 4;
89 if (pktlen != (s - rawpkt + len)) {
90 debug(2, "Outstanding data");
91 return (INPROGRESS);
92 }
93
94 debug(2, "Valid data");
95 return (DONE_FORCE);
96 }
97
98
99 char *
farmsim_xml_attrib(char * line,char * name)100 farmsim_xml_attrib(char *line, char *name)
101 {
102 char *q, *p, *val;
103
104 p = strstr(line, name);
105 if (p == NULL) {
106 return (NULL);
107 }
108
109 p += strlen(name);
110 if (strlen(p) < 4) {
111 return (NULL);
112 }
113 p += 2;
114
115 q = strchr(p, '"');
116 if (q == NULL) {
117 return (NULL);
118 }
119 *q = '\0';
120
121 val = strdup(p);
122 *q = '"';
123 debug(4, "%s = %s", name, val);
124
125 return (val);
126 }
127
128
129 query_status_t
deal_with_farmsim_packet(struct qserver * server,char * rawpkt,int pktlen)130 deal_with_farmsim_packet(struct qserver *server, char *rawpkt, int pktlen)
131 {
132 char *s, *val, *line;
133 query_status_t state = INPROGRESS;
134
135 debug(2, "processing...");
136
137 if (!server->combined) {
138 state = valid_farmsim_response(server, rawpkt, pktlen);
139 server->retry1 = n_retries;
140 if (server->n_requests == 0) {
141 server->ping_total = time_delta(&packet_recv_time, &server->packet_time1);
142 server->n_requests++;
143 }
144
145 switch (state) {
146 case INPROGRESS:
147 {
148 // response fragment recieved
149 int pkt_id;
150 int pkt_max;
151
152 // We're expecting more to come
153 debug(5, "fragment recieved...");
154 pkt_id = packet_count(server);
155 pkt_max = pkt_id + 1;
156 if (!add_packet(server, 0, pkt_id, pkt_max, pktlen, rawpkt, 1)) {
157 // fatal error e.g. out of memory
158 return (MEM_ERROR);
159 }
160
161 // combine_packets will call us recursively
162 return (combine_packets(server));
163 }
164
165 case DONE_FORCE:
166 break; // single packet response fall through
167
168 default:
169 return (state);
170 }
171 }
172
173 if (state != DONE_FORCE) {
174 state = valid_farmsim_response(server, rawpkt, pktlen);
175 switch (state) {
176 case DONE_FORCE:
177 break; // actually process
178
179 default:
180 return (state);
181 }
182 }
183
184 // Correct ping
185 // Not quite right but gives a good estimate
186 server->ping_total = (server->ping_total * server->n_requests) / 2;
187
188 debug(3, "processing response...");
189
190 s = rawpkt;
191 // Ensure we're null terminated (will only loose the last \x0a)
192 s[pktlen - 1] = '\0';
193 s = decode_farmsim_val(s);
194 line = strtok(s, "\012");
195
196 // NOTE: id=XXX and msg=XXX will be processed by the mod following the one they where the response of
197 while (line != NULL) {
198 debug(4, "LINE: %s\n", line);
199 if (strstr(line, "<Server") != NULL) {
200 debug(1, "<Server...");
201 // <Server
202 // game="Farming Simulator 2013"
203 // version="2.0.0.5 Public Beta 3"
204 // server="International"
205 // name="Multiplay :: liv3dz0r"
206 // mapName="Hagenstedt"
207 // money="5993"
208 // dayTime="24687375"
209 // mapOverviewFilename="data/maps/map01/pda_map.png">
210
211 // Server Name
212 val = farmsim_xml_attrib(line, "name");
213 if (val != NULL) {
214 server->server_name = val;
215 } else {
216 server->server_name = strdup("Unknown");
217 }
218
219 // Map Name
220 val = farmsim_xml_attrib(line, "mapName");
221 if (val != NULL) {
222 server->map_name = val;
223 } else {
224 server->map_name = strdup("Default");
225 }
226 } else if (strstr(line, "<Slots") != NULL) {
227 // <Slots capacity="16" numUsed="1">
228 debug(1, "<Slots...");
229
230 // Max Players
231 val = farmsim_xml_attrib(line, "capacity");
232 if (val != NULL) {
233 server->max_players = atoi(val);
234 free(val);
235 } else {
236 server->max_players = get_param_ui_value(server, "maxplayers", 1);
237 }
238 // Num Players
239 val = farmsim_xml_attrib(line, "numUsed");
240 if (val != NULL) {
241 server->num_players = atoi(val);
242 free(val);
243 } else {
244 server->num_players = 0;
245 }
246 }
247
248 line = strtok(NULL, "\012");
249 }
250
251 gettimeofday(&server->packet_time1, NULL);
252
253 return (DONE_FORCE);
254 }
255