og-odamex/master/main.cpp
2026-01-05 01:52:47 +00:00

461 lines
9.3 KiB
C++

// Emacs style mode select -*- C++ -*-
//-----------------------------------------------------------------------------
//
// $Id$
//
// Copyright (C) 2000-2006 by Sergey Makovkin (CSDoom .62)
// Copyright (C) 2006-2026 by The Odamex Team.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// DESCRIPTION:
// Main loop
//
//-----------------------------------------------------------------------------
#if _MSC_VER == 1200
// MSVC6, disable broken warnings about truncated stl lines
#pragma warning(disable:4786)
#include <iostream>
#endif
#include <string>
#include <vector>
#include <list>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <time.h>
#ifdef UNIX
#include <netinet/in.h>
#include <unistd.h>
#endif
#ifdef _WIN32
#include <winsock.h>
#define usleep(n) Sleep(n/1000)
#endif
#include "i_net.h"
using namespace std;
#define MAX_SERVERS 1024
#define MAX_SERVERS_PER_IP 64
#define MAX_SERVER_AGE 5000
#define MAX_UNVERIFIED_SERVER_AGE 1000
#define LOGFILE "master_log.txt"
buf_t message(MAX_UDP_PACKET);
typedef struct server
{
netadr_t addr;
int age;
// from server itself
string hostname;
int players, maxplayers;
string map;
vector<string> pwads;
int gametype, skill, teamplay, ctfmode;
vector<string> playernames;
vector<int> playerfrags;
vector<int> playerpings;
vector<int> playerteams;
unsigned int key_sent;
bool pinged, verified;
server() : age(0), players(0), maxplayers(0), gametype(0), skill(0), teamplay(0), ctfmode(0), key_sent(0), pinged(0), verified(0) { memset(&addr, 0, sizeof(addr)); }
} SServer;
list<SServer> servers;
list<SServer>::iterator ping_itr = servers.begin(); // this iterator must be updated when servers is changed
bool ipReachedLimit(netadr_t addr)
{
list<SServer>::iterator itr;
int verifiedservers = 0;
// count verified servers
for (itr = servers.begin(); itr != servers.end(); ++itr)
if ((*itr).verified && memcmp(&(*itr).addr.ip, &addr.ip, 4) == 0)
verifiedservers++;
return verifiedservers >= MAX_SERVERS_PER_IP;
}
void addServer(netadr_t addr)
{
list<SServer>::iterator itr;
SServer temp;
for (itr = servers.begin(); itr != servers.end(); ++itr)
{
if (memcmp(&(*itr).addr.ip, &addr.ip, 4) == 0)
{
if ((*itr).addr.port == addr.port)
{
(*itr).age = 0;
(*itr).pinged = false;
return;
}
}
}
if (servers.size() < MAX_SERVERS)
{
if(ipReachedLimit(addr))
return;
memcpy(&temp.addr, &addr, sizeof(addr));
temp.age = 0;
servers.push_back(temp);
printf("Added new server: %s, %d total\n", NET_AdrToString(temp.addr), (int)servers.size());
FILE *fp = fopen(LOGFILE, "a");
if(fp)
{
fprintf(fp, "Server registered: %s, %d total\r\n", NET_AdrToString(temp.addr), (int)servers.size());
fclose(fp);
}
else
printf("Failed to write to logfile %s\n", LOGFILE);
return;
}
printf("Failed to add server: %s, no slots left\n", NET_AdrToString(addr));
}
void addServerInfo(netadr_t addr)
{
list<SServer>::iterator itr;
size_t i;
for (itr = servers.begin(); itr != servers.end(); ++itr)
{
if (memcmp(&(*itr).addr.ip, &addr.ip, 4) != 0)
continue;
if ((*itr).addr.port != addr.port)
continue;
SServer &s = *itr;
if(!s.key_sent)
continue;
net_message.ReadLong();
// check key against one we issued
if((unsigned)net_message.ReadLong() != s.key_sent)
continue;
// do not allow too many servers
if(ipReachedLimit((*itr).addr))
continue;
printf("Server info, IP = %s\n", NET_AdrToString(addr));
s.verified = true;
s.age = 0;
s.hostname = net_message.ReadString();
s.players = net_message.ReadByte();
s.maxplayers = net_message.ReadByte();
s.map = net_message.ReadString();
int pwadcount = net_message.ReadByte();
if(pwadcount < 0)
pwadcount = 0;
s.pwads.resize(pwadcount);
for(i = 0; i < s.pwads.size(); i++)
s.pwads[i] = net_message.ReadString();
s.gametype = net_message.ReadByte();
s.skill = net_message.ReadByte();
s.teamplay = net_message.ReadByte();
s.ctfmode = net_message.ReadByte();
byte playercount = net_message.ReadByte();
s.playernames.resize(playercount);
s.playerfrags.resize(playercount);
s.playerpings.resize(playercount);
s.playerteams.resize(playercount);
for(i = 0; i < playercount; i++)
{
s.playernames[i] = net_message.ReadString();
s.playerfrags[i] = net_message.ReadShort();
s.playerpings[i] = net_message.ReadLong();
s.playerteams[i] = net_message.ReadByte();
}
break;
}
return;
}
void ageServers(void)
{
list<SServer>::iterator itr;
itr = servers.begin();
while (itr != servers.end())
{
(*itr).age++;
if((*itr).verified)
{
if ((*itr).age > MAX_SERVER_AGE)
{
printf("Remote server timed out: %s, ", NET_AdrToString((*itr).addr));
if(ping_itr == itr)
++ping_itr;
itr = servers.erase(itr);
printf("%d total\n", (int)servers.size());
}
else
++itr;
}
else
{
if ((*itr).age > MAX_UNVERIFIED_SERVER_AGE)
{
printf("Remote server timed out: %s, ", NET_AdrToString((*itr).addr));
if(ping_itr == itr)
++ping_itr;
itr = servers.erase(itr);
printf("%d total\n", (int)servers.size());
}
else
++itr;
}
}
}
void dumpServersToFile(const char *file = "./latest")
{
static bool file_error = false;
FILE *fp = fopen(file, "w");
if(!fp)
{
if(!file_error)
printf("error opening file %s for writing\n", file);
file_error = true;
return;
}
file_error = false;
list<SServer>::iterator itr;
itr = servers.begin();
fprintf(fp, "\"Name\",\"Map\",\"Players/Max\",\"WADs\",\"Gametype\",\"Address:Port\"\n");
while (itr != servers.end())
{
if(!(*itr).verified)
{
++itr;
continue;
}
string detectgametype = "ERROR";
if((*itr).gametype == 0)
detectgametype = "COOP";
else
detectgametype = "DM";
if((*itr).gametype == 1 && (*itr).teamplay == 1)
detectgametype = "TEAM DM";
if((*itr).ctfmode == 1)
detectgametype = "CTF";
string str_wads;
for(size_t j = 0; j < (*itr).pwads.size(); j++)
{
str_wads += (*itr).pwads[j];
str_wads += " ";
}
if(!str_wads.length())
str_wads = " ";
fprintf(fp, "\"%s\",\"%s\",\"%d/%d\",\"%s\",\"%s\",\"%s\"\n", (*itr).hostname.c_str(), (*itr).map.c_str(), (*itr).players, (*itr).maxplayers, str_wads.c_str(), detectgametype.c_str(), NET_AdrToString((*itr).addr, true));
++itr;
}
fclose(fp);
}
void writeServerData(void)
{
list<SServer>::iterator itr;
size_t num_verified = 0;
// count verified servers
for (itr = servers.begin(); itr != servers.end(); ++itr)
if((*itr).verified)
num_verified++;
message.WriteShort(num_verified);
for (itr = servers.begin(); itr != servers.end(); ++itr)
{
if(!(*itr).verified)
continue;
for (int i = 0; i < 4; ++i)
message.WriteByte((*itr).addr.ip[i]);
message.WriteShort(htons((*itr).addr.port));
}
}
void daemon_init(void)
{
#ifdef UNIX
int pid;
FILE *fpid;
if ((pid = fork()) != 0)
exit(0);
pid = getpid();
fpid = fopen("MasterPID", "w");
if(fpid)
{
fprintf(fpid, "%d\n", pid);
fclose(fpid);
}
#endif
}
void pingServer(SServer &s)
{
if(s.pinged && !s.verified)
{
return; // have already asked and got no answer
}
#ifdef _WIN32
s.key_sent = rand() * (intptr_t)GetModuleHandle(0) * time(0);
#else
s.key_sent = rand() * getpid() * time(0);
#endif
message.clear();
message.WriteLong(LAUNCHER_CHALLENGE);
message.WriteLong(s.key_sent);
NET_SendPacket(message.cursize, message.data, s.addr);
s.pinged = true;
}
int main()
{
int challenge;
localport = MASTERPORT;
InitNetCommon();
daemon_init();
printf("Odamex Master Started\n");
while (true)
{
while (NET_GetPacket())
{
challenge = net_message.ReadLong();
switch (challenge)
{
case 0:
case SERVER_CHALLENGE:
if(net_message.BytesLeftToRead() > 2)
{
// full reply with deathmatch, wad, etc
addServerInfo(net_from);
}
else
{
// plain contact
if(net_message.BytesLeftToRead() == 2)
{
unsigned short use_port = net_message.ReadShort();
net_from.port = htons(use_port);
}
addServer(net_from);
}
break;
case LAUNCHER_CHALLENGE:
if(net_message.BytesLeftToRead() > 0)
{
printf("Master syncing server list (ignored), IP = %s\n", NET_AdrToString(net_from));
}
else
{
printf("Client request IP = %s\n", NET_AdrToString(net_from));
message.clear();
message.WriteLong(LAUNCHER_CHALLENGE);
writeServerData();
NET_SendPacket(message.cursize, message.data, net_from);
}
break;
default:
break;
}
}
static int counter = 0;
usleep(50*1000); // every 50ms // denis - fixme - use proper timer, age of servers should increase even during packet sending
ageServers();
if(!(counter%100))
{
dumpServersToFile();
if (ping_itr == servers.end())
ping_itr = servers.begin();
if(ping_itr != servers.end())
pingServer(*(ping_itr++));
}
counter++;
}
servers.clear();
CloseNetwork();
return 0;
}