diff --git a/code/gamespy/sv_gamespy.c b/code/gamespy/sv_gamespy.c index 0833bf98..8a53dd24 100644 --- a/code/gamespy/sv_gamespy.c +++ b/code/gamespy/sv_gamespy.c @@ -79,10 +79,6 @@ static const char* GS_GAME_VERSION_DEMO[] = static const unsigned int GAMESPY_DEFAULT_PORT = 12300; -void qr_send_statechanged(qr_t qrec); -void qr_shutdown(qr_t qrec); -void qr_process_queries(qr_t qrec); - int qr_init( qr_t *qrec, const char *ip, diff --git a/code/gamespy/sv_gqueryreporting.c b/code/gamespy/sv_gqueryreporting.c index 7eb6131a..985918ce 100644 --- a/code/gamespy/sv_gqueryreporting.c +++ b/code/gamespy/sv_gqueryreporting.c @@ -20,404 +20,144 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ -#include "../qcommon/q_shared.h" -#include "sv_gqueryreporting.h" +/****** +gqueryreporting.h +GameSpy Query & Reporting SDK + +Copyright 2000 GameSpy Industries, Inc -#include "common/gsPlatformSocket.h" -#include "common/gsPlatformUtil.h" -#include "gutil.h" +18002 Skypark Circle +Irvine, CA 92614 +(949)798-4200 +Fax(949)798-4299 +****** -static char *queries[] = {"basic", "info", "rules", "players", "status", "packets", "echo", "secure"}; -static qr_implementation_t static_rec; -static qr_implementation_t *current_rec = &static_rec; -struct sockaddr_in hbaddr; -char qr_hostname[64]; + Please see the GameSpy Query & Reporting SDK documentation for more + information -void qr_check_queries(qr_t qrec); -void qr_check_send_heartbeat(qr_t qrec); -static void parse_query(qr_t qrec, char *query, struct sockaddr *sender); -static void send_heartbeat(qr_t qrec, int statechanged); + Updated 6/9/99 - DDW + Added get_sockaddrin function, and use for resolving + Made portable with additions from CEngine code + Double check that we don't bind to localhost (instead use INADDR_ANY) -void qr_process_queries(qr_t qrec) -{ - if (!qrec) { - qrec = current_rec; - } + Updated 9/1/99 - DDW + Add the ability to pass in 0 as the queryport, to allocate it + automatically. - qr_check_send_heartbeat(qrec); - qr_check_queries(qrec); -} + Updated 10/11/99 - DDW + TCP Heartbeat support (#define TCP_HEARTBEATS) -void qr_process_queries_no_heartbeat(qr_t qrec) -{ - if (!qrec) { - qrec = current_rec; - } + Updated 11/20/99 - BGW + Split goa_process_queries into two functions: goa_check_queries() and + goa_check_send_heartbeat(). - qr_check_queries(qrec); -} + Updated 3/19/00 - DDW + Added Dreamcast CE Support -void qr_check_queries(qr_t qrec) -{ -#define INBUF_LEN 256 - static char indata[INBUF_LEN]; //256 byte input buffer - struct sockaddr_in saddr; - int error; + Updated 4/5/00 - DDW + Added Dreamcast Shinobi Support (use sendto/recvfrom for UDP sockets to work + around connect bug) -#if defined(_LINUX) - unsigned int saddrlen = sizeof(struct sockaddr_in); + Updated 4/17/00 - DDW + Use a single socket for heartbeats/queries (conserve sockets on DC) + + Updated 5/23/00 - DDW + Developer SDK renamed "Query & Reporting SDK" + Encapsulate global data in a structure so multiple simultaneous instances + can run in the same process. Most developers can just pass NULL for the extra + parameter. + qr_init now scans for an open port, starting from a base port. + heartbeats are sent every 30 seconds now until the first query is received, + then they are sent every 5 minutes as recommened by the developer spec + this helps remove the problem of dropped heartbeats causing a server to not + show up for 5 minutes. + + Updated 10/2/00 - DDW + Added qr_process_queries_no_heartbeat, so you can advertise a server on the + LAN only. + Updated 11/9/00 - DDW + Use common alloc/re-alloc functions +******/ + +/******** +INCLUDES +********/ +#if defined(applec) || defined(THINK_C) || defined(__MWERKS__) && !defined(__KATANA__) && !defined(__mips64) + #include "::nonport.h" #else - int saddrlen = sizeof(struct sockaddr_in); + #include "nonport.h" +#endif +#include "sv_gqueryreporting.h" +#include +#include + + +#include +#if !defined(UNDER_CE) && !defined(__KATANA__) +#include +#else +#define assert(a) #endif - while (CanReceiveOnSocket((SOCKET)qrec->hbsock)) { - //else we have data - error = (int)recvfrom((SOCKET)qrec->hbsock, indata, (INBUF_LEN - 1), 0, (struct sockaddr *)&saddr, &saddrlen); +#ifdef __cplusplus +extern "C" { +#endif - if (gsiSocketIsNotError(error)) { - indata[error] = '\0'; - parse_query(qrec, indata, (struct sockaddr *)&saddr); - } - } -} +/***** +TCP_HEARTBEATS +Define this to use reliable heartbeats. Only needed for certain +classes of peer-to-peer games, please contact us if you are unsure +whether you need this or not. +******/ +//#define TCP_HEARTBEATS -void qr_check_send_heartbeat(qr_t qrec) +/******** +TYPEDEFS +********/ +typedef unsigned char uchar; + +typedef enum {qtunknown, qtbasic, qtinfo, qtrules, qtplayers, qtstatus, qtpackets, qtecho, qtsecure} query_t; + +struct qr_implementation_s { - unsigned long tc; + SOCKET querysock; + SOCKET hbsock; + char gamename[64]; + char secret_key[128]; + qr_querycallback_t qr_basic_callback; + qr_querycallback_t qr_info_callback; + qr_querycallback_t qr_rules_callback; + qr_querycallback_t qr_players_callback; + unsigned long lastheartbeat; + int queryid; + int packetnumber; + int qport; + char no_query; + struct sockaddr_in master_saddr; + qr_custom_handler_t qr_custom_handler; + void *udata; +}; - tc = current_time(); - if ((SOCKET)qrec->hbsock != INVALID_SOCKET) { - if (tc - qrec->lastheartbeat > MAX_HEARTBEAT_TIME || tc < qrec->lastheartbeat) { - send_heartbeat(qrec, 0); - } else if (qrec->no_query > 0 && tc - qrec->lastheartbeat > MIN_HEARTBEAT_TIME) { - send_heartbeat(qrec, 0); +/******** +VARS +********/ +static const char *queries[]={"","basic","info","rules","players","status","packets", "echo", "secure"}; +static struct sockaddr_in hbaddr; +static struct qr_implementation_s static_rec = {INVALID_SOCKET, INVALID_SOCKET}; +static qr_t current_rec = &static_rec; +char qr_hostname[64] = MASTER_ADDR; - qrec->no_query++; - if (qrec->no_query > 10) { - qrec->no_query = 0; - } - } - } -} - -void qr_send_statechanged(qr_t qrec) -{ - if (!qrec) { - qrec = current_rec; - } - send_heartbeat(qrec, 1); -} - -void qr_shutdown(qr_t qrec) -{ - if (qrec == NULL) { - qrec = current_rec; - } - if (INVALID_SOCKET != (SOCKET)qrec->hbsock && (SOCKET)qrec->querysock) //if we own the socket - { - closesocket((SOCKET)qrec->hbsock); - } - qrec->hbsock = (void *)INVALID_SOCKET; - qrec->lastheartbeat = 0; - if (qrec->querysock) //if we own the socket - { - SocketShutDown(); - } - if (qrec != &static_rec) //need to free it, it was dynamically allocated - { - free(qrec); - } -} - -/* Return a sockaddrin for the given host (numeric or DNS) and port) -Returns the hostent in savehent if it is not NULL */ -static int get_sockaddrin(const char *host, int port, struct sockaddr_in *saddr, struct hostent **savehent) -{ - struct hostent *hent = NULL; - - saddr->sin_family = AF_INET; - saddr->sin_port = htons((unsigned short)port); - if (host == NULL) { - saddr->sin_addr.s_addr = INADDR_ANY; - } else { - saddr->sin_addr.s_addr = inet_addr(host); - } - - if (saddr->sin_addr.s_addr == INADDR_NONE && strcmp(host, "255.255.255.255") != 0) { - hent = gethostbyname(host); - if (!hent) { - return 0; - } - saddr->sin_addr.s_addr = *(unsigned int *)hent->h_addr_list[0]; - } - if (savehent != NULL) { - *savehent = hent; - } - return 1; -} - -/* value_for_key: this returns a value for a certain key in s, where s is a string -containing key\value pairs. If the key does not exist, it returns NULL. -Note: the value is stored in a common buffer. If you want to keep it, make a copy! */ -static char *value_for_key(const char *s, const char *key) -{ - static int valueindex; - char *pos, *pos2; - char keyspec[256] = "\\"; - static char value[2][256]; - - valueindex ^= 1; - strcat(keyspec, key); - strcat(keyspec, "\\"); - pos = strstr(s, keyspec); - if (!pos) { - return NULL; - } - pos += strlen(keyspec); - pos2 = value[valueindex]; - while (*pos && *pos != '\\') { - *pos2++ = *pos++; - } - *pos2 = '\0'; - return value[valueindex]; -} - -static void packet_send(qr_t qrec, struct sockaddr *addr, char *buffer) -{ - char keyvalue[80]; - - if (!strlen(buffer)) { - return; - } - - qrec->packetnumber += 1; - - Com_sprintf(keyvalue, sizeof(keyvalue), "\\queryid\\%d.%d", qrec->queryid, qrec->packetnumber); - strcat(buffer, keyvalue); - - sendto((SOCKET)qrec->querysock, buffer, (int)strlen(buffer), 0, addr, sizeof(*addr)); - *buffer = 0; -} - -static void buffer_send(qr_t qrec, struct sockaddr *sender, char *buffer, char *newdata) -{ - int bcount; - int i; - char *lastkey; - char *pos; - - bcount = 0; - if (strlen(newdata) + strlen(buffer) < MAX_INFO_STRING) { - strcat(buffer, newdata); - return; - } - - pos = newdata; - while (strlen(pos) > MAX_INFO_STRING) { - lastkey = pos; - - for (i = 0; i < MAX_INFO_STRING; ++i) { - if (pos[i] == '\\') { - if (!(bcount % 2)) { - lastkey = pos + i; - } - - ++bcount; - } - } - - if (lastkey == pos) { - return; - } - - *lastkey = 0; - buffer_send(qrec, sender, buffer, pos); - *lastkey = '\\'; - pos = lastkey; - bcount = 0; - - if (strlen(buffer) + strlen(lastkey) < MAX_INFO_STRING) { - strcat(buffer, pos); - return; - } - } - - packet_send(qrec, sender, buffer); - strcpy(buffer, pos); -} - -static void send_basic(qr_t qrec, struct sockaddr *sender, char *outbuf) -{ - char keyvalue[MAX_KEYVALUES_LENGTH] = {0}; - - qrec->qr_basic_callback(keyvalue, MAX_KEYVALUES_LENGTH, qrec->udata); - buffer_send(qrec, sender, outbuf, keyvalue); -} - -static void send_info(qr_t qrec, struct sockaddr *sender, char *outbuf) -{ - char keyvalue[MAX_KEYVALUES_LENGTH] = {0}; - - qrec->qr_info_callback(keyvalue, MAX_KEYVALUES_LENGTH, qrec->udata); - buffer_send(qrec, sender, outbuf, keyvalue); -} - -static void send_rules(qr_t qrec, struct sockaddr *sender, char *outbuf) -{ - char keyvalue[MAX_KEYVALUES_LENGTH] = {0}; - - qrec->qr_rules_callback(keyvalue, MAX_KEYVALUES_LENGTH, qrec->udata); - buffer_send(qrec, sender, outbuf, keyvalue); -} - -static void send_players(qr_t qrec, struct sockaddr *sender, char *outbuf) -{ - char keyvalue[MAX_KEYVALUES_LENGTH] = {0}; - - qrec->qr_players_callback(keyvalue, MAX_KEYVALUES_LENGTH, qrec->udata); - buffer_send(qrec, sender, outbuf, keyvalue); -} - -static void send_echo(qr_t qrec, struct sockaddr *sender, char *outbuf, const char *echostr) -{ - char keyvalue[MAX_KEYVALUES_LENGTH] = {0}; - - if (strlen(echostr) > 50) { - return; - } - - Com_sprintf(keyvalue, sizeof(keyvalue), "\\echoresponse\\%s", echostr); - buffer_send(qrec, sender, outbuf, keyvalue); -} - -static void send_final(qr_t qrec, struct sockaddr *sender, char *outbuf, char *validation) -{ - char keyvalue[256]; - uchar encrypted_val[128]; - uchar encoded_val[200]; - int keylen; - - if (*validation) { - keylen = (int)strlen(validation); - - if (keylen >= ARRAY_LEN(encrypted_val)) { - return; - } - - strcpy((char *)encrypted_val, validation); - - gs_encrypt((uchar *)qrec->secret_key, (int)strlen(qrec->secret_key), encrypted_val, keylen); - gs_encode(encrypted_val, keylen, encoded_val); - - Com_sprintf(keyvalue, sizeof(keyvalue), "\\validate\\%s", encoded_val); - buffer_send(qrec, sender, outbuf, keyvalue); - } - - Com_sprintf(keyvalue, sizeof(keyvalue), "\\final\\"); - buffer_send(qrec, sender, outbuf, keyvalue); - packet_send(qrec, sender, outbuf); -} - -static void parse_query(qr_t qrec, char *query, struct sockaddr *sender) -{ - query_t querytype = qtunknown; - char buffer[MAX_KEYVALUES_LENGTH] = {0}; - const char *value; - char validation[256] = {0}; - - if (!qrec) { - qrec = current_rec; - } - - if (*query == ';') { - // custom handler - if (qrec->qr_custom_handler) { - qrec->qr_custom_handler(query, sender); - } - return; - } - - qrec->packetnumber = 0; - qrec->queryid++; - - if (qrec->no_query > 0) { - qrec->no_query = 0; - } - - for (querytype = qtbasic; querytype <= qtsecure; ++querytype) { - value = value_for_key(query, queries[querytype - 1]); - if (value) { - switch (querytype) { - case qtbasic: - send_basic(qrec, sender, buffer); - break; - case qtinfo: - send_info(qrec, sender, buffer); - break; - case qtrules: - send_rules(qrec, sender, buffer); - break; - case qtplayers: - send_players(qrec, sender, buffer); - break; - case qtstatus: - send_basic(qrec, sender, buffer); - send_info(qrec, sender, buffer); - send_rules(qrec, sender, buffer); - send_players(qrec, sender, buffer); - break; - case qtpackets: - send_basic(qrec, sender, buffer); - packet_send(qrec, sender, buffer); - send_info(qrec, sender, buffer); - packet_send(qrec, sender, buffer); - send_rules(qrec, sender, buffer); - packet_send(qrec, sender, buffer); - send_players(qrec, sender, buffer); - break; - case qtecho: - send_echo(qrec, sender, buffer, value); - break; - case qtsecure: - strcpy(validation, value); - break; - default: - continue; - } - } - } - - send_final(qrec, sender, buffer, validation); -} - -static void send_heartbeat(qr_t qrec, int statechanged) -{ - char buf[256]; - - sprintf(buf, "\\heartbeat\\%d\\gamename\\%s", qrec->qport, qrec->gamename); - - if (statechanged) { - sprintf(buf + strlen(buf), "\\statechanged\\%d", statechanged); - } - - sendto( - (SOCKET)qrec->hbsock, - buf, - (int)strlen(buf), - 0, - (const struct sockaddr *)&qrec->master_saddr, - sizeof(qrec->master_saddr) - ); - qrec->lastheartbeat = current_time(); -} - -static int do_connect(void *sock, char *addr, int port, struct sockaddr_in *master_saddr) -{ - get_sockaddrin(addr, port, master_saddr, NULL); - return 0; -} +/******** +PROTOTYPES +********/ +static void send_heartbeat(qr_t qrec, int statechanged); +static void parse_query(qr_t qrec, char *query, struct sockaddr *sender); +static int get_sockaddrin(const char *host, int port, struct sockaddr_in *saddr, struct hostent **savehent); +static int do_connect(SOCKET sock, char *addr, int port, struct sockaddr_in *master_saddr); +void qr_check_queries(qr_t qrec); +void qr_check_send_heartbeat(qr_t qrec); void init_qrec( qr_t *qrec, @@ -433,94 +173,96 @@ void init_qrec( void *userdata ) { - qr_t qr; - - if (qrec) { - qr = (qr_t)malloc(sizeof(qr_implementation_t)); - *qrec = qr; - } else { - qr = &static_rec; - } - - strcpy(qr->gamename, gamename); - strcpy(qr->secret_key, secret_key); - - qr->qport = baseport; - qr->hbsock = (void *)hbsock; - qr->querysock = (void *)querysock; - qr->queryid = 1; - qr->no_query = 1; - qr->udata = userdata; - qr->qr_basic_callback = qr_basic_callback; - qr->qr_info_callback = qr_info_callback; - qr->lastheartbeat = 0; - qr->packetnumber = 0; - qr->qr_players_callback = qr_players_callback; - qr->qr_rules_callback = qr_rules_callback; - qr->qr_custom_handler = NULL; + if (qrec == NULL) + { + current_rec = &static_rec; + } + else + { + current_rec = (qr_t)gsimalloc(sizeof(struct qr_implementation_s)); + *qrec = current_rec; + } + strcpy(current_rec->gamename,gamename); + strcpy(current_rec->secret_key,secret_key); + current_rec->qport = baseport; + current_rec->lastheartbeat = 0; + current_rec->hbsock = hbsock; + current_rec->querysock = querysock; + current_rec->packetnumber = 0; + current_rec->queryid = 1; + current_rec->no_query = 1; + current_rec->udata = userdata; + current_rec->qr_basic_callback = qr_basic_callback; + current_rec->qr_info_callback = qr_info_callback; + current_rec->qr_players_callback = qr_players_callback; + current_rec->qr_rules_callback = qr_rules_callback; + current_rec->qr_custom_handler = NULL; } -#define NUM_PORTS_TO_TRY 100 +/****************************************************************************/ +/* PUBLIC FUNCTIONS */ +/****************************************************************************/ -int qr_init( - qr_t *qrec, - const char *ip, - int baseport, - const char *gamename, - const char *secret_key, - qr_querycallback_t qr_basic_callback, - qr_querycallback_t qr_info_callback, - qr_querycallback_t qr_rules_callback, - qr_querycallback_t qr_players_callback, - void *userdata -) +/* qr_init: Initializes the sockets, etc. Returns an error value +if an error occured, or 0 otherwise */ +int qr_init(qr_t *qrec, const char *ip, int baseport, const char *gamename, const char *secret_key, + qr_querycallback_t qr_basic_callback, + qr_querycallback_t qr_info_callback, + qr_querycallback_t qr_rules_callback, + qr_querycallback_t qr_players_callback, + void *userdata) { - struct sockaddr_in saddr; - SOCKET hbsock; - int maxport; - int lasterror = 0; + int lasterror; + struct sockaddr_in saddr; + int saddrlen; + int maxport; + SOCKET querysock; + SOCKET hbsock; -#if defined(_LINUX) - unsigned int saddrlen; + if (qrec != NULL) //init it to empty + *qrec = NULL; + //create our sockets + SocketStartUp(); + + querysock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); +#ifdef TCP_HEARTBEATS + hbsock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); #else - int saddrlen; + hbsock = querysock; //share the socket #endif + if (INVALID_SOCKET == querysock || INVALID_SOCKET == hbsock) + { + return E_GOA_WSOCKERROR; + } - hbsock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - if (hbsock == INVALID_SOCKET) { - return 1; - } + maxport = baseport + NUM_PORTS_TO_TRY; + while (baseport < maxport) + { + get_sockaddrin(ip,baseport,&saddr,NULL); + if (saddr.sin_addr.s_addr == htonl(0x7F000001)) //localhost -- we don't want that! + saddr.sin_addr.s_addr = INADDR_ANY; + + + lasterror = bind(querysock, (struct sockaddr *)&saddr, sizeof(saddr)); + if (lasterror == 0) + break; //we found a port + baseport++; + } - maxport = baseport + NUM_PORTS_TO_TRY; - while (baseport < maxport) { - get_sockaddrin(ip, baseport, &saddr, NULL); - if (saddr.sin_addr.s_addr == htonl(0x7F000001)) { //localhost -- we don't want that! - saddr.sin_addr.s_addr = INADDR_ANY; - } + if (lasterror != 0) //we weren't able to find a port + { + return E_GOA_BINDERROR; + } - lasterror = bind(hbsock, (struct sockaddr *)&saddr, sizeof(saddr)); - if (lasterror == 0) { - break; //we found a port - } - baseport++; - } - if (lasterror != 0) //we weren't able to find a port - { - return 2; - } - - if (baseport == 0) //we bound it dynamically - { - saddrlen = sizeof(saddr); - - lasterror = getsockname(hbsock, (struct sockaddr *)&saddr, &saddrlen); - - if (lasterror) { - return 2; - } - baseport = ntohs(saddr.sin_port); - } + if (baseport == 0) //we bound it dynamically + { + saddrlen = sizeof(saddr); + lasterror = getsockname(querysock,(struct sockaddr *)&saddr, &saddrlen); + if (lasterror) + return E_GOA_BINDERROR; + baseport = ntohs(saddr.sin_port); + } init_qrec( qrec, @@ -540,5 +282,498 @@ int qr_init( qrec = ¤t_rec; } - return do_connect((void *)hbsock, MASTER_SERVER_HOST, 27900, &(*qrec)->master_saddr); + return do_connect(hbsock, qr_hostname, MASTER_PORT, &(*qrec)->master_saddr); + } + +/* qr_process_queries: Processes any waiting queries, and sends a +heartbeat if 5 minutes have elapsed */ +void qr_process_queries(qr_t qrec) +{ + if (qrec == NULL) + qrec = current_rec; + qr_check_send_heartbeat(qrec); + qr_check_queries(qrec); +} + + +/* qr_process_queries_no_heartbeat: Processes any waiting queries, but +never sends heartbeats */ +void qr_process_queries_no_heartbeat(qr_t qrec) +{ + if (qrec == NULL) + qrec = current_rec; + qr_check_queries(qrec); +} + + + +/* qr_check_queries: Processes any waiting queries */ +void qr_check_queries(qr_t qrec) +{ + static char indata[INBUF_LEN]; //256 byte input buffer + struct sockaddr_in saddr; + int saddrlen = sizeof(struct sockaddr_in); + fd_set set; + struct timeval timeout = {0,0}; + int error; + + if (INVALID_SOCKET == qrec->querysock) + return; //no sockets to work with! + + FD_ZERO ( &set ); + FD_SET ( qrec->querysock, &set ); + + while (1) + { + error = select(FD_SETSIZE, &set, NULL, NULL, &timeout); + if (-1 == error || 0 == error) + return; + //else we have data + error = recvfrom(qrec->querysock, indata, INBUF_LEN - 1, 0, (struct sockaddr *)&saddr, &saddrlen); + if (error != -1) + { + indata[error] = '\0'; + parse_query(qrec, indata, (struct sockaddr *)&saddr); + } + } +} + +/* check_send_heartbeat: Perform any scheduled outgoing +heartbeats, (every 5 minutes) */ +void qr_check_send_heartbeat(qr_t qrec) +{ + unsigned long tc = current_time(); + + if (INVALID_SOCKET == qrec->hbsock) + return; //no sockets to work with! + + //check if we need to send a heartbet + if (tc - qrec->lastheartbeat > HB_TIME || qrec->lastheartbeat == 0 || tc < qrec->lastheartbeat) + send_heartbeat(qrec,0); + else if (qrec->no_query > 0 && tc - qrec->lastheartbeat > FIRST_HB_TIME) + { //check to see if we haven't gotten a query yet + send_heartbeat(qrec, 0); + qrec->no_query++; + if (qrec->no_query > MAX_FIRST_COUNT) + qrec->no_query = 0; //stop trying to get first query + } +} + +/* qr_send_statechanged: Sends a statechanged heartbeat, call when +your gamemode changes */ +void qr_send_statechanged(qr_t qrec) +{ + if (qrec == NULL) + qrec = current_rec; + send_heartbeat(qrec, 1); +} + +/* qr_shutdown: Cleans up the sockets and shuts down */ +void qr_shutdown(qr_t qrec) +{ + if (qrec == NULL) + qrec = current_rec; + if (INVALID_SOCKET != qrec->querysock) + { + closesocket(qrec->querysock); + } + if (INVALID_SOCKET != qrec->hbsock && qrec->hbsock != qrec->querysock) + { + closesocket(qrec->hbsock); + } + qrec->hbsock = INVALID_SOCKET; + qrec->querysock = INVALID_SOCKET; + qrec->lastheartbeat = 0; + if (qrec != &static_rec) //need to gsifree it, it was dynamically allocated + { + gsifree(qrec); + } + SocketShutDown(); +} + +/****************************************************************************/ + +static int do_connect(SOCKET sock, char *addr, int port, struct sockaddr_in *master_saddr) +{ +#ifdef TCP_HEARTBEATS + struct sockaddr_in saddr; + + get_sockaddrin(addr, port,&saddr,NULL); + + if (connect (sock, (struct sockaddr *) &saddr, sizeof(saddr)) == -1) + { + return E_GOA_CONNERROR; + } +#else + get_sockaddrin(addr, port, master_saddr, NULL); +#endif + return 0; +} + +/* Return a sockaddrin for the given host (numeric or DNS) and port) +Returns the hostent in savehent if it is not NULL */ +static int get_sockaddrin(const char *host, int port, struct sockaddr_in *saddr, struct hostent **savehent) +{ + struct hostent *hent; + + saddr->sin_family = AF_INET; + saddr->sin_port = htons((unsigned short)port); + if (host == NULL) + saddr->sin_addr.s_addr = INADDR_ANY; + else + saddr->sin_addr.s_addr = inet_addr(host); + + if (saddr->sin_addr.s_addr == INADDR_NONE && strcmp(host,"255.255.255.255") != 0) + { + hent = gethostbyname(host); + if (!hent) + return 0; + saddr->sin_addr.s_addr = *(unsigned int *)hent->h_addr_list[0]; + } + if (savehent != NULL) + *savehent = hent; + return 1; + +} + + + +/* value_for_key: this returns a value for a certain key in s, where s is a string +containing key\value pairs. If the key does not exist, it returns NULL. +Note: the value is stored in a common buffer. If you want to keep it, make a copy! */ +static char *value_for_key(const char *s, const char *key) +{ + static int valueindex; + char *pos,*pos2; + char keyspec[256]="\\"; + static char value[2][256]; + + valueindex ^= 1; + strcat(keyspec,key); + strcat(keyspec,"\\"); + pos = strstr(s,keyspec); + if (!pos) + return NULL; + pos += strlen(keyspec); + pos2 = value[valueindex]; + while (*pos && *pos != '\\') + *pos2++ = *pos++; + *pos2 = '\0'; + return value[valueindex]; +} + +/*****************************************************************************/ +/* Various encryption / encoding routines */ + +#ifndef _GUTIL +#define _GUTIL +static void swap_byte ( uchar *a, uchar *b ) +{ + uchar swapByte; + + swapByte = *a; + *a = *b; + *b = swapByte; +} + +static uchar encode_ct ( uchar c ) +{ + if (c < 26) return ('A'+c); + if (c < 52) return ('a'+c-26); + if (c < 62) return ('0'+c-52); + if (c == 62) return ('+'); + if (c == 63) return ('/'); + + return 0; +} + +void gs_encode ( uchar *ins, int size, uchar *result ) +{ + int i,pos; + uchar trip[3]; + uchar kwart[4]; + + i=0; + while (i < size) + { + for (pos=0 ; pos <= 2 ; pos++, i++) + if (i < size) trip[pos] = *ins++; + else trip[pos] = '\0'; + kwart[0] = (trip[0]) >> 2; + kwart[1] = (((trip[0]) & 3) << 4) + ((trip[1]) >> 4); + kwart[2] = (((trip[1]) & 15) << 2) + ((trip[2]) >> 6); + kwart[3] = (trip[2]) & 63; + for (pos=0; pos <= 3; pos++) *result++ = encode_ct(kwart[pos]); + } + *result='\0'; +} + +void gs_encrypt ( uchar *key, int key_len, uchar *buffer_ptr, int buffer_len ) +{ + short counter; + uchar x, y, xorIndex; + uchar state[256]; + + for ( counter = 0; counter < 256; counter++) state[counter] = (uchar) counter; + + x = 0; y = 0; + for ( counter = 0; counter < 256; counter++) + { + y = (key[x] + state[counter] + y) % 256; + x = (x + 1) % key_len; + swap_byte ( &state[counter], &state[y] ); + } + + x = 0; y = 0; + for ( counter = 0; counter < buffer_len; counter ++) + { + x = (x + buffer_ptr[counter] + 1) % 256; + y = (state[x] + y) % 256; + swap_byte ( &state[x], &state[y] ); + xorIndex = (state[x] + state[y]) % 256; + buffer_ptr[counter] ^= state[xorIndex]; + } +} +/*****************************************************************************/ +#endif + +/* packet_send: sends a key\value packet. Appends the queryid +key\value pair. Clears the buffer */ +static void packet_send(qr_t qrec, struct sockaddr *addr, char *buffer) +{ + char keyvalue[80]; + + if (strlen(buffer) == 0) + return; //dont need to send an empty one! + qrec->packetnumber++; //packet numbers start at 1 + snprintf(keyvalue,sizeof(keyvalue),"\\queryid\\%d.%d",qrec->queryid, qrec->packetnumber); + strcat(buffer,keyvalue); + sendto(qrec->querysock, buffer, strlen(buffer), 0, addr, sizeof(struct sockaddr_in)); + buffer[0]='\0'; +} + +/* buffer_send: appends buffer with newdata. If the combined +size would be too large, it flushes buffer first. Space is reserved +on the total size to allow for the queryid key\value */ +static void buffer_send(qr_t qrec, struct sockaddr *sender, char *buffer, char *newdata) +{ + char *pos, *lastkey; + int bcount = 0; + + if (strlen(buffer) + strlen(newdata) < MAX_DATA_SIZE - 50) + { + strcat(buffer, newdata); + } else + { + if (strlen(newdata) > MAX_DATA_SIZE - 50) //incoming data is too big already! + { + lastkey = pos = newdata; + while (pos - newdata < MAX_DATA_SIZE-50) + { + if ('\\' == *pos) + { + if (bcount % 2 == 0) + lastkey = pos; + bcount++; + } + pos++; + } + if (lastkey == newdata) + return; //endless loop - single key was too big! + *lastkey = '\0'; + buffer_send(qrec,sender, buffer, newdata); + *lastkey = '\\'; + buffer_send(qrec,sender, buffer, lastkey); //send the rest! + } else + { + packet_send(qrec,sender, buffer); + strcpy(buffer,newdata); + } + } + +} + +/* send_basic: sends a response to the basic query */ +static void send_basic(qr_t qrec, struct sockaddr *sender, char *outbuf) +{ + char keyvalue[BUF_SIZE] = ""; + + assert(qrec->qr_basic_callback); + qrec->qr_basic_callback(keyvalue, sizeof(keyvalue), qrec->udata); + buffer_send(qrec,sender, outbuf, keyvalue); +} + +/* send_info: sends a response to the info query */ +static void send_info(qr_t qrec, struct sockaddr *sender, char *outbuf) +{ + char keyvalue[BUF_SIZE] = ""; + + assert(qrec->qr_info_callback); + qrec->qr_info_callback(keyvalue, sizeof(keyvalue), qrec->udata); + buffer_send(qrec,sender, outbuf, keyvalue); + +} + +/* send_rules: sends a response to the rules query. */ +static void send_rules(qr_t qrec, struct sockaddr *sender, char *outbuf) +{ + char keyvalue[BUF_SIZE] = ""; + + assert(qrec->qr_rules_callback); + qrec->qr_rules_callback(keyvalue, sizeof(keyvalue), qrec->udata); + buffer_send(qrec,sender, outbuf, keyvalue); + +} + +/* send_players: sends the players and their information.*/ +static void send_players(qr_t qrec, struct sockaddr *sender, char *outbuf) +{ + char keyvalue[BUF_SIZE] = ""; + + assert(qrec->qr_players_callback); + qrec->qr_players_callback(keyvalue, sizeof(keyvalue), qrec->udata); + buffer_send(qrec,sender, outbuf, keyvalue); + +} + +/* send_echo: bounces the echostr back to sender +Note: you should always make sure that your echostr doesn't exceed the MAX_DATA_SIZE*/ +static void send_echo(qr_t qrec, struct sockaddr *sender, char *outbuf,char *echostr) +{ + char keyvalue[MAX_DATA_SIZE]; + + if (strlen(echostr) > MAX_DATA_SIZE - 50) + return; + snprintf(keyvalue,sizeof(keyvalue),"\\echo\\%s",echostr); + buffer_send(qrec,sender, outbuf, keyvalue); + +} + +/* send_final: sends the remaining data in outbuf. Appends the final +key\value to the end. Also adds validation if required. */ +static void send_final(qr_t qrec, struct sockaddr *sender, char *outbuf,char *validation) +{ + char keyvalue[256]; + char encrypted_val[128]; //don't need to null terminate + char encoded_val[200]; + int keylen; + + if (validation[0]) + { + keylen = strlen(validation); + if (keylen > 128) return; + strcpy(encrypted_val, validation); + gs_encrypt((uchar *)qrec->secret_key, strlen(qrec->secret_key), (uchar *)encrypted_val, keylen); + gs_encode((uchar *)encrypted_val,keylen, (uchar *)encoded_val); + snprintf(keyvalue,sizeof(keyvalue),"\\validate\\%s",encoded_val); + buffer_send(qrec,sender, outbuf, keyvalue); + } + + snprintf(keyvalue,sizeof(keyvalue),"\\final\\"); + buffer_send(qrec,sender, outbuf, keyvalue); + packet_send(qrec,sender, outbuf); +} + + +/* parse_query: parse an incoming query (which may contain 1 or more +individual queries) and reply to each query */ +static void parse_query(qr_t qrec, char *query, struct sockaddr *sender) +{ + query_t querytype; + char buffer[MAX_DATA_SIZE]=""; + char *value; + char validation[256] = ""; + + if (qrec == NULL) { + qrec = current_rec; + } + + if (*query == ';') { + // custom handler + if (qrec->qr_custom_handler) { + qrec->qr_custom_handler(query, sender); + } + return; + } + + qrec->queryid++; + qrec->packetnumber = 0; + if (qrec->no_query > 0) + qrec->no_query = 0; + + for (querytype = qtbasic; querytype <= qtsecure ; querytype++) + { + if ((value = value_for_key(query, queries[querytype]))) + switch (querytype) + { + case qtbasic: + send_basic(qrec,sender,buffer); + break; + case qtinfo: + send_info(qrec,sender,buffer); + break; + case qtrules: + send_rules(qrec,sender,buffer); + break; + case qtplayers: + send_players(qrec,sender,buffer); + break; + case qtstatus: + send_basic(qrec,sender,buffer); + send_info(qrec,sender,buffer); + send_rules(qrec,sender,buffer); + send_players(qrec,sender,buffer); + break; + case qtpackets: + /*note: "packets" is NOT a real query type. It is simply here to illustrate + how a large query would look if broken into packets */ + send_basic(qrec,sender,buffer); packet_send(qrec,sender, buffer); + send_info(qrec,sender,buffer); packet_send(qrec,sender, buffer); + send_rules(qrec,sender,buffer); packet_send(qrec,sender, buffer); + send_players(qrec,sender,buffer); + break; + case qtecho: + //note: \echo\value is the syntax here + send_echo(qrec,sender,buffer,value); + break; + case qtsecure: + strcpy(validation, value); + break; + case qtunknown: + break; + + } + } + send_final(qrec,sender,buffer,validation); +} + +/* send_heartbeat: Sends a heartbeat to the gamemaster, +adds \statechanged\ if statechanged != 0 */ +static void send_heartbeat(qr_t qrec, int statechanged) +{ + char buf[256]; + int ret; + + snprintf(buf,sizeof(buf),"\\heartbeat\\%d\\gamename\\%s",qrec->qport, qrec->gamename); + if (statechanged) + strcat(buf,"\\statechanged\\"); +#ifdef TCP_HEARTBEATS + ret = send(qrec->hbsock, buf, strlen(buf), 0); + if (ret < 0) /* try to reconnect (tcp only) */ + { + closesocket(qrec->hbsock); + qrec->hbsock = socket(AF_INET, SOCK_STREAM, IPPROTO_UDP); + if (do_connect(qrec->hbsock, qr_hostname, MASTER_PORT) == 0) + send(qrec->hbsock, buf, strlen(buf), 0); /* try again */ + } +#else + ret = sendto(qrec->hbsock, buf, strlen(buf), 0, (const struct sockaddr *)&qrec->master_saddr, sizeof(struct sockaddr_in)); +#endif + + qrec->lastheartbeat = current_time(); +} + +#ifdef __cplusplus +} +#endif diff --git a/code/gamespy/sv_gqueryreporting.h b/code/gamespy/sv_gqueryreporting.h index d433cf74..308bff03 100644 --- a/code/gamespy/sv_gqueryreporting.h +++ b/code/gamespy/sv_gqueryreporting.h @@ -20,47 +20,180 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ +/****** +gqueryreporting.h +GameSpy Query & Reporting SDK + +Copyright 2000 GameSpy Industries, Inc + +18002 Skypark Circle +Irvine, CA 92614 +(949)798-4200 +Fax(949)798-4299 +****** + + Please see the GameSpy Query & Reporting SDK documentation for more + information + +******/ + #pragma once -#include "common/gsPlatformSocket.h" +#ifdef __cplusplus +extern "C" { +#endif -#define MAX_KEYVALUES_LENGTH 1400 +/******** +ERROR CONSTANTS +--------------- +These constants are returned from qr_init to signal an error condition +***************/ -static const unsigned int MIN_HEARTBEAT_TIME = 30000; // wait at least 30 seconds before new heartbeat can be sent -static const unsigned int MAX_HEARTBEAT_TIME = 300000; // 5 minutes +#define E_GOA_WSOCKERROR 1 +#define E_GOA_BINDERROR 2 +#define E_GOA_DNSERROR 3 +#define E_GOA_CONNERROR 4 +/********* +NUM_PORTS_TO_TRY +---------------- +This value is the maximum number of ports that will be scanned to +find an open query port, starting from the value passed to qr_init +as the base port. Generally there is no reason to modify this value. +***********/ +#define NUM_PORTS_TO_TRY 100 -typedef void (*qr_querycallback_t)(char *outbuf, int maxlen, void *userdata); +/* The hostname of the master server. +If the app resolves the hostname, an +IP can be stored here before calling +qr_init */ +extern char qr_hostname[64]; + +/******** +qr_querycallback_t +------------------- +This is the prototype for the callback functions your game needs to +implement for each of the four basic query types. The callback works the +same for each query type. + +[outbuf] is a pre-allocated buffer for you to place the query reply. It's size is +[maxlen] (default is 1400). If you need larger, you can adjust the + defines in gqueryreporting.c +[userdata] is the pointer that was passed into qr_init. You can use this for an + object or structure pointer if needed. + +Simply fill outbuf with the correct data for the query type (consult the sample +apps and the GameSpy Developer Spec). +outbuf should be a NULL terminated ANSI string. +********/ +typedef void (*qr_querycallback_t)(char *outbuf, int maxlen, void *userdata); typedef void (*qr_custom_handler_t)(const char *query, struct sockaddr *sender); -typedef enum query_e { - qtunknown, - qtbasic, - qtinfo, - qtrules, - qtplayers, - qtstatus, - qtpackets, - qtecho, - qtsecure -} query_t; +/*********** +qr_t +---- +This abstract type is used to instantiate multiple instances of the +Query & Reporting SDK (for example, if you are running multiple servers +in the same process). +For most games, you can ignore this value and pass NULL in to all functions +that require it. A single global instance will be used, similar to how the +original Developer SDK worked +************/ +typedef struct qr_implementation_s *qr_t; -typedef struct qr_implementation_s { - void *querysock; - void *hbsock; - char gamename[64]; - char secret_key[128]; - qr_querycallback_t qr_basic_callback; - qr_querycallback_t qr_info_callback; - qr_querycallback_t qr_rules_callback; - qr_querycallback_t qr_players_callback; - long unsigned int lastheartbeat; - int queryid; - int packetnumber; - int qport; - char no_query; - struct sockaddr_in master_saddr; - qr_custom_handler_t qr_custom_handler; - void *udata; -} qr_implementation_t, *qr_t; +/************ +QR_INIT +-------- +This creates/binds the sockets needed for heartbeats and queries/replies. +[qrec] if not null, will be filled with the qr_t instance for this server. + If you are not using more than one instance of the Query & Reporting SDK you + can pass in NULL for this value. +[ip] is an optional parameter that determines which dotted IP address to bind to on + a multi-homed machine. You can pass NULL to bind to all IP addresses. +[baseport] is the port to accept queries on. If baseport is not available, the + Query and Reporting SDK will scan for an available port in the range of + baseport -> baseport + NUM_PORTS_TO_TRY + Optionally, you can pass in 0 to have a port chosen automatically + (makes it harder for debugging/testing). +[gamename] is the unique gamename that you were given +[secretkey] is your unique secret key +[qr_*_callback] are your data callback functions, this cannot be NULL +[userdata] is an optional, implementation specific parameter that will be + passed to all callback functions. Use it to store an object or structure + pointer if needed. + +Returns +0 is successful, otherwise one of the E_GOA constants above. +************/ +int qr_init(/*[out]*/qr_t *qrec, const char *ip, int baseport, const char *gamename, const char *secret_key, + qr_querycallback_t qr_basic_callback, + qr_querycallback_t qr_info_callback, + qr_querycallback_t qr_rules_callback, + qr_querycallback_t qr_players_callback, + void *userdata); + + +/******************* +QR_PROCESS_QUERIES +------------------- +This function should be called somewhere in your main program loop to +process any pending server queries and send a heartbeat if 5 minutes has +elapsed. + +Query replies are very latency sensative, so you should make sure this +function is called at least every 100ms while your game is in progress. +The function has very low overhead and should not cause any performance +problems. +Unless you are using multiple instances of the SDK, you should pass NULl +for qrec. +The no_heartbeat version will not send any heartbeats to the master - use +this if you only want to advertise your server on the LAN. +********************/ +void qr_process_queries(qr_t qrec); +void qr_process_queries_no_heartbeat(qr_t qrec); + +/***************** +QR_SEND_STATECHANGED +-------------------- +This function forces a \statechanged\ heartbeat to be sent immediately. +Use it any time you have changed the gamestate of your game to signal the +master to update your status. +Also use it before your game exits by changing the gamestate to "exiting" +and sending a statechanged heartbeat. This will insure that your game +is removed from the list promptly. +Unless you are using multiple instances of the SDK, you should pass NULl +for qrec. +*******************/ +void qr_send_statechanged(qr_t qrec); + +/***************** +QR_SHUTDOWN +------------ +This function closes the sockets created in qr_init and takes care of +any misc. cleanup. You should try to call it when before exiting the server +if qr_init was called. +If you pass in a qrec that was returned from qr_init, all resources associated +with that qrec will be freed. If you passed NULL into qr_int, you can pass +NULL in here as well. +******************/ +void qr_shutdown(qr_t qrec); + +void qr_check_queries(qr_t qrec); + +/******** +DEFINES +********/ +#define MASTER_PORT 27900 +#define MASTER_ADDR MASTER_SERVER_HOST +#define FIRST_HB_TIME 30000 /* 30 sec */ +#define HB_TIME 300000 /* 5 minutes */ +#define MAX_FIRST_COUNT 10 /* 10 tries = 5 minutes */ +#define MAX_DATA_SIZE 1400 +#define INBUF_LEN 256 +#define BUF_SIZE 1400 #define MASTER_SERVER_HOST "master.333networks.com" + +#ifdef __cplusplus +} +#endif + diff --git a/code/server/sv_main.c b/code/server/sv_main.c index 2c4f5d8b..c2ffbbd7 100644 --- a/code/server/sv_main.c +++ b/code/server/sv_main.c @@ -267,7 +267,7 @@ void SV_MasterHeartbeat( void ) { if (svs.time >= svs.nextHeartbeatTime) { - svs.nextHeartbeatTime = svs.time + MAX_HEARTBEAT_TIME; + svs.nextHeartbeatTime = svs.time + HB_TIME; SV_GamespyHeartbeat(); } }