#include "AdeptHeaders.hpp" #include "GameSpy.h" #define MAX_SERVERS_PER_MACHINE 100 #ifndef NEW_GAMESPY /****** goautil.c GameSpy Developer SDK Copyright 1999 GameSpy Industries, Inc Suite E-204 2900 Bristol Street Costa Mesa, CA 92626 (714)549-7689 Fax(714)549-0757 ****** Please see the GameSpy Developer SDK documentation for more information 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) Updated 9/1/99 - DDW Add the ability to pass in 0 as the queryport, to allocate it automatically. Updated 10/11/99 - DDW TCP Heartbeat support (#define TCP_HEARTBEATS) Updated 11/20/99 - BGW Split goa_process_queries into two functions: goa_check_queries() and goa_check_send_heartbeat(). Updated 3/19/00 - DDW Added Dreamcast CE Support ******/ /******** INCLUDES ********/ #include "nonport.h" #include "goautil.h" #include #include #include #ifndef UNDER_CE #include #else #define assert(a) #endif #ifdef __cplusplus extern "C" { #endif /******** DEFINES ********/ #define HB_TIME 300000 /* 5 minutes */ #define MAX_DATA_SIZE 1400 #define INBUF_LEN 256 #define BUF_SIZE 1400 /***** 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 /******** GLOBAL VARS ********/ goa_querycallback_t goa_basic_callback = NULL; goa_querycallback_t goa_info_callback = NULL; goa_querycallback_t goa_rules_callback = NULL; goa_querycallback_t goa_players_callback = NULL; char goa_secret_key[256]; /******** TYPEDEFS ********/ typedef unsigned char uchar; typedef enum {qtunknown, qtbasic, qtinfo, qtrules, qtplayers, qtstatus, qtpackets, qtecho, qtsecure} query_t; /******** VARS ********/ static const char *queries[]={"","basic","info","rules","players","status","packets", "echo", "secure"}; static unsigned long lastheartbeat = 0; static int queryid = 1; static int packetnumber; static int qport; static void *udata = NULL; static char gname[24]; static SOCKET querysock = INVALID_SOCKET; static SOCKET hbsock = INVALID_SOCKET; /******** PROTOTYPES ********/ static void send_heartbeat(int statechanged); static void parse_query(char *query, struct sockaddr *sender); static int get_sockaddrin(char *host, int port, struct sockaddr_in *saddr, struct hostent **savehent); static int do_connect(SOCKET sock, char *addr, int port); /****************************************************************************/ /* PUBLIC FUNCTIONS */ /****************************************************************************/ /* goa_init: Initializes the sockets, etc. Returns an error value if an error occured, or 0 otherwise */ int goa_init(int queryport, char *gamename, char *ip, void *userdata) { int lasterror; struct sockaddr_in saddr; int saddrlen; //save off query port,gamename and userdata qport = queryport; strncpy(gname, gamename, 23); udata = userdata; //create our sockets SocketStartUp(); do { querysock = socket(PF_INET, SOCK_DGRAM, 0); #ifdef TCP_HEARTBEATS hbsock = socket(PF_INET, SOCK_STREAM, 0); #else hbsock = socket(PF_INET, SOCK_DGRAM, 0); #endif if (INVALID_SOCKET == querysock || INVALID_SOCKET == hbsock) { return E_GOA_WSOCKERROR; } get_sockaddrin(ip,qport,&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; //shutdown(querysock, SO_BOTH); //shutdown(hbsock, SO_BOTH); closesocket(querysock); closesocket(hbsock); qport++; } while (qport < (queryport + MAX_SERVERS_PER_MACHINE)); if (lasterror) return E_GOA_BINDERROR; if (qport == 0) //we need to allocate it ourselves { saddrlen = sizeof(saddr); lasterror = getsockname(querysock,(struct sockaddr *)&saddr, &saddrlen); if (lasterror) return E_GOA_BINDERROR; qport = ntohs(saddr.sin_port); } int nGameSpyPort; GameSpyOld::GetGameSpyAddress(GameSpyOld::szGameSpyAddress, 256); nGameSpyPort = GameSpyOld::GetHeartbeatPort(); return do_connect(hbsock, GameSpyOld::szGameSpyAddress, nGameSpyPort); } /* goa_process_queries: Processes any waiting queries, and sends a heartbeat if 5 minutes have elapsed */ void goa_process_queries(void) { goa_check_send_heartbeat(); goa_check_queries(); } /* check_queries: Processes any waiting queries */ void goa_check_queries(void) { static char indata[INBUF_LEN]; //256 byte input buffer struct sockaddr saddr; int saddrlen = sizeof(struct sockaddr); fd_set set; struct timeval timeout = {0,0}; int error; if (INVALID_SOCKET == querysock) return; //no sockets to work with! FD_ZERO ( &set ); FD_SET ( querysock, &set ); while (true) { error = select(FD_SETSIZE, &set, NULL, NULL, &timeout); if (SOCKET_ERROR == error || 0 == error) return; //else we have data error = recvfrom(querysock, indata, INBUF_LEN - 1, 0, &saddr, &saddrlen); if (error != SOCKET_ERROR) { indata[error] = '\0'; parse_query(indata, &saddr); } } } /* check_send_heartbeat: Perform any scheduled outgoing heartbeats, (every 5 minutes) */ void goa_check_send_heartbeat(void) { unsigned int tc = current_time(); if (INVALID_SOCKET == hbsock) return; //no sockets to work with! //check if we need to send a heartbet if (tc - lastheartbeat > HB_TIME) send_heartbeat(0); } /* goa_send_statechanged: Sends a statechanged heartbeat, call when your gamemode changes */ void goa_send_statechanged(void) { send_heartbeat(1); } /* goa_shutdown: Cleans up the sockets and shuts down */ void goa_shutdown(void) { if (INVALID_SOCKET != querysock) { closesocket(querysock); querysock = INVALID_SOCKET; } if (INVALID_SOCKET != hbsock) { closesocket(hbsock); hbsock = INVALID_SOCKET; } lastheartbeat = 0; SocketShutDown(); } /****************************************************************************/ static int do_connect(SOCKET sock, char *addr, int port) { struct sockaddr_in saddr; if (!get_sockaddrin(addr, port,&saddr,NULL)) return E_GOA_CONNERROR; if (connect (sock, (struct sockaddr *) &saddr, sizeof(saddr)) == SOCKET_ERROR) { return E_GOA_CONNERROR; } 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(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 = *(u_long *)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 (uchar)('A'+c); if (c < 52) return (uchar)('a'+c-26); if (c < 62) return (uchar)('0'+c-52); if (c == 62) return (uchar)('+'); if (c == 63) return (uchar)('/'); 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] = (uchar)( (trip[0]) >> 2); kwart[1] = (uchar)((((trip[0]) & 3) << 4) + ((trip[1]) >> 4)); kwart[2] = (uchar)((((trip[1]) & 15) << 2) + ((trip[2]) >> 6)); kwart[3] = (uchar)( (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 = (uchar)((key[x] + state[counter] + y) % 256); x = (uchar)((x + 1) % key_len); swap_byte ( &state[counter], &state[y] ); } x = 0; y = 0; for ( counter = 0; counter < buffer_len; counter ++) { x = (uchar)((x + buffer_ptr[counter] + 1) % 256); y = (uchar)((state[x] + y) % 256); swap_byte ( &state[x], &state[y] ); xorIndex = (uchar)((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(struct sockaddr *addr, char *buffer) { char keyvalue[80]; if (strlen(buffer) == 0) return; //dont need to send an empty one! packetnumber++; //packet numbers start at 1 sprintf(keyvalue,"\\queryid\\%d.%d",queryid, packetnumber); strcat(buffer,keyvalue); #ifdef _DEBUG OutputDebugString("\n"); OutputDebugString(buffer); OutputDebugString("\n"); #endif sendto(querysock, buffer, strlen(buffer), 0, addr, sizeof(struct sockaddr)); 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(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 {//test this! 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(sender, buffer, newdata); *lastkey = '\\'; buffer_send(sender, buffer, lastkey); //send the rest! } else { packet_send(sender, buffer); strcpy(buffer,newdata); } } } /* send_basic: sends a response to the basic query */ static void send_basic(struct sockaddr *sender, char *outbuf) { char keyvalue[BUF_SIZE] = ""; assert(goa_basic_callback); goa_basic_callback(keyvalue, sizeof(keyvalue), udata); buffer_send(sender, outbuf, keyvalue); } /* send_info: sends a response to the info query */ static void send_info(struct sockaddr *sender, char *outbuf) { char keyvalue[BUF_SIZE] = ""; assert(goa_info_callback); goa_info_callback(keyvalue, sizeof(keyvalue), udata); buffer_send(sender, outbuf, keyvalue); } /* send_rules: sends a response to the rules query. */ static void send_rules(struct sockaddr *sender, char *outbuf) { char keyvalue[BUF_SIZE] = ""; assert(goa_rules_callback); goa_rules_callback(keyvalue, sizeof(keyvalue), udata); buffer_send(sender, outbuf, keyvalue); } /* send_players: sends the players and their information.*/ static void send_players(struct sockaddr *sender, char *outbuf) { char keyvalue[BUF_SIZE] = ""; assert(goa_players_callback); goa_players_callback(keyvalue, sizeof(keyvalue), udata); buffer_send(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(struct sockaddr *sender, char *outbuf,char *echostr) { char keyvalue[MAX_DATA_SIZE]; if (strlen(echostr) > MAX_DATA_SIZE - 50) return; sprintf(keyvalue,"\\echo\\%s",echostr); buffer_send(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(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 *)goa_secret_key, strlen(goa_secret_key), (uchar *)encrypted_val, keylen); gs_encode((uchar *)encrypted_val,keylen, (uchar *)encoded_val); sprintf(keyvalue,"\\validate\\%s",encoded_val); buffer_send(sender, outbuf, keyvalue); } sprintf(keyvalue,"\\final\\"); buffer_send(sender, outbuf, keyvalue); packet_send(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(char *query, struct sockaddr *sender) { query_t querytype; char buffer[MAX_DATA_SIZE]=""; char *value; char validation[256] = ""; queryid++; packetnumber = 0; for (querytype = qtbasic; querytype <= qtsecure ; (*(int *)(&querytype))++) { if ((value = value_for_key(query, queries[querytype])) != 0) switch (querytype) { case qtbasic: send_basic(sender,buffer); break; case qtinfo: send_info(sender,buffer); break; case qtrules: send_rules(sender,buffer); break; case qtplayers: send_players(sender,buffer); break; case qtstatus: send_basic(sender,buffer); send_info(sender,buffer); send_rules(sender,buffer); send_players(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(sender,buffer); packet_send(sender, buffer); send_info(sender,buffer); packet_send(sender, buffer); send_rules(sender,buffer); packet_send(sender, buffer); send_players(sender,buffer); break; case qtecho: //note: \echo\value is the syntax here send_echo(sender,buffer,value); break; case qtsecure: strcpy(validation, value); break; case qtunknown: break; } } send_final(sender,buffer,validation); } /* send_heartbeat: Sends a heartbeat to the gamemaster, adds \statechanged\ if statechanged != 0 */ static void send_heartbeat(int statechanged) { char buf[256]; int ret; sprintf(buf,"\\heartbeat\\%d\\gamename\\%s",qport, gname); if (statechanged) strcat(buf,"\\statechanged\\"); #ifdef _DEBUG OutputDebugString("send: "); OutputDebugString(buf); OutputDebugString("\n"); #endif ret = send(hbsock, buf, strlen(buf), 0); if (ret < 0) /* try to reconnect (tcp only) */ { closesocket(hbsock); hbsock = socket(PF_INET, SOCK_STREAM, 0); int nGameSpyPort; GameSpyOld::GetGameSpyAddress(GameSpyOld::szGameSpyAddress, 256); nGameSpyPort = GameSpyOld::GetHeartbeatPort(); if (do_connect(hbsock, GameSpyOld::szGameSpyAddress, nGameSpyPort) == 0) { #ifdef _DEBUG OutputDebugString("send: "); OutputDebugString(buf); OutputDebugString("\n"); #endif send(hbsock, buf, strlen(buf), 0); /* try again */ } } lastheartbeat = current_time(); } #ifdef __cplusplus } #endif #endif