1 /*
2 * qstat
3 * by Steve Jankowski
4 *
5 * Terraria / TShock 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 #define strtok_ret strtok_r
18 #else
19 #include <winsock.h>
20 #define strtok_ret strtok_s
21 #endif
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <ctype.h>
25
26 #include "debug.h"
27 #include "qstat.h"
28 #include "packet_manip.h"
29
30 query_status_t
send_terraria_request_packet(struct qserver * server)31 send_terraria_request_packet(struct qserver *server)
32 {
33 return (send_packet(server, server->type->status_packet, server->type->status_len));
34 }
35
36
37 query_status_t
deal_with_terraria_packet(struct qserver * server,char * rawpkt,int pktlen)38 deal_with_terraria_packet(struct qserver *server, char *rawpkt, int pktlen)
39 {
40 char *s, *key, *val, *sep, *linep, *varp;
41 int complete_response = 0;
42 char last_char;
43 unsigned short port = 0;
44
45 debug(2, "processing...");
46
47 if (0 == pktlen) {
48 // Disconnect?
49 return (REQ_ERROR);
50 }
51
52 complete_response = (0 == strncmp("HTTP/1.1 200", rawpkt, 12) && '}' == rawpkt[pktlen - 1]) ? 1 : 0;
53 last_char = rawpkt[pktlen - 1];
54 rawpkt[pktlen - 1] = '\0';
55
56 debug(3, "packet: combined = %d, complete = %d", server->combined, complete_response);
57 if (!complete_response) {
58 if (!server->combined) {
59 // response fragment recieved
60 int pkt_id;
61 int pkt_max;
62 server->retry1 = n_retries;
63 if (0 == server->n_requests) {
64 server->ping_total = time_delta(&packet_recv_time, &server->packet_time1);
65 server->n_requests++;
66 }
67
68 // We're expecting more to come
69 debug(5, "fragment recieved...");
70 pkt_id = packet_count(server);
71 pkt_max = pkt_id + 1;
72 rawpkt[pktlen - 1] = last_char; // restore the last character
73 if (!add_packet(server, 0, pkt_id, pkt_max, pktlen, rawpkt, 1)) {
74 // fatal error e.g. out of memory
75 return (MEM_ERROR);
76 }
77
78 if (0 == pkt_id) {
79 return (INPROGRESS);
80 }
81
82 // combine_packets will call us recursively
83 return (combine_packets(server));
84 }
85
86 // recursive call which is still incomplete
87 return (INPROGRESS);
88 }
89
90 // find the end of the headers
91 s = strstr(rawpkt, "\x0d\x0a\x0d\x0a");
92
93 if (NULL == s) {
94 debug(1, "Error: missing end of headers");
95 return (REQ_ERROR);
96 }
97
98 s += 4;
99
100 // Correct ping
101 // Not quite right but gives a good estimate
102 server->ping_total = (server->ping_total * server->n_requests) / 2;
103
104 debug(3, "processing response...");
105
106 s = strtok_ret(s, "\x0d\x0a", &linep);
107
108 // NOTE: id=XXX and msg=XXX will be processed by the mod following the one they where the response of
109 while (NULL != s) {
110 debug(2, "LINE: %s\n", s);
111 if (0 == strcmp(s, "{")) {
112 s = strtok_ret(NULL, "\x0d\x0a", &linep);
113 continue;
114 }
115
116 s = strtok_ret(s, "\"", &varp);
117 key = strtok_ret(NULL, "\"", &varp);
118 sep = strtok_ret(NULL, " ", &varp);
119 val = strtok_ret(NULL, "\"", &varp);
120 if (NULL == val) {
121 // world etc may be empty which results in NULL val
122 s = strtok_ret(NULL, "\x0d\x0a", &linep);
123 continue;
124 }
125 //if ( NULL == val && sep
126 debug(2, "var: '%s' = '%s', sep: '%s'\n", key, val, sep);
127 if (0 == strcmp(key, "name")) {
128 server->server_name = strdup(val);
129 } else if (0 == strcmp(key, "port")) {
130 port = atoi(val);
131 change_server_port(server, port, 0);
132 add_rule(server, key, val, NO_FLAGS);
133 } else if (0 == strcmp(key, "playercount")) {
134 server->num_players = atoi(val);
135 } else if (0 == strcmp(key, "maxplayers")) {
136 server->max_players = atoi(val);
137 } else if (0 == strcmp(key, "world")) {
138 server->map_name = strdup(val);
139 } else if (0 == strcmp(key, "players")) {
140 // Players are ", " seperated but potentially player names can have spaces
141 // so we manually check for the leading space and fix if found
142 char *playersp;
143 char *player_name = strtok_ret(val, ",", &playersp);
144 while (NULL != player_name) {
145 struct player *player = add_player(server, server->n_player_info);
146 if (NULL != player) {
147 if (' ' == player_name[0]) {
148 player_name++;
149 }
150 player->name = strdup(player_name);
151 debug(4, "Player: %s\n", player->name);
152 }
153 player_name = strtok_ret(NULL, ",", &playersp);
154 }
155 } else if (0 == strcmp(key, "status")) {
156 if (200 != atoi(val)) {
157 server->server_name = DOWN;
158 return (DONE_FORCE);
159 }
160 } else {
161 add_rule(server, key, val, NO_FLAGS);
162 }
163
164 s = strtok_ret(NULL, "\x0d\x0a", &linep);
165 }
166
167 gettimeofday(&server->packet_time1, NULL);
168
169 return (DONE_FORCE);
170 }
171