Descent3/mac/MACGAMESPY.CPP
2024-04-16 12:56:40 -06:00

535 lines
16 KiB
C++

/*
* $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 <stdlib.h>
#include <memory.h>
#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;
}