/* * $Logfile: /DescentIII/Main/mac/MACGAMESPY.CPP $ * $Revision: 1.1.1.1 $ * $Date: 2003/08/26 03:58:15 $ * $Author: kevinb $ * * Gamespy client code * This library knows about the game and is responsible for telling the * gamespy client and server about everything it wants to know * * * $Log: MACGAMESPY.CPP,v $ * Revision 1.1.1.1 2003/08/26 03:58:15 kevinb * initial 1.5 import * * * 2 10/21/99 1:55p Kevin * Mac Merge! * * 1 7/28/99 2:31p Kevin * Mac only stuff * * * * $NoKeywords: $ */ #include "pstypes.h" #include "pserror.h" #include "player.h" #include "multi.h" #include "networking.h" #include "descent.h" #include "ddio.h" #include "args.h" #include "CFILE.H" #include "program.h" #include #include #include "gamespyutils.h" #include "gamespy.h" #ifdef FIXED extern short Multi_kills[MAX_NET_PLAYERS]; extern short Multi_deaths[MAX_NET_PLAYERS]; // Secret code... encrypted using some really high tech method... char gspy_d3_secret[10]; // = "feWh2G"; const char origstring[] = {(const char)0x50, (const char)0xf8, (const char)0xa4, (const char)0xba, (const char)0xc7, (const char)0x7c}; char gspy_cfgfilename[_MAX_PATH]; #define MAX_GAMESPY_SERVERS 5 #define MAX_GAMESPY_BUFFER 1400 #define MAX_HOSTNAMELEN 300 #define GSPY_HEARBEAT_INTERVAL 300 // Seconds between heartbeats. #define GAMESPY_PORT 27900 #define GAMESPY_LISTENPORT 20142 #define THISGAMENAME "descent3" #ifdef DEMO #define THISGAMEVER "Demo2" #elif defined(OEM) #define THISGAMEVER "OEM" #else #define THISGAMEVER "Retail" #endif SOCKET gspy_socket; SOCKADDR_IN gspy_server[MAX_GAMESPY_SERVERS]; extern ushort Gameport; int gspy_region = 0; char gspy_outgoingbuffer[MAX_GAMESPY_BUFFER] = ""; float gspy_last_heartbeat; bool gspy_game_running = false; int gspy_packetnumber = 0; int gspy_queryid = 0; char gspy_validate[MAX_GAMESPY_BUFFER] = ""; unsigned short gspy_listenport; #endif // FIXED // Register a game with this library so we will tell the servers about it... void gspy_StartGame(char *name) { #ifdef FIXED gspy_last_heartbeat = timer_GetTime() - GSPY_HEARBEAT_INTERVAL; gspy_game_running = true; #endif // FIXED } // Let the servers know that the game is over void gspy_EndGame() { #ifdef FIXED gspy_game_running = false; #endif // FIXED } // Initialize gamespy with the info we need to talk to the servers int gspy_Init(void) { #ifdef FIXED #ifndef OEM char cfgpath[_MAX_PATH * 2]; int argnum = FindArg("-gspyfile"); if (argnum) { strcpy(gspy_cfgfilename, GameArgs[argnum + 1]); } else { strcpy(gspy_cfgfilename, "gamespy.cfg"); } for (int a = 0; a < MAX_GAMESPY_SERVERS; a++) { // gspy_server[a].sin_addr.S_un.S_addr = INADDR_NONE; INADDR_SET_SUN_SADDR(&gspy_server[a].sin_addr, INADDR_NONE); gspy_server[a].sin_port = htons(GAMESPY_PORT); gspy_server[a].sin_family = AF_INET; } unsigned char keychars[] = {0x36, 0x9d, 0xf3, 0xd2, 0xf5, 0x3b, 0x42, 0xcc, 0x58}; for (int i = 0; i < 6; i++) { gspy_d3_secret[i] = (char)(origstring[i] ^ keychars[i]); } gspy_d3_secret[6] = NULL; gspy_d3_secret[7] = NULL; // strcpy(gspy_d3_secret,"feWh2G\0\0"); // Read the config, resolve the name if needed and setup the server addresses ddio_MakePath(cfgpath, Base_directory, gspy_cfgfilename, NULL); gspy_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); ; if (SOCKET_ERROR == gspy_socket) { int lerror = WSAGetLastError(); mprintf((0, "Unable to init gamespy socket! (%d)\n", lerror)); return 0; } SOCKADDR_IN sock_addr; memset(&sock_addr, 0, sizeof(SOCKADDR_IN)); sock_addr.sin_family = AF_INET; unsigned int my_ip; my_ip = nw_GetThisIP(); memcpy(&sock_addr.sin_addr.s_addr, &my_ip, sizeof(uint)); int portarg = FindArg("-gamespyport"); if (portarg) { gspy_listenport = htons(atoi(GameArgs[portarg + 1])); } else { gspy_listenport = htons(GAMESPY_LISTENPORT); } mprintf((0, "Using port %d for gamespy requests.\n", GAMESPY_LISTENPORT)); sock_addr.sin_port = gspy_listenport; if (bind(gspy_socket, (SOCKADDR *)&sock_addr, sizeof(sock_addr)) == SOCKET_ERROR) { mprintf((0, "Couldn't bind gamespy socket (%d)!\n", WSAGetLastError())); return 0; } int error; unsigned long arg; arg = TRUE; // make the socket non blocking #ifdef WIN32 error = ioctlsocket(gspy_socket, FIONBIO, &arg); #elif defined(__LINUX__) error = ioctl(gspy_socket, FIONBIO, &arg); #endif CFILE *cfp = cfopen(cfgpath, "rt"); if (cfp) { mprintf((0, "Found a gamespy config file!\n")); char hostn[MAX_HOSTNAMELEN]; for (int i = 0; i < MAX_GAMESPY_SERVERS; i++) { // First in the config file is the region, which is a number from 0-12 (currently) if (cf_ReadString(hostn, MAX_HOSTNAMELEN - 1, cfp)) { gspy_region = atoi(hostn); } // next in the config file are the servers // Each gamespy server should appear in the file with the hostname:port // Or optionally, just the hostname // Examples: // 192.168.1.100:27900 // master01.gamespy.com:27900 // master02.gamespy.com // 192.168.1.100 if (cf_ReadString(hostn, MAX_HOSTNAMELEN - 1, cfp)) { char *port = strstr(hostn, ":"); if (port) { // terminate the hostname *port = NULL; // Increment to the first character of the port name port++; // get the port number gspy_server[i].sin_port = htons(atoi(port)); } if (INADDR_NONE == inet_addr(hostn)) { // This is a name we must resolve HOSTENT *he; mprintf((0, "Resolving hostname for gamespy: %s\n", hostn)); he = gethostbyname(hostn); if (!he) { mprintf((0, "Unable to resolve %s\n", hostn)); // gspy_server[i].sin_addr.S_un.S_addr = INADDR_NONE; INADDR_SET_SUN_SADDR(&gspy_server[i].sin_addr, INADDR_NONE); } else { // memcpy(&gspy_server[i].sin_addr.S_un.S_addr,he->h_addr_list[0],sizeof(unsigned int)); memcpy(&gspy_server[i].sin_addr, he->h_addr_list[0], sizeof(unsigned int)); } } else { // This is just a number // gspy_server[i].sin_addr.S_un.S_addr = inet_addr(hostn); INADDR_SET_SUN_SADDR(&gspy_server[i].sin_addr, inet_addr(hostn)); // break; } } #if defined(WIN32) if (gspy_server[i].sin_addr.S_un.S_addr != INADDR_NONE) { mprintf((0, "Sending gamespy heartbeats to %s:%d\n", inet_ntoa(gspy_server[i].sin_addr), htons(gspy_server[i].sin_port))); } #elif defined(__LINUX__) if (gspy_server[i].sin_addr.s_addr != INADDR_NONE) { mprintf((0, "Sending gamespy heartbeats to %s:%d\n", inet_ntoa(gspy_server[i].sin_addr), htons(gspy_server[i].sin_port))); } #endif } } #endif #endif // FIXED return 1; } // Takes a gspy response and puts the appropriate validation code to the end // Of the string. If crypt is something besides NULL, create and tack the proper // response to the end #define VALIDATE_SIZE 6 bool gpsy_ValidateString(char *str, char *crypt) { #ifdef FIXED char keyvalue[80]; unsigned char encrypted_val[VALIDATE_SIZE]; // don't need to num terminate unsigned char encoded_val[(VALIDATE_SIZE * 4) / 3 + 1]; if (crypt) { strcpy((char *)encrypted_val, crypt); gspy_encrypt(encrypted_val, VALIDATE_SIZE, (unsigned char *)gspy_d3_secret); gspy_encode(encrypted_val, VALIDATE_SIZE, (unsigned char *)encoded_val); sprintf(keyvalue, "\\validate\\%s", encoded_val); strcat(str, keyvalue); } sprintf(keyvalue, "\\final\\"); strcat(str, keyvalue); #endif // FIXED return false; } // Check the socket for data, and respond properly if needed // Also send heartbeat when needed void gspy_DoFrame() { #ifdef FIXED #ifndef OEM SOCKADDR_IN fromaddr; int bytesin; int fromsize = sizeof(SOCKADDR_IN); char inbuffer[MAX_GAMESPY_BUFFER]; if (!gspy_game_running) return; // If it's time, send the heartbeat if ((timer_GetTime() - gspy_last_heartbeat) > GSPY_HEARBEAT_INTERVAL) { for (int a = 0; a < MAX_GAMESPY_SERVERS; a++) { #if defined(WIN32) if (INADDR_NONE != gspy_server[a].sin_addr.S_un.S_addr) { mprintf( (0, "Sending heartbeat to %s:%d\n", inet_ntoa(gspy_server[a].sin_addr), htons(gspy_server[a].sin_port))); gspy_DoHeartbeat(&gspy_server[a]); } #elif defined(__LINUX__) if (INADDR_NONE != gspy_server[a].sin_addr.s_addr) { mprintf( (0, "Sending heartbeat to %s:%d\n", inet_ntoa(gspy_server[a].sin_addr), htons(gspy_server[a].sin_port))); gspy_DoHeartbeat(&gspy_server[a]); } #endif } gspy_last_heartbeat = timer_GetTime(); } // Look for incoming network data do { bytesin = recvfrom(gspy_socket, inbuffer, MAX_GAMESPY_BUFFER, 0, (SOCKADDR *)&fromaddr, &fromsize); if (bytesin > 0) { *(inbuffer + bytesin) = NULL; mprintf((0, "Got a gamespy request:\n%s\n", inbuffer)); gspy_ParseReq(inbuffer, &fromaddr); } else if (bytesin == SOCKET_ERROR) { int lerror = WSAGetLastError(); if (lerror != WSAEWOULDBLOCK) { mprintf((0, "Warning: recvfrom failed for gamespy! (%d)\n", lerror)); } } } while (bytesin > 0); #endif #endif // FIXED } // Sends the packet out to whoever it is that we are sending to int gspy_SendPacket(SOCKADDR_IN *addr) { #ifdef FIXED gspy_packetnumber++; // packet numbers start at 1 char keyvalue[80]; if (!*gspy_outgoingbuffer) { // It's an empty buffer, so don't send anything!! return 0; } gpsy_ValidateString(gspy_outgoingbuffer, *gspy_validate ? gspy_validate : NULL); sprintf(keyvalue, "\\queryid\\%d.%d", gspy_queryid, gspy_packetnumber); strcat(gspy_outgoingbuffer, keyvalue); mprintf((0, "GSPYOUT:%s\n", gspy_outgoingbuffer)); sendto(gspy_socket, gspy_outgoingbuffer, strlen(gspy_outgoingbuffer) + 1, 0, (SOCKADDR *)addr, sizeof(SOCKADDR_IN)); *gspy_outgoingbuffer = NULL; #endif // FIXED return 0; } // Adds some values\keys to the send buffer and sends the packet if it overflows int gspy_AddToBuffer(SOCKADDR_IN *addr, char *addstr) { #ifdef FIXED if (strlen(gspy_outgoingbuffer) + strlen(addstr) + 50 >= MAX_GAMESPY_BUFFER + 1) { // package up this response and send this packet gspy_SendPacket(addr); } else { strcat(gspy_outgoingbuffer, addstr); } #endif // FIXED return 1; } // Looks for the secure key in the request and returns it if there is. If there isn't, it returns a NULL char *gspy_GetSecure(char *req) { #ifdef FIXED char *tokp; char str[MAX_GAMESPY_BUFFER]; strcpy(str, req); tokp = strtok(str, "\\"); if (tokp) { while (tokp) { if (strcmpi(tokp, "secure") == 0) { tokp = strtok(NULL, "\\"); return tokp; } tokp = strtok(NULL, "\\"); }; return NULL; } else { return NULL; } #endif // FIXED return NULL; } int gspy_ContainsKey(char *buffer, char *key) { #ifdef FIXED char str[MAX_GAMESPY_BUFFER]; char lowkey[MAX_GAMESPY_BUFFER]; strcpy(str, buffer); int len = strlen(str); int i; // If it's an empty string return 0 if (*buffer == '\0') return 0; for (i = 0; i < len; i++) tolower(str[i]); strcpy(lowkey, key); len = strlen(str); for (i = 0; i < len; i++) tolower(lowkey[i]); if (strstr(str, lowkey)) { return 1; } else { return 0; } #endif // FIXED } int gspy_ParseReq(char *buffer, SOCKADDR_IN *addr) { #ifdef FIXED gspy_packetnumber = 0; gspy_queryid++; char *validate = gspy_GetSecure(buffer); if (validate) { strcpy(gspy_validate, validate); } else { *gspy_validate = 0; } if (gspy_ContainsKey(buffer, "basic")) { // Send basic gspy_DoBasic(addr); } if (gspy_ContainsKey(buffer, "info")) { // Send info gspy_DoGameInfo(addr); } if (gspy_ContainsKey(buffer, "rules")) { // Send rules gspy_DoRules(addr); } if (gspy_ContainsKey(buffer, "players")) { // Send players gspy_DoPlayers(addr); } if (gspy_ContainsKey(buffer, "status")) { // Send status gspy_DoStatus(addr); } if (gspy_ContainsKey(buffer, "echo")) { // Send echo gspy_DoEcho(addr, buffer); } gspy_SendPacket(addr); #endif // FIXED return 0; } int gspy_DoEcho(SOCKADDR_IN *addr, char *msg) { #ifdef FIXED char buf[MAX_GAMESPY_BUFFER]; // All this is needed in case an echo packet was embedded with other stuff strcpy(buf, msg); char *p = strstr(buf, "\\echo\\"); if (!p) { mprintf((0, "Couldn't find echo keyword in gamespy query, this is a wacky bug that should never happen!\n")); Int3(); return 0; } // send back the string! gspy_AddToBuffer(addr, p); #endif // FIXED return 0; } int gspy_DoBasic(SOCKADDR_IN *addr) { #ifdef FIXED char buf[MAX_GAMESPY_BUFFER]; sprintf(buf, "\\gamename\\%s", THISGAMENAME); gspy_AddToBuffer(addr, buf); // sprintf(buf,"\\gamever\\%d.%d",Program_version.major,Program_version.minor); sprintf(buf, "\\gamever\\%s %.1d.%.1d.%.1d", THISGAMEVER, Program_version.major, Program_version.minor, Program_version.build); gspy_AddToBuffer(addr, buf); sprintf(buf, "\\location\\%d", gspy_region); gspy_AddToBuffer(addr, buf); #endif // FIXED return 0; } int gspy_DoStatus(SOCKADDR_IN *addr) { #ifdef FIXED gspy_DoBasic(addr); gspy_DoGameInfo(addr); gspy_DoRules(addr); gspy_DoPlayers(addr); #endif // FIXED return 0; } int gspy_DoRules(SOCKADDR_IN *addr) { #ifdef FIXED char buf[MAX_GAMESPY_BUFFER]; sprintf(buf, "\\teamplay\\%d", Num_teams); gspy_AddToBuffer(addr, buf); sprintf(buf, "\\timelimit\\%d", (Netgame.flags & NF_TIMER) ? 0 : Netgame.timelimit); gspy_AddToBuffer(addr, buf); sprintf(buf, "\\fraglimit\\%d", (Netgame.flags & NF_KILLGOAL) ? 0 : Netgame.killgoal); gspy_AddToBuffer(addr, buf); sprintf(buf, "\\cl_pxotrack\\%d", Game_is_master_tracker_game); gspy_AddToBuffer(addr, buf); sprintf(buf, "\\mouselook\\%d", (Netgame.flags & NF_ALLOW_MLOOK) ? 1 : 0); gspy_AddToBuffer(addr, buf); sprintf(buf, "\\permissable\\%d", (Netgame.flags & NF_PERMISSABLE) ? 1 : 0); gspy_AddToBuffer(addr, buf); sprintf(buf, "\\brightships\\%d", (Netgame.flags & NF_BRIGHT_PLAYERS) ? 1 : 0); gspy_AddToBuffer(addr, buf); sprintf(buf, "\\acccollisions\\%d", (Netgame.flags & NF_USE_ACC_WEAP) ? 1 : 0); gspy_AddToBuffer(addr, buf); sprintf(buf, "\\randpowerup\\%d", (Netgame.flags & NF_RANDOMIZE_RESPAWN) ? 1 : 0); gspy_AddToBuffer(addr, buf); #endif // FIXED return 0; } // Send the player list to whoever wants it. int gspy_DoPlayers(SOCKADDR_IN *addr) { #ifdef FIXED char buf[MAX_GAMESPY_BUFFER]; int player_count = 0; for (int i = 0; i < MAX_NET_PLAYERS; i++) { if (NetPlayers[i].flags & NPF_CONNECTED) { sprintf(buf, "\\player_%d\\%s", player_count, Players[i].callsign); gspy_AddToBuffer(addr, buf); sprintf(buf, "\\frags_%d\\%d", player_count, Multi_kills[i]); gspy_AddToBuffer(addr, buf); sprintf(buf, "\\deaths_%d\\%d", player_count, Multi_deaths[i]); gspy_AddToBuffer(addr, buf); sprintf(buf, "\\team_%d\\%d", player_count, Players[i].team); gspy_AddToBuffer(addr, buf); sprintf(buf, "\\ping_%d\\%.0f", player_count, (NetPlayers[i].ping_time * 1000.0)); gspy_AddToBuffer(addr, buf); player_count++; } } #endif // FIXED return 0; } int gspy_DoGameInfo(SOCKADDR_IN *addr) { #ifdef FIXED char buf[MAX_GAMESPY_BUFFER]; int curplayers = 0; for (int i = 0; i < MAX_NET_PLAYERS; i++) { if (NetPlayers[i].flags & NPF_CONNECTED) curplayers++; } sprintf(buf, "\\hostname\\%s", Netgame.name); gspy_AddToBuffer(addr, buf); sprintf(buf, "\\hostport\\%d", Gameport); gspy_AddToBuffer(addr, buf); sprintf(buf, "\\mapname\\%s", Netgame.mission); gspy_AddToBuffer(addr, buf); sprintf(buf, "\\gametype\\%s", Netgame.scriptname); gspy_AddToBuffer(addr, buf); sprintf(buf, "\\numplayers\\%d", curplayers); gspy_AddToBuffer(addr, buf); sprintf(buf, "\\maxplayers\\%d", Netgame.max_players); gspy_AddToBuffer(addr, buf); sprintf(buf, "\\gamemode\\%s", "openplaying"); gspy_AddToBuffer(addr, buf); #endif // FIXED return 0; } int gspy_DoHeartbeat(SOCKADDR_IN *addr) { #ifdef FIXED char buf[MAX_GAMESPY_BUFFER]; sprintf(buf, "\\heartbeat\\%d\\gamename\\%s", htons(gspy_listenport), THISGAMENAME); mprintf((0, "GSPYOUT:%s\n", buf)); sendto(gspy_socket, buf, strlen(buf) + 1, 0, (SOCKADDR *)addr, sizeof(SOCKADDR_IN)); #endif // FIXED return 0; }