Skip to content
Snippets Groups Projects
Select Git revision
  • 16569b4e9a2abd5ed8be0f8c293ea800b92b5ef6
  • next default protected
  • movie
  • movie-netcode-fixes
  • fix-nil-key-unarchiving-error
  • next-test
  • classic-netcode-fixes
  • master protected
  • softcode-info
  • acs
  • clipmidtex
  • custom-map-names
  • nogravity-trampolines
  • 2214-pre4
  • 2214-pre3
  • just-in-case
  • fix-opengl-parameter-crash
  • 2214-pre2
  • 2214-pre1
  • delfile2
  • cleanupmusic
  • SRB2_release_2.2.15
  • SRB2_release_2.2.13
  • SRB2_release_2.2.12
  • SRB2_release_2.2.11
  • SRB2_release_2.2.10
  • SRB2_release_2.2.9
  • SRB2_release_2.2.8
  • SRB2_release_2.2.7
  • SRB2_release_2.2.6
  • SRB2_release_2.2.5
  • SRB2_release_2.2.4
  • SRB2_release_2.2.3
  • SRB2_release_2.2.2
  • SRB2_release_2.2.1
  • SRB2_release_2.2.0
  • SRB2_release_2.1.25
  • SRB2_release_2.1.24
  • SRB2_release_2.1.23
  • SRB2_release_2.1.22
  • SRB2_release_2.1.21
41 results

am_map.h

Blame
  • server_connection.c 13.88 KiB
    // SONIC ROBO BLAST 2
    //-----------------------------------------------------------------------------
    // Copyright (C) 1998-2000 by DooM Legacy Team.
    // Copyright (C) 1999-2024 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 "../command.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];
    
    consvar_t cv_showjoinaddress = CVAR_INIT ("showjoinaddress", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL);
    
    consvar_t cv_allownewplayer = CVAR_INIT ("allowjoin", "On", CV_SAVE|CV_NETVAR|CV_ALLOWLUA, CV_OnOff, NULL);
    
    static CV_PossibleValue_t maxplayers_cons_t[] = {{2, "MIN"}, {32, "MAX"}, {0, NULL}};
    consvar_t cv_maxplayers = CVAR_INIT ("maxplayers", "8", CV_SAVE|CV_NETVAR|CV_ALLOWLUA, maxplayers_cons_t, NULL);
    
    static CV_PossibleValue_t joindelay_cons_t[] = {{1, "MIN"}, {3600, "MAX"}, {0, "Off"}, {0, NULL}};
    consvar_t cv_joindelay = CVAR_INIT ("joindelay", "10", CV_SAVE|CV_NETVAR, joindelay_cons_t, NULL);
    
    static CV_PossibleValue_t rejointimeout_cons_t[] = {{1, "MIN"}, {60 * FRACUNIT, "MAX"}, {0, "Off"}, {0, NULL}};
    consvar_t cv_rejointimeout = CVAR_INIT ("rejointimeout", "2", CV_SAVE|CV_NETVAR|CV_FLOAT, rejointimeout_cons_t, NULL);
    
    static INT32 FindRejoinerNum(SINT8 node)
    {
    	char addressbuffer[64];
    	const char *nodeaddress;
    	const char *strippednodeaddress;
    
    	// 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(addressbuffer, nodeaddress);
    	strippednodeaddress = I_NetSplitAddress(addressbuffer, NULL);
    
    	// Check if any player matches the stripped address
    	for (INT32 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);
    
    	// Exclude bots from both counts
    	netbuffer->u.serverinfo.numberofplayer = (UINT8)D_NumNodes(dedicated);
    	netbuffer->u.serverinfo.maxplayer = (UINT8)(cv_maxplayers.value - D_NumBots());
    
    	netbuffer->u.serverinfo.refusereason = GetRefuseReason(node);
    
    	strncpy(netbuffer->u.serverinfo.gametypename, Gametype_Names[gametype],
    			sizeof(netbuffer->u.serverinfo.gametypename)-1);
    	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,
    		sizeof(netbuffer->u.serverinfo.servername)-1);
    	strlcpy(netbuffer->u.serverinfo.mapname, G_BuildMapName(gamemap), sizeof netbuffer->u.serverinfo.mapname);
    	M_Memcpy(netbuffer->u.serverinfo.mapmd5, mapmd5, 16);
    
    	memset(netbuffer->u.serverinfo.maptitle, 0, sizeof netbuffer->u.serverinfo.maptitle);
    
    	memset(netbuffer->u.serverinfo.httpsource, 0, MAX_MIRROR_LENGTH);
    
    	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';
    	}
    	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;
    
    	const char *httpurl = cv_httpsource.string;
    	size_t mirror_length = strlen(httpurl);
    	if (mirror_length > MAX_MIRROR_LENGTH)
    		mirror_length = MAX_MIRROR_LENGTH;
    
    	if (snprintf(netbuffer->u.serverinfo.httpsource, mirror_length+1, "%s", httpurl) < 0)
    		// If there's an encoding error, send nothing, we accept that the above may be truncated
    		strncpy(netbuffer->u.serverinfo.httpsource, "", mirror_length);
    
    	netbuffer->u.serverinfo.httpsource[MAX_MIRROR_LENGTH-1] = '\0';
    
    	p = PutFileNeeded(0);
    
    	HSendPacket(node, false, 0, p - ((UINT8 *)&netbuffer->u));
    }
    
    static void SV_SendPlayerInfo(INT32 node)
    {
    	netbuffer->packettype = PT_PLAYERINFO;
    
    	for (UINT8 i = 0; i < MAXPLAYERS; i++)
    	{
    		if (playernode[i] == UINT8_MAX || !netnodes[playernode[i]].ingame)
    		{
    			netbuffer->u.playerinfo[i].num = 255; // This slot is empty.
    			continue;
    		}
    
    		netbuffer->u.playerinfo[i].num = i;
    		memset(netbuffer->u.playerinfo[i].name, 0x00, sizeof(netbuffer->u.playerinfo[i].name));
    		memcpy(netbuffer->u.playerinfo[i].name, player_names[i], sizeof(player_names[i]));
    
    		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_pak) * 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;
    	netbuffer->u.servercfg.usedCheats = (UINT8)usedCheats;
    
    	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 PT_ClientJoin(SINT8 node)
    {
    	char names[MAXSPLITSCREENPLAYERS][MAXPLAYERNAME + 1];
    	INT32 numplayers = netbuffer->u.clientcfg.localplayers;
    	INT32 rejoinernum;
    
    	// Ignore duplicate packets
    	if (client || netnodes[node].ingame)
    		return;
    
    	rejoinernum = FindRejoinerNum(node);
    
    	const char *refuse = GetRefuseMessage(node, rejoinernum);
    	if (refuse)
    	{
    		SV_SendRefuse(node, refuse);
    		return;
    	}
    
    	for (INT32 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;
    	}
    	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 (INT32 i = 0; i < numplayers; i++)
    		SV_AddPlayer(node, names[i]);
    
    	joindelay += cv_joindelay.value * TICRATE;
    }
    
    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);
    }