mirror of
https://github.com/ptitSeb/Serious-Engine
synced 2024-11-29 21:25:54 +01:00
528 lines
14 KiB
C++
528 lines
14 KiB
C++
/* Copyright (c) 2002-2012 Croteam Ltd. All rights reserved. */
|
|
|
|
#include <Engine/StdH.h>
|
|
|
|
#include <Engine/Engine.h>
|
|
#include <Engine/CurrentVersion.h>
|
|
|
|
#include <Engine/Entities/Entity.h>
|
|
|
|
#include <Engine/Base/Shell.h>
|
|
#include <Engine/Base/Console.h>
|
|
#include <Engine/Base/CTString.h>
|
|
|
|
#include <Engine/Network/Server.h>
|
|
#include <Engine/Network/Network.h>
|
|
#include <Engine/Network/SessionState.h>
|
|
|
|
#include <GameMP/SessionProperties.h>
|
|
|
|
#include <Engine/GameAgent/GameAgent.h>
|
|
|
|
#ifdef PLATFORM_UNIX
|
|
#include <fcntl.h>
|
|
#include <netdb.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <errno.h>
|
|
#define INVALID_SOCKET -1
|
|
#define SOCKET_ERROR -1
|
|
#define closesocket close
|
|
typedef int SOCKET;
|
|
typedef struct hostent HOSTENT;
|
|
typedef struct sockaddr_in SOCKADDR_IN;
|
|
typedef struct sockaddr SOCKADDR;
|
|
#define WSAGetLastError() (INDEX) errno
|
|
#endif
|
|
|
|
#ifdef PLATFORM_WIN32
|
|
#pragma comment(lib, "wsock32.lib")
|
|
WSADATA* _wsaData = NULL;
|
|
typedef int socklen_t;
|
|
#endif
|
|
|
|
SOCKET _socket = INVALID_SOCKET;
|
|
|
|
sockaddr_in* _sin = NULL;
|
|
sockaddr_in* _sinLocal = NULL;
|
|
sockaddr_in _sinFrom;
|
|
|
|
CHAR* _szBuffer = NULL;
|
|
|
|
BOOL _bServer = FALSE;
|
|
static BOOL _bInitialized = FALSE;
|
|
|
|
TIME _tmLastHeartbeat = 0;
|
|
|
|
CDynamicStackArray<CServerRequest> ga_asrRequests;
|
|
|
|
CTString ga_strServer = "master1.croteam.org";
|
|
|
|
void _uninitWinsock();
|
|
void _initializeWinsock(void)
|
|
{
|
|
#ifdef PLATFORM_WIN32
|
|
if(_wsaData != NULL && _socket != INVALID_SOCKET) {
|
|
return;
|
|
}
|
|
|
|
_wsaData = new WSADATA;
|
|
_socket = INVALID_SOCKET;
|
|
|
|
// make the buffer that we'll use for packet reading
|
|
if(_szBuffer != NULL) {
|
|
delete[] _szBuffer;
|
|
}
|
|
_szBuffer = new char[1400];
|
|
|
|
// start WSA
|
|
if(WSAStartup(MAKEWORD(2, 2), _wsaData) != 0) {
|
|
CPrintF("Error initializing winsock!\n");
|
|
_uninitWinsock();
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
// get the host IP
|
|
hostent* phe = gethostbyname(ga_strServer);
|
|
|
|
// if we couldn't resolve the hostname
|
|
if(phe == NULL) {
|
|
// report and stop
|
|
CPrintF("Couldn't resolve GameAgent server %s.\n", (const char *) ga_strServer);
|
|
_uninitWinsock();
|
|
return;
|
|
}
|
|
|
|
// create the socket destination address
|
|
_sin = new sockaddr_in;
|
|
_sin->sin_family = AF_INET;
|
|
_sin->sin_addr.s_addr = *(ULONG*)phe->h_addr_list[0];
|
|
_sin->sin_port = htons(9005);
|
|
|
|
// create the socket
|
|
_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (_socket == INVALID_SOCKET) {
|
|
CPrintF("Error creating GameAgent socket!\n");
|
|
_uninitWinsock();
|
|
return;
|
|
}
|
|
|
|
// if we're a server
|
|
if(_bServer) {
|
|
// create the local socket source address
|
|
_sinLocal = new sockaddr_in;
|
|
_sinLocal->sin_family = AF_INET;
|
|
_sinLocal->sin_addr.s_addr = inet_addr("0.0.0.0");
|
|
_sinLocal->sin_port = htons(_pShell->GetINDEX("net_iPort") + 1);
|
|
|
|
// bind the socket
|
|
bind(_socket, (sockaddr*)_sinLocal, sizeof(*_sinLocal));
|
|
}
|
|
|
|
// set the socket to be nonblocking
|
|
#ifdef PLATFORM_WIN32
|
|
DWORD dwNonBlocking = 1;
|
|
if(ioctlsocket(_socket, FIONBIO, &dwNonBlocking) != 0) {
|
|
CPrintF("Error setting socket to nonblocking!\n");
|
|
_uninitWinsock();
|
|
return;
|
|
}
|
|
#else
|
|
int flags = fcntl(_socket, F_GETFL);
|
|
int failed = flags;
|
|
if (failed != -1) {
|
|
flags |= O_NONBLOCK;
|
|
failed = fcntl(_socket, F_SETFL, flags);
|
|
}
|
|
|
|
if (failed == -1) {
|
|
CPrintF("Error setting socket to nonblocking!\n");
|
|
_uninitWinsock();
|
|
return;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void _uninitWinsock()
|
|
{
|
|
if (_socket != INVALID_SOCKET) {
|
|
closesocket(_socket);
|
|
_socket = INVALID_SOCKET;
|
|
}
|
|
#ifdef PLATFORM_WIN32
|
|
if(_wsaData != NULL) {
|
|
delete _wsaData;
|
|
_wsaData = NULL;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void _sendPacketTo(const char* pubBuffer, INDEX iLen, sockaddr_in* sin)
|
|
{
|
|
sendto(_socket, pubBuffer, iLen, 0, (sockaddr*)sin, sizeof(sockaddr_in));
|
|
}
|
|
void _sendPacketTo(const char* szBuffer, sockaddr_in* addsin)
|
|
{
|
|
sendto(_socket, szBuffer, strlen(szBuffer), 0, (sockaddr*)addsin, sizeof(sockaddr_in));
|
|
}
|
|
|
|
void _sendPacket(const char* pubBuffer, INDEX iLen)
|
|
{
|
|
_initializeWinsock();
|
|
_sendPacketTo(pubBuffer, iLen, _sin);
|
|
}
|
|
void _sendPacket(const char* szBuffer)
|
|
{
|
|
_initializeWinsock();
|
|
_sendPacketTo(szBuffer, _sin);
|
|
}
|
|
|
|
int _recvPacket()
|
|
{
|
|
socklen_t fromLength = sizeof(_sinFrom);
|
|
return recvfrom(_socket, _szBuffer, 1024, 0, (sockaddr*)&_sinFrom, &fromLength);
|
|
}
|
|
|
|
CTString _getGameModeName(INDEX iGameMode)
|
|
{
|
|
// get function that will provide us the info about gametype
|
|
CShellSymbol *pss = _pShell->GetSymbol("GetGameTypeNameSS", /*bDeclaredOnly=*/ TRUE);
|
|
|
|
if(pss == NULL) {
|
|
return "";
|
|
}
|
|
|
|
CTString (*pFunc)(INDEX) = (CTString (*)(INDEX))pss->ss_pvValue;
|
|
return pFunc(iGameMode);
|
|
}
|
|
|
|
const CSessionProperties* _getSP()
|
|
{
|
|
return ((const CSessionProperties *)_pNetwork->GetSessionProperties());
|
|
}
|
|
|
|
void _sendHeartbeat(INDEX iChallenge)
|
|
{
|
|
CTString strPacket;
|
|
strPacket.PrintF("0;challenge;%d;players;%d;maxplayers;%d;level;%s;gametype;%s;version;%s;product;%s",
|
|
iChallenge,
|
|
_pNetwork->ga_srvServer.GetPlayersCount(),
|
|
_pNetwork->ga_sesSessionState.ses_ctMaxPlayers,
|
|
(const char *) _pNetwork->ga_World.wo_strName,
|
|
(const char *) _getGameModeName(_getSP()->sp_gmGameMode),
|
|
_SE_VER_STRING,
|
|
(const char *) _pShell->GetString("sam_strGameName"));
|
|
_sendPacket(strPacket);
|
|
_tmLastHeartbeat = _pTimer->GetRealTimeTick();
|
|
}
|
|
|
|
static void _setStatus(const CTString &strStatus)
|
|
{
|
|
_pNetwork->ga_bEnumerationChange = TRUE;
|
|
_pNetwork->ga_strEnumerationStatus = strStatus;
|
|
}
|
|
|
|
CServerRequest::CServerRequest(void)
|
|
{
|
|
Clear();
|
|
}
|
|
CServerRequest::~CServerRequest(void) { }
|
|
void CServerRequest::Clear(void)
|
|
{
|
|
sr_ulAddress = 0;
|
|
sr_iPort = 0;
|
|
sr_tmRequestTime = 0;
|
|
}
|
|
|
|
/// Initialize GameAgent.
|
|
extern void GameAgent_ServerInit(void)
|
|
{
|
|
// join
|
|
_bServer = TRUE;
|
|
_bInitialized = TRUE;
|
|
_sendPacket("q");
|
|
}
|
|
|
|
/// Let GameAgent know that the server has stopped.
|
|
extern void GameAgent_ServerEnd(void)
|
|
{
|
|
if (!_bInitialized) {
|
|
return;
|
|
}
|
|
_uninitWinsock();
|
|
_bInitialized = FALSE;
|
|
}
|
|
|
|
/// GameAgent server update call which responds to enumeration pings and sends pings to masterserver.
|
|
extern void GameAgent_ServerUpdate(void)
|
|
{
|
|
if((_socket == INVALID_SOCKET) || (!_bInitialized)) {
|
|
return;
|
|
}
|
|
|
|
int iLen = _recvPacket();
|
|
if(iLen > 0) {
|
|
// check the received packet ID
|
|
switch(_szBuffer[0]) {
|
|
case 1: // server join response
|
|
{
|
|
int iChallenge = *(INDEX*)(_szBuffer + 1);
|
|
// send the challenge
|
|
_sendHeartbeat(iChallenge);
|
|
break;
|
|
}
|
|
|
|
case 2: // server status request
|
|
{
|
|
// send the status response
|
|
CTString strPacket;
|
|
strPacket.PrintF("0;players;%d;maxplayers;%d;level;%s;gametype;%s;version;%s;gamename;%s;sessionname;%s",
|
|
_pNetwork->ga_srvServer.GetPlayersCount(),
|
|
_pNetwork->ga_sesSessionState.ses_ctMaxPlayers,
|
|
(const char *) _pNetwork->ga_World.wo_strName,
|
|
(const char *) _getGameModeName(_getSP()->sp_gmGameMode),
|
|
_SE_VER_STRING,
|
|
(const char *) _pShell->GetString("sam_strGameName"),
|
|
(const char *) _pShell->GetString("gam_strSessionName"));
|
|
_sendPacketTo(strPacket, &_sinFrom);
|
|
break;
|
|
}
|
|
|
|
case 3: // player status request
|
|
{
|
|
// send the player status response
|
|
CTString strPacket;
|
|
strPacket.PrintF("\x01players\x02%d\x03", _pNetwork->ga_srvServer.GetPlayersCount());
|
|
for(INDEX i=0; i<_pNetwork->ga_srvServer.GetPlayersCount(); i++) {
|
|
CPlayerBuffer &plb = _pNetwork->ga_srvServer.srv_aplbPlayers[i];
|
|
CPlayerTarget &plt = _pNetwork->ga_sesSessionState.ses_apltPlayers[i];
|
|
if(plt.plt_bActive) {
|
|
CTString strPlayer;
|
|
plt.plt_penPlayerEntity->GetGameAgentPlayerInfo(plb.plb_Index, strPlayer);
|
|
|
|
// if we don't have enough space left for the next player
|
|
if(strlen(strPacket) + strlen(strPlayer) > 1024) {
|
|
// send the packet
|
|
_sendPacketTo(strPacket, &_sinFrom);
|
|
strPacket = "";
|
|
}
|
|
|
|
strPacket += strPlayer;
|
|
}
|
|
}
|
|
|
|
strPacket += "\x04";
|
|
_sendPacketTo(strPacket, &_sinFrom);
|
|
break;
|
|
}
|
|
|
|
case 4: // ping
|
|
{
|
|
// just send back 1 byte and the amount of players in the server (this could be useful in some cases for external scripts)
|
|
CTString strPacket;
|
|
strPacket.PrintF("\x04%d", _pNetwork->ga_srvServer.GetPlayersCount());
|
|
_sendPacketTo(strPacket, &_sinFrom);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// send a heartbeat every 150 seconds
|
|
if(_pTimer->GetRealTimeTick() - _tmLastHeartbeat >= 150.0f) {
|
|
_sendHeartbeat(0);
|
|
}
|
|
}
|
|
|
|
/// Notify GameAgent that the server state has changed.
|
|
extern void GameAgent_ServerStateChanged(void)
|
|
{
|
|
if (_bInitialized) {
|
|
_sendPacket("u");
|
|
}
|
|
}
|
|
|
|
/// Request serverlist enumeration.
|
|
extern void GameAgent_EnumTrigger(BOOL bInternet)
|
|
{
|
|
if (!_bInitialized) {
|
|
return;
|
|
}
|
|
// make sure that there are no requests still stuck in buffer
|
|
ga_asrRequests.Clear();
|
|
|
|
// we're not a server
|
|
_bServer = FALSE;
|
|
|
|
// send enumeration packet to masterserver
|
|
_sendPacket("e");
|
|
_setStatus("");
|
|
}
|
|
|
|
/// GameAgent client update for enumerations.
|
|
extern void GameAgent_EnumUpdate(void)
|
|
{
|
|
if((_socket == INVALID_SOCKET) || (!_bInitialized)) {
|
|
return;
|
|
}
|
|
|
|
int iLen = _recvPacket();
|
|
if(iLen != -1) {
|
|
// null terminate the buffer
|
|
_szBuffer[iLen] = 0;
|
|
|
|
switch(_szBuffer[0]) {
|
|
case 's':
|
|
{
|
|
// !!! FIXME: serialize this and byteswap it. --ryan.
|
|
#pragma pack(1)
|
|
struct sIPPort {
|
|
UBYTE bFirst;
|
|
UBYTE bSecond;
|
|
UBYTE bThird;
|
|
UBYTE bFourth;
|
|
USHORT iPort;
|
|
};
|
|
#pragma pack()
|
|
|
|
sIPPort* pServers = (sIPPort*)(_szBuffer + 1);
|
|
while(iLen - ((CHAR*)pServers - _szBuffer) >= sizeof(sIPPort)) {
|
|
sIPPort ip = *pServers;
|
|
|
|
CTString strIP;
|
|
strIP.PrintF("%d.%d.%d.%d", ip.bFirst, ip.bSecond, ip.bThird, ip.bFourth);
|
|
|
|
sockaddr_in sinServer;
|
|
sinServer.sin_family = AF_INET;
|
|
sinServer.sin_addr.s_addr = inet_addr(strIP);
|
|
sinServer.sin_port = htons(ip.iPort + 1);
|
|
|
|
// insert server status request into container
|
|
CServerRequest &sreq = ga_asrRequests.Push();
|
|
sreq.sr_ulAddress = sinServer.sin_addr.s_addr;
|
|
sreq.sr_iPort = sinServer.sin_port;
|
|
sreq.sr_tmRequestTime = _pTimer->GetHighPrecisionTimer().GetMilliseconds();
|
|
|
|
// send packet to server
|
|
_sendPacketTo("\x02", &sinServer);
|
|
|
|
pServers++;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case '0':
|
|
{
|
|
CTString strPlayers;
|
|
CTString strMaxPlayers;
|
|
CTString strLevel;
|
|
CTString strGameType;
|
|
CTString strVersion;
|
|
CTString strGameName;
|
|
CTString strSessionName;
|
|
|
|
CHAR* pszPacket = _szBuffer + 2; // we do +2 because the first character is always ';', which we don't care about.
|
|
|
|
BOOL bReadValue = FALSE;
|
|
CTString strKey;
|
|
CTString strValue;
|
|
|
|
while(*pszPacket != 0) {
|
|
switch(*pszPacket) {
|
|
case ';':
|
|
if(strKey != "sessionname") {
|
|
if(bReadValue) {
|
|
// we're done reading the value, check which key it was
|
|
if(strKey == "players") {
|
|
strPlayers = strValue;
|
|
} else if(strKey == "maxplayers") {
|
|
strMaxPlayers = strValue;
|
|
} else if(strKey == "level") {
|
|
strLevel = strValue;
|
|
} else if(strKey == "gametype") {
|
|
strGameType = strValue;
|
|
} else if(strKey == "version") {
|
|
strVersion = strValue;
|
|
} else if(strKey == "gamename") {
|
|
strGameName = strValue;
|
|
} else {
|
|
CPrintF("Unknown GameAgent parameter key '%s'!", (const char *) strKey);
|
|
}
|
|
|
|
// reset temporary holders
|
|
strKey = "";
|
|
strValue = "";
|
|
}
|
|
}
|
|
bReadValue = !bReadValue;
|
|
break;
|
|
|
|
default:
|
|
// read into the value or into the key, depending where we are
|
|
if(bReadValue) {
|
|
strValue.InsertChar(strlen(strValue), *pszPacket);
|
|
} else {
|
|
strKey.InsertChar(strlen(strKey), *pszPacket);
|
|
}
|
|
break;
|
|
}
|
|
|
|
// move to next character
|
|
pszPacket++;
|
|
}
|
|
|
|
// check if we still have a sessionname to back up
|
|
if(strKey == "sessionname") {
|
|
strSessionName = strValue;
|
|
}
|
|
|
|
// insert the server into the serverlist
|
|
CNetworkSession &ns = *new CNetworkSession;
|
|
_pNetwork->ga_lhEnumeratedSessions.AddTail(ns.ns_lnNode);
|
|
|
|
long long tmPing = -1;
|
|
// find the request in the request array
|
|
for(INDEX i=0; i<ga_asrRequests.Count(); i++) {
|
|
CServerRequest &req = ga_asrRequests[i];
|
|
if(req.sr_ulAddress == _sinFrom.sin_addr.s_addr && req.sr_iPort == _sinFrom.sin_port) {
|
|
tmPing = _pTimer->GetHighPrecisionTimer().GetMilliseconds() - req.sr_tmRequestTime;
|
|
ga_asrRequests.Delete(&req);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(tmPing == -1) {
|
|
// server status was never requested
|
|
break;
|
|
}
|
|
|
|
// add the server to the serverlist
|
|
ns.ns_strSession = strSessionName;
|
|
ns.ns_strAddress = inet_ntoa(_sinFrom.sin_addr) + CTString(":") + CTString(0, "%d", htons(_sinFrom.sin_port) - 1);
|
|
ns.ns_tmPing = (tmPing / 1000.0f);
|
|
ns.ns_strWorld = strLevel;
|
|
ns.ns_ctPlayers = atoi(strPlayers);
|
|
ns.ns_ctMaxPlayers = atoi(strMaxPlayers);
|
|
ns.ns_strGameType = strGameType;
|
|
ns.ns_strMod = strGameName;
|
|
ns.ns_strVer = strVersion;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
CPrintF("Unknown enum packet ID %x!\n", _szBuffer[0]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Cancel the GameAgent serverlist enumeration.
|
|
extern void GameAgent_EnumCancel(void)
|
|
{
|
|
if (_bInitialized) {
|
|
ga_asrRequests.Clear();
|
|
_uninitWinsock();
|
|
}
|
|
}
|