mirror of
https://git.do.srb2.org/STJr/SRB2.git
synced 2024-12-14 23:01:05 +00:00
499 lines
12 KiB
C
499 lines
12 KiB
C
// SONIC ROBO BLAST 2
|
|
//-----------------------------------------------------------------------------
|
|
// Copyright (C) 1998-2000 by DooM Legacy Team.
|
|
// Copyright (C) 1999-2022 by Sonic Team Junior.
|
|
//
|
|
// This program is free software distributed under the
|
|
// terms of the GNU General Public License, version 2.
|
|
// See the 'LICENSE' file for more details.
|
|
//-----------------------------------------------------------------------------
|
|
/// \file server_connection.c
|
|
/// \brief Server-side part of connection handling
|
|
|
|
#include "server_connection.h"
|
|
#include "i_net.h"
|
|
#include "d_clisrv.h"
|
|
#include "d_netfil.h"
|
|
#include "mserv.h"
|
|
#include "net_command.h"
|
|
#include "gamestate.h"
|
|
#include "../byteptr.h"
|
|
#include "../g_game.h"
|
|
#include "../g_state.h"
|
|
#include "../p_setup.h"
|
|
#include "../p_tick.h"
|
|
#include "../doomstat.h"
|
|
|
|
// Minimum timeout for sending the savegame
|
|
// The actual timeout will be longer depending on the savegame length
|
|
tic_t jointimeout = (10*TICRATE);
|
|
|
|
// Incremented by cv_joindelay when a client joins, decremented each tic.
|
|
// If higher than cv_joindelay * 2 (3 joins in a short timespan), joins are temporarily disabled.
|
|
tic_t joindelay = 0;
|
|
|
|
// Minimum timeout for sending the savegame
|
|
// The actual timeout will be longer depending on the savegame length
|
|
char playeraddress[MAXPLAYERS][64];
|
|
|
|
UINT8 player_joining = false;
|
|
|
|
static INT32 FindRejoinerNum(SINT8 node)
|
|
{
|
|
char strippednodeaddress[64];
|
|
const char *nodeaddress;
|
|
char *port;
|
|
INT32 i;
|
|
|
|
// Make sure there is no dead dress before proceeding to the stripping
|
|
if (!I_GetNodeAddress)
|
|
return -1;
|
|
nodeaddress = I_GetNodeAddress(node);
|
|
if (!nodeaddress)
|
|
return -1;
|
|
|
|
// Strip the address of its port
|
|
strcpy(strippednodeaddress, nodeaddress);
|
|
port = strchr(strippednodeaddress, ':');
|
|
if (port)
|
|
*port = '\0';
|
|
|
|
// Check if any player matches the stripped address
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
if (playeringame[i] && playeraddress[i][0] && playernode[i] == UINT8_MAX
|
|
&& !strcmp(playeraddress[i], strippednodeaddress))
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static UINT8
|
|
GetRefuseReason (INT32 node)
|
|
{
|
|
if (!node || FindRejoinerNum(node) != -1)
|
|
return 0;
|
|
else if (bannednode && bannednode[node])
|
|
return REFUSE_BANNED;
|
|
else if (!cv_allownewplayer.value)
|
|
return REFUSE_JOINS_DISABLED;
|
|
else if (D_NumPlayers() >= cv_maxplayers.value)
|
|
return REFUSE_SLOTS_FULL;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static void SV_SendServerInfo(INT32 node, tic_t servertime)
|
|
{
|
|
UINT8 *p;
|
|
|
|
netbuffer->packettype = PT_SERVERINFO;
|
|
netbuffer->u.serverinfo._255 = 255;
|
|
netbuffer->u.serverinfo.packetversion = PACKETVERSION;
|
|
netbuffer->u.serverinfo.version = VERSION;
|
|
netbuffer->u.serverinfo.subversion = SUBVERSION;
|
|
strncpy(netbuffer->u.serverinfo.application, SRB2APPLICATION,
|
|
sizeof netbuffer->u.serverinfo.application);
|
|
// return back the time value so client can compute their ping
|
|
netbuffer->u.serverinfo.time = (tic_t)LONG(servertime);
|
|
netbuffer->u.serverinfo.leveltime = (tic_t)LONG(leveltime);
|
|
|
|
netbuffer->u.serverinfo.numberofplayer = (UINT8)D_NumPlayers();
|
|
netbuffer->u.serverinfo.maxplayer = (UINT8)cv_maxplayers.value;
|
|
|
|
netbuffer->u.serverinfo.refusereason = GetRefuseReason(node);
|
|
|
|
strncpy(netbuffer->u.serverinfo.gametypename, Gametype_Names[gametype],
|
|
sizeof netbuffer->u.serverinfo.gametypename);
|
|
netbuffer->u.serverinfo.modifiedgame = (UINT8)modifiedgame;
|
|
netbuffer->u.serverinfo.cheatsenabled = CV_CheatsEnabled();
|
|
netbuffer->u.serverinfo.flags = (dedicated ? SV_DEDICATED : 0);
|
|
strncpy(netbuffer->u.serverinfo.servername, cv_servername.string,
|
|
MAXSERVERNAME);
|
|
strncpy(netbuffer->u.serverinfo.mapname, G_BuildMapName(gamemap), 7);
|
|
|
|
M_Memcpy(netbuffer->u.serverinfo.mapmd5, mapmd5, 16);
|
|
|
|
memset(netbuffer->u.serverinfo.maptitle, 0, sizeof netbuffer->u.serverinfo.maptitle);
|
|
|
|
if (mapheaderinfo[gamemap-1] && *mapheaderinfo[gamemap-1]->lvlttl)
|
|
{
|
|
char *read = mapheaderinfo[gamemap-1]->lvlttl, *writ = netbuffer->u.serverinfo.maptitle;
|
|
while (writ < (netbuffer->u.serverinfo.maptitle+32) && *read != '\0')
|
|
{
|
|
if (!(*read & 0x80))
|
|
{
|
|
*writ = toupper(*read);
|
|
writ++;
|
|
}
|
|
read++;
|
|
}
|
|
*writ = '\0';
|
|
//strncpy(netbuffer->u.serverinfo.maptitle, (char *)mapheaderinfo[gamemap-1]->lvlttl, 33);
|
|
}
|
|
else
|
|
strncpy(netbuffer->u.serverinfo.maptitle, "UNKNOWN", 32);
|
|
|
|
if (mapheaderinfo[gamemap-1] && !(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE))
|
|
netbuffer->u.serverinfo.iszone = 1;
|
|
else
|
|
netbuffer->u.serverinfo.iszone = 0;
|
|
|
|
if (mapheaderinfo[gamemap-1])
|
|
netbuffer->u.serverinfo.actnum = mapheaderinfo[gamemap-1]->actnum;
|
|
|
|
p = PutFileNeeded(0);
|
|
|
|
HSendPacket(node, false, 0, p - ((UINT8 *)&netbuffer->u));
|
|
}
|
|
|
|
static void SV_SendPlayerInfo(INT32 node)
|
|
{
|
|
UINT8 i;
|
|
netbuffer->packettype = PT_PLAYERINFO;
|
|
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
if (!playeringame[i])
|
|
{
|
|
netbuffer->u.playerinfo[i].num = 255; // This slot is empty.
|
|
continue;
|
|
}
|
|
|
|
netbuffer->u.playerinfo[i].num = i;
|
|
strncpy(netbuffer->u.playerinfo[i].name, (const char *)&player_names[i], MAXPLAYERNAME+1);
|
|
netbuffer->u.playerinfo[i].name[MAXPLAYERNAME] = '\0';
|
|
|
|
//fetch IP address
|
|
//No, don't do that, you fuckface.
|
|
memset(netbuffer->u.playerinfo[i].address, 0, 4);
|
|
|
|
if (G_GametypeHasTeams())
|
|
{
|
|
if (!players[i].ctfteam)
|
|
netbuffer->u.playerinfo[i].team = 255;
|
|
else
|
|
netbuffer->u.playerinfo[i].team = (UINT8)players[i].ctfteam;
|
|
}
|
|
else
|
|
{
|
|
if (players[i].spectator)
|
|
netbuffer->u.playerinfo[i].team = 255;
|
|
else
|
|
netbuffer->u.playerinfo[i].team = 0;
|
|
}
|
|
|
|
netbuffer->u.playerinfo[i].score = LONG(players[i].score);
|
|
netbuffer->u.playerinfo[i].timeinserver = SHORT((UINT16)(players[i].jointime / TICRATE));
|
|
netbuffer->u.playerinfo[i].skin = (UINT8)(players[i].skin
|
|
#ifdef DEVELOP // it's safe to do this only because PLAYERINFO isn't read by the game itself
|
|
% 3
|
|
#endif
|
|
);
|
|
|
|
// Extra data
|
|
netbuffer->u.playerinfo[i].data = 0; //players[i].skincolor;
|
|
|
|
if (players[i].pflags & PF_TAGIT)
|
|
netbuffer->u.playerinfo[i].data |= 0x20;
|
|
|
|
if (players[i].gotflag)
|
|
netbuffer->u.playerinfo[i].data |= 0x40;
|
|
|
|
if (players[i].powers[pw_super])
|
|
netbuffer->u.playerinfo[i].data |= 0x80;
|
|
}
|
|
|
|
HSendPacket(node, false, 0, sizeof(plrinfo) * MAXPLAYERS);
|
|
}
|
|
|
|
/** Sends a PT_SERVERCFG packet
|
|
*
|
|
* \param node The destination
|
|
* \return True if the packet was successfully sent
|
|
*
|
|
*/
|
|
static boolean SV_SendServerConfig(INT32 node)
|
|
{
|
|
boolean waspacketsent;
|
|
|
|
netbuffer->packettype = PT_SERVERCFG;
|
|
|
|
netbuffer->u.servercfg.serverplayer = (UINT8)serverplayer;
|
|
netbuffer->u.servercfg.totalslotnum = (UINT8)(doomcom->numslots);
|
|
netbuffer->u.servercfg.gametic = (tic_t)LONG(gametic);
|
|
netbuffer->u.servercfg.clientnode = (UINT8)node;
|
|
netbuffer->u.servercfg.gamestate = (UINT8)gamestate;
|
|
netbuffer->u.servercfg.gametype = (UINT8)gametype;
|
|
netbuffer->u.servercfg.modifiedgame = (UINT8)modifiedgame;
|
|
|
|
memcpy(netbuffer->u.servercfg.server_context, server_context, 8);
|
|
|
|
{
|
|
const size_t len = sizeof (serverconfig_pak);
|
|
|
|
#ifdef DEBUGFILE
|
|
if (debugfile)
|
|
{
|
|
fprintf(debugfile, "ServerConfig Packet about to be sent, size of packet:%s to node:%d\n",
|
|
sizeu1(len), node);
|
|
}
|
|
#endif
|
|
|
|
waspacketsent = HSendPacket(node, true, 0, len);
|
|
}
|
|
|
|
#ifdef DEBUGFILE
|
|
if (debugfile)
|
|
{
|
|
if (waspacketsent)
|
|
{
|
|
fprintf(debugfile, "ServerConfig Packet was sent\n");
|
|
}
|
|
else
|
|
{
|
|
fprintf(debugfile, "ServerConfig Packet could not be sent right now\n");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return waspacketsent;
|
|
}
|
|
|
|
// Adds a node to the game (player will follow at map change or at savegame....)
|
|
static inline void SV_AddNode(INT32 node)
|
|
{
|
|
netnodes[node].tic = gametic;
|
|
netnodes[node].supposedtic = gametic;
|
|
// little hack because the server connects to itself and puts
|
|
// nodeingame when connected not here
|
|
if (node)
|
|
netnodes[node].ingame = true;
|
|
}
|
|
|
|
static void SV_AddPlayer(SINT8 node, const char *name)
|
|
{
|
|
INT32 n;
|
|
UINT8 buf[2 + MAXPLAYERNAME];
|
|
UINT8 *p;
|
|
INT32 newplayernum;
|
|
|
|
newplayernum = FindRejoinerNum(node);
|
|
if (newplayernum == -1)
|
|
{
|
|
// search for a free playernum
|
|
// we can't use playeringame since it is not updated here
|
|
for (newplayernum = dedicated ? 1 : 0; newplayernum < MAXPLAYERS; newplayernum++)
|
|
{
|
|
if (playeringame[newplayernum])
|
|
continue;
|
|
for (n = 0; n < MAXNETNODES; n++)
|
|
if (netnodes[n].player == newplayernum || netnodes[n].player2 == newplayernum)
|
|
break;
|
|
if (n == MAXNETNODES)
|
|
break;
|
|
}
|
|
}
|
|
|
|
// should never happen since we check the playernum
|
|
// before accepting the join
|
|
I_Assert(newplayernum < MAXPLAYERS);
|
|
|
|
playernode[newplayernum] = (UINT8)node;
|
|
|
|
p = buf + 2;
|
|
buf[0] = (UINT8)node;
|
|
buf[1] = newplayernum;
|
|
if (netnodes[node].numplayers < 1)
|
|
{
|
|
netnodes[node].player = newplayernum;
|
|
}
|
|
else
|
|
{
|
|
netnodes[node].player2 = newplayernum;
|
|
buf[1] |= 0x80;
|
|
}
|
|
WRITESTRINGN(p, name, MAXPLAYERNAME);
|
|
netnodes[node].numplayers++;
|
|
|
|
SendNetXCmd(XD_ADDPLAYER, &buf, p - buf);
|
|
|
|
DEBFILE(va("Server added player %d node %d\n", newplayernum, node));
|
|
}
|
|
|
|
static void SV_SendRefuse(INT32 node, const char *reason)
|
|
{
|
|
strcpy(netbuffer->u.serverrefuse.reason, reason);
|
|
|
|
netbuffer->packettype = PT_SERVERREFUSE;
|
|
HSendPacket(node, true, 0, strlen(netbuffer->u.serverrefuse.reason) + 1);
|
|
Net_CloseConnection(node);
|
|
}
|
|
|
|
static const char *
|
|
GetRefuseMessage (SINT8 node, INT32 rejoinernum)
|
|
{
|
|
clientconfig_pak *cc = &netbuffer->u.clientcfg;
|
|
|
|
boolean rejoining = (rejoinernum != -1);
|
|
|
|
if (!node)/* server connecting to itself */
|
|
return NULL;
|
|
|
|
if (
|
|
cc->modversion != MODVERSION ||
|
|
strncmp(cc->application, SRB2APPLICATION,
|
|
sizeof cc->application)
|
|
){
|
|
return/* this is probably client's fault */
|
|
"Incompatible.";
|
|
}
|
|
else if (bannednode && bannednode[node])
|
|
{
|
|
return
|
|
"You have been banned\n"
|
|
"from the server.";
|
|
}
|
|
else if (cc->localplayers != 1)
|
|
{
|
|
return
|
|
"Wrong player count.";
|
|
}
|
|
|
|
if (!rejoining)
|
|
{
|
|
if (!cv_allownewplayer.value)
|
|
{
|
|
return
|
|
"The server is not accepting\n"
|
|
"joins for the moment.";
|
|
}
|
|
else if (D_NumPlayers() >= cv_maxplayers.value)
|
|
{
|
|
return va(
|
|
"Maximum players reached: %d",
|
|
cv_maxplayers.value);
|
|
}
|
|
}
|
|
|
|
if (luafiletransfers)
|
|
{
|
|
return
|
|
"The serveris broadcasting a file\n"
|
|
"requested by a Lua script.\n"
|
|
"Please wait a bit and then\n"
|
|
"try rejoining.";
|
|
}
|
|
|
|
if (netgame)
|
|
{
|
|
const tic_t th = 2 * cv_joindelay.value * TICRATE;
|
|
|
|
if (joindelay > th)
|
|
{
|
|
return va(
|
|
"Too many people are connecting.\n"
|
|
"Please wait %d seconds and then\n"
|
|
"try rejoining.",
|
|
(joindelay - th) / TICRATE);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/** Called when a PT_CLIENTJOIN packet is received
|
|
*
|
|
* \param node The packet sender
|
|
*
|
|
*/
|
|
void HandleConnect(SINT8 node)
|
|
{
|
|
char names[MAXSPLITSCREENPLAYERS][MAXPLAYERNAME + 1];
|
|
INT32 numplayers = netbuffer->u.clientcfg.localplayers;
|
|
INT32 rejoinernum;
|
|
INT32 i;
|
|
|
|
// Ignore duplicate packets
|
|
if (netnodes[node].ingame)
|
|
return;
|
|
|
|
rejoinernum = FindRejoinerNum(node);
|
|
|
|
const char *refuse = GetRefuseMessage(node, rejoinernum);
|
|
if (refuse)
|
|
{
|
|
SV_SendRefuse(node, refuse);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < numplayers; i++)
|
|
{
|
|
strlcpy(names[i], netbuffer->u.clientcfg.names[i], MAXPLAYERNAME + 1);
|
|
if (!EnsurePlayerNameIsGood(names[i], rejoinernum))
|
|
{
|
|
SV_SendRefuse(node, "Bad player name");
|
|
return;
|
|
}
|
|
}
|
|
|
|
SV_AddNode(node);
|
|
|
|
if (!SV_SendServerConfig(node))
|
|
{
|
|
/// \note Shouldn't SV_SendRefuse be called before ResetNode?
|
|
ResetNode(node);
|
|
SV_SendRefuse(node, M_GetText("Server couldn't send info, please try again"));
|
|
/// \todo fix this !!!
|
|
return; // restart the while
|
|
}
|
|
DEBFILE("new node joined\n");
|
|
|
|
if (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION)
|
|
{
|
|
SV_SendSaveGame(node, false); // send a complete game state
|
|
DEBFILE("send savegame\n");
|
|
}
|
|
|
|
// Splitscreen can allow 2 players in one node
|
|
for (i = 0; i < numplayers; i++)
|
|
SV_AddPlayer(node, names[i]);
|
|
|
|
joindelay += cv_joindelay.value * TICRATE;
|
|
player_joining = true;
|
|
}
|
|
|
|
void PT_AskInfoViaMS(SINT8 node)
|
|
{
|
|
Net_CloseConnection(node);
|
|
}
|
|
|
|
void PT_TellFilesNeeded(SINT8 node)
|
|
{
|
|
if (server && serverrunning)
|
|
{
|
|
UINT8 *p;
|
|
INT32 firstfile = netbuffer->u.filesneedednum;
|
|
|
|
netbuffer->packettype = PT_MOREFILESNEEDED;
|
|
netbuffer->u.filesneededcfg.first = firstfile;
|
|
netbuffer->u.filesneededcfg.more = 0;
|
|
|
|
p = PutFileNeeded(firstfile);
|
|
|
|
HSendPacket(node, false, 0, p - ((UINT8 *)&netbuffer->u));
|
|
}
|
|
else // Shouldn't get this if you aren't the server...?
|
|
Net_CloseConnection(node);
|
|
}
|
|
|
|
void PT_AskInfo(SINT8 node)
|
|
{
|
|
if (server && serverrunning)
|
|
{
|
|
SV_SendServerInfo(node, (tic_t)LONG(netbuffer->u.askinfo.time));
|
|
SV_SendPlayerInfo(node); // Send extra info
|
|
}
|
|
Net_CloseConnection(node);
|
|
}
|