// 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); }