/** * If a packet has not been received from a client for timeout.value * seconds, drop the conneciton. Server frames are used instead of realtime * to avoid dropping the local client while debugging. * * When a client is normally dropped, the client_t goes into a zombie state * for a few seconds to make sure any final reliable message gets resent if * necessary. */ public static void SV_CheckTimeouts() { int i; client_t cl; int droppoint; int zombiepoint; droppoint = (int)(SV_INIT.svs.realtime - 1000 * SV_MAIN.timeout.value); zombiepoint = (int)(SV_INIT.svs.realtime - 1000 * SV_MAIN.zombietime.value); for (i = 0; i < SV_MAIN.maxclients.value; i++) { cl = SV_INIT.svs.clients[i]; // message times may be wrong across a changelevel if (cl.lastmessage > SV_INIT.svs.realtime) { cl.lastmessage = SV_INIT.svs.realtime; } if (cl.state == Defines.cs_zombie && cl.lastmessage < zombiepoint) { cl.state = Defines.cs_free; // can now be reused continue; } if ((cl.state == Defines.cs_connected || cl.state == Defines.cs_spawned) && cl.lastmessage < droppoint) { SV_SEND.SV_BroadcastPrintf(Defines.PRINT_HIGH, cl.name + " timed out\n"); SV_MAIN.SV_DropClient(cl); cl.state = Defines.cs_free; // don't bother with zombie state } } }
/** * Initializes player structures after successfull connection. */ public static void gotnewcl(int i, int challenge, string userinfo, netadr_t adr, int qport) { // build a new connection // accept the new client // this is the only place a client_t is ever initialized SV_MAIN.sv_client = SV_INIT.svs.clients[i]; var edictnum = i + 1; var ent = GameBase.g_edicts[edictnum]; SV_INIT.svs.clients[i].edict = ent; // save challenge for checksumming SV_INIT.svs.clients[i].challenge = challenge; // get the game a chance to reject this connection or modify the // userinfo if (!PlayerClient.ClientConnect(ent, userinfo)) { if (Info.Info_ValueForKey(userinfo, "rejmsg") != null) { Netchan.OutOfBandPrint(Defines.NS_SERVER, adr, "print\n" + Info.Info_ValueForKey(userinfo, "rejmsg") + "\nConnection refused.\n"); } else { Netchan.OutOfBandPrint(Defines.NS_SERVER, adr, "print\nConnection refused.\n"); } Com.DPrintf("Game rejected a connection.\n"); return; } // parse some info from the info strings SV_INIT.svs.clients[i].userinfo = userinfo; SV_MAIN.SV_UserinfoChanged(SV_INIT.svs.clients[i]); // send the connect packet to the client Netchan.OutOfBandPrint(Defines.NS_SERVER, adr, "client_connect"); Netchan.Setup(Defines.NS_SERVER, SV_INIT.svs.clients[i].netchan, adr, qport); SV_INIT.svs.clients[i].state = Defines.cs_connected; SZ.Init(SV_INIT.svs.clients[i].datagram, SV_INIT.svs.clients[i].datagram_buf, SV_INIT.svs.clients[i].datagram_buf.Length); SV_INIT.svs.clients[i].datagram.allowoverflow = true; SV_INIT.svs.clients[i].lastmessage = SV_INIT.svs.realtime; // don't timeout SV_INIT.svs.clients[i].lastconnect = SV_INIT.svs.realtime; Com.DPrintf("new client added.\n"); }
/** * A connectionless packet has four leading 0xff characters to distinguish * it from a game channel. Clients that are in the game can still send * connectionless packets. It is used also by rcon commands. */ public static void SV_ConnectionlessPacket() { string s; string c; MSG.BeginReading(Globals.net_message); MSG.ReadLong(Globals.net_message); // skip the -1 marker s = MSG.ReadStringLine(Globals.net_message); Cmd.TokenizeString(s.ToCharArray(), false); c = Cmd.Argv(0); //for debugging purposes //Com.Printf("Packet " + NET.AdrToString(Netchan.net_from) + " : " + c + "\n"); //Com.Printf(Lib.hexDump(net_message.data, 64, false) + "\n"); if (0 == Lib.strcmp(c, "ping")) { SV_MAIN.SVC_Ping(); } else if (0 == Lib.strcmp(c, "ack")) { SV_MAIN.SVC_Ack(); } else if (0 == Lib.strcmp(c, "status")) { SV_MAIN.SVC_Status(); } else if (0 == Lib.strcmp(c, "info")) { SV_MAIN.SVC_Info(); } else if (0 == Lib.strcmp(c, "getchallenge")) { SV_MAIN.SVC_GetChallenge(); } else if (0 == Lib.strcmp(c, "connect")) { SV_MAIN.SVC_DirectConnect(); } else if (0 == Lib.strcmp(c, "rcon")) { SV_MAIN.SVC_RemoteCommand(); } else { Com.Printf("bad connectionless packet from " + NET.AdrToString(Globals.net_from) + "\n"); Com.Printf("[" + s + "]\n"); Com.Printf("" + Lib.hexDump(Globals.net_message.data, 128, false)); } }
/** * A client issued an rcon command. Shift down the remaining args Redirect * all printfs fromt hte server to the client. */ public static void SVC_RemoteCommand() { int i; string remaining; i = SV_MAIN.Rcon_Validate(); var msg = Lib.CtoJava(Globals.net_message.data, 4, 1024); if (i == 0) { Com.Printf("Bad rcon from " + NET.AdrToString(Globals.net_from) + ":\n" + msg + "\n"); } else { Com.Printf("Rcon from " + NET.AdrToString(Globals.net_from) + ":\n" + msg + "\n"); } Com.BeginRedirect( Defines.RD_PACKET, SV_SEND.sv_outputbuf, Defines.SV_OUTPUTBUF_LENGTH, (target, buffer) => { SV_SEND.SV_FlushRedirect(target, Lib.stringToBytes(buffer.ToString())); } ); if (0 == SV_MAIN.Rcon_Validate()) { Com.Printf("Bad rcon_password.\n"); } else { remaining = ""; for (i = 2; i < Cmd.Argc(); i++) { remaining += Cmd.Argv(i); remaining += " "; } Cmd.ExecuteString(remaining); } Com.EndRedirect(); }
/** * Called when each game quits, before Sys_Quit or Sys_Error. */ public static void SV_Shutdown(string finalmsg, bool reconnect) { if (SV_INIT.svs.clients != null) { SV_MAIN.SV_FinalMessage(finalmsg, reconnect); } SV_MAIN.Master_Shutdown(); SV_GAME.SV_ShutdownGameProgs(); // free current level if (SV_INIT.sv.demofile != null) { try { SV_INIT.sv.demofile.Close(); } catch (Exception e) { Console.WriteLine(e); } } SV_INIT.sv = new(); Globals.server_state = SV_INIT.sv.state; if (SV_INIT.svs.demofile != null) { try { SV_INIT.svs.demofile.Close(); } catch (Exception e1) { Console.WriteLine(e1); } } SV_INIT.svs = new(); }
public static void Master_Heartbeat() { string @string; int i; // pgm post3.19 change, cvar pointer not validated before dereferencing if (Globals.dedicated == null || 0 == Globals.dedicated.value) { return; // only dedicated servers send heartbeats } // pgm post3.19 change, cvar pointer not validated before dereferencing if (null == SV_MAIN.public_server || 0 == SV_MAIN.public_server.value) { return; // a private dedicated game } // check for time wraparound if (SV_INIT.svs.last_heartbeat > SV_INIT.svs.realtime) { SV_INIT.svs.last_heartbeat = SV_INIT.svs.realtime; } if (SV_INIT.svs.realtime - SV_INIT.svs.last_heartbeat < SV_MAIN.HEARTBEAT_SECONDS * 1000) { return; // not time to send yet } SV_INIT.svs.last_heartbeat = SV_INIT.svs.realtime; // send the same string that we would give for a status OOB command @string = SV_MAIN.SV_StatusString(); // send to group master for (i = 0; i < Defines.MAX_MASTERS; i++) { if (SV_MAIN.master_adr[i].port != 0) { Com.Printf("Sending heartbeat to " + NET.AdrToString(SV_MAIN.master_adr[i]) + "\n"); Netchan.OutOfBandPrint(Defines.NS_SERVER, SV_MAIN.master_adr[i], "heartbeat\n" + @string); } } }
/** * SV_Frame. */ public static void SV_Frame(long msec) { Globals.time_before_game = Globals.time_after_game = 0; // if server is not active, do nothing if (!SV_INIT.svs.initialized) { return; } SV_INIT.svs.realtime += (int)msec; // keep the random time dependent Lib.rand(); // check timeouts SV_MAIN.SV_CheckTimeouts(); // get packets from clients SV_MAIN.SV_ReadPackets(); //if (Game.g_edicts[1] !=null) // Com.p("player at:" + Lib.vtofsbeaty(Game.g_edicts[1].s.origin )); // move autonomous things around if enough time has passed if (0 == SV_MAIN.sv_timedemo.value && SV_INIT.svs.realtime < SV_INIT.sv.time) { // never let the time get too far off if (SV_INIT.sv.time - SV_INIT.svs.realtime > 100) { if (SV_MAIN.sv_showclamp.value != 0) { Com.Printf("sv lowclamp\n"); } SV_INIT.svs.realtime = SV_INIT.sv.time - 100; } return; } // update ping based on the last known frame from all clients SV_MAIN.SV_CalcPings(); // give the clients some timeslices SV_MAIN.SV_GiveMsec(); // let everything in the world think and move SV_MAIN.SV_RunGameFrame(); // send messages back to the clients that had packets read this frame SV_SEND.SV_SendClientMessages(); // save the entire world state if recording a serverdemo SV_ENTS.SV_RecordDemoMessage(); // send a heartbeat to the master if needed SV_MAIN.Master_Heartbeat(); // clear teleport flags, etc for next frame SV_MAIN.SV_PrepWorldFrame(); }
/** * Reads packets from the network or loopback. */ public static void SV_ReadPackets() { int i; client_t cl; var qport = 0; while (NET.GetPacket(Defines.NS_SERVER, Globals.net_from, Globals.net_message)) { // check for connectionless packet (0xffffffff) first if (Globals.net_message.data[0] == 255 && Globals.net_message.data[1] == 255 && Globals.net_message.data[2] == 255 && Globals.net_message.data[3] == 255) { SV_MAIN.SV_ConnectionlessPacket(); continue; } // read the qport out of the message so we can fix up // stupid address translating routers MSG.BeginReading(Globals.net_message); MSG.ReadLong(Globals.net_message); // sequence number MSG.ReadLong(Globals.net_message); // sequence number qport = MSG.ReadShort(Globals.net_message) & 0xffff; // check for packets from connected clients for (i = 0; i < SV_MAIN.maxclients.value; i++) { cl = SV_INIT.svs.clients[i]; if (cl.state == Defines.cs_free) { continue; } if (!NET.CompareBaseAdr(Globals.net_from, cl.netchan.remote_address)) { continue; } if (cl.netchan.qport != qport) { continue; } if (cl.netchan.remote_address.port != Globals.net_from.port) { Com.Printf("SV_ReadPackets: fixing up a translated port\n"); cl.netchan.remote_address.port = Globals.net_from.port; } if (Netchan.Process(cl.netchan, Globals.net_message)) { // this is a valid, sequenced packet, so process it if (cl.state != Defines.cs_zombie) { cl.lastmessage = SV_INIT.svs.realtime; // don't timeout SV_USER.SV_ExecuteClientMessage(cl); } } break; } if (i != SV_MAIN.maxclients.value) { continue; } } }
/** * A connection request that did not come from the master. */ public static void SVC_DirectConnect() { string userinfo; netadr_t adr; int i; client_t cl; int version; int qport; adr = Globals.net_from; Com.DPrintf("SVC_DirectConnect ()\n"); version = Lib.atoi(Cmd.Argv(1)); if (version != Defines.PROTOCOL_VERSION) { Netchan.OutOfBandPrint(Defines.NS_SERVER, adr, "print\nServer is version " + Globals.VERSION + "\n"); Com.DPrintf(" rejected connect from version " + version + "\n"); return; } qport = Lib.atoi(Cmd.Argv(2)); var challenge = Lib.atoi(Cmd.Argv(3)); userinfo = Cmd.Argv(4); // force the IP key/value pair so the game can filter based on ip userinfo = Info.Info_SetValueForKey(userinfo, "ip", NET.AdrToString(Globals.net_from)); // attractloop servers are ONLY for local clients if (SV_INIT.sv.attractloop) { if (!NET.IsLocalAddress(adr)) { Com.Printf("Remote connect in attract loop. Ignored.\n"); Netchan.OutOfBandPrint(Defines.NS_SERVER, adr, "print\nConnection refused.\n"); return; } } // see if the challenge is valid if (!NET.IsLocalAddress(adr)) { for (i = 0; i < Defines.MAX_CHALLENGES; i++) { if (NET.CompareBaseAdr(Globals.net_from, SV_INIT.svs.challenges[i].adr)) { if (challenge == SV_INIT.svs.challenges[i].challenge) { break; // good } Netchan.OutOfBandPrint(Defines.NS_SERVER, adr, "print\nBad challenge.\n"); return; } } if (i == Defines.MAX_CHALLENGES) { Netchan.OutOfBandPrint(Defines.NS_SERVER, adr, "print\nNo challenge for address.\n"); return; } } // if there is already a slot for this ip, reuse it for (i = 0; i < SV_MAIN.maxclients.value; i++) { cl = SV_INIT.svs.clients[i]; if (cl.state == Defines.cs_free) { continue; } if (NET.CompareBaseAdr(adr, cl.netchan.remote_address) && (cl.netchan.qport == qport || adr.port == cl.netchan.remote_address.port)) { if (!NET.IsLocalAddress(adr) && SV_INIT.svs.realtime - cl.lastconnect < (int)SV_MAIN.sv_reconnect_limit.value * 1000) { Com.DPrintf(NET.AdrToString(adr) + ":reconnect rejected : too soon\n"); return; } Com.Printf(NET.AdrToString(adr) + ":reconnect\n"); SV_MAIN.gotnewcl(i, challenge, userinfo, adr, qport); return; } } // find a client slot //newcl = null; var index = -1; for (i = 0; i < SV_MAIN.maxclients.value; i++) { cl = SV_INIT.svs.clients[i]; if (cl.state == Defines.cs_free) { index = i; break; } } if (index == -1) { Netchan.OutOfBandPrint(Defines.NS_SERVER, adr, "print\nServer is full.\n"); Com.DPrintf("Rejected a connection.\n"); return; } SV_MAIN.gotnewcl(index, challenge, userinfo, adr, qport); }
/** * Responds with all the info that qplug or qspy can see */ public static void SVC_Status() { Netchan.OutOfBandPrint(Defines.NS_SERVER, Globals.net_from, "print\n" + SV_MAIN.SV_StatusString()); }
/** * SV_InitGame. * * A brand new game has been started. */ public static void SV_InitGame() { int i; edict_t ent; //char idmaster[32]; string idmaster; if (SV_INIT.svs.initialized) { // cause any connected clients to reconnect SV_MAIN.SV_Shutdown("Server restarted\n", true); } else { // make sure the client is down Cl.Drop(); SCR.BeginLoadingPlaque(); } // get any latched variable changes (maxclients, etc) Cvar.GetLatchedVars(); SV_INIT.svs.initialized = true; if (Cvar.VariableValue("coop") != 0 && Cvar.VariableValue("deathmatch") != 0) { Com.Printf("Deathmatch and Coop both set, disabling Coop\n"); Cvar.FullSet("coop", "0", Defines.CVAR_SERVERINFO | Defines.CVAR_LATCH); } // dedicated servers are can't be single player and are usually DM // so unless they explicity set coop, force it to deathmatch if (Globals.dedicated.value != 0) { if (0 == Cvar.VariableValue("coop")) { Cvar.FullSet("deathmatch", "1", Defines.CVAR_SERVERINFO | Defines.CVAR_LATCH); } } // init clients if (Cvar.VariableValue("deathmatch") != 0) { if (SV_MAIN.maxclients.value <= 1) { Cvar.FullSet("maxclients", "8", Defines.CVAR_SERVERINFO | Defines.CVAR_LATCH); } else if (SV_MAIN.maxclients.value > Defines.MAX_CLIENTS) { Cvar.FullSet("maxclients", "" + Defines.MAX_CLIENTS, Defines.CVAR_SERVERINFO | Defines.CVAR_LATCH); } } else if (Cvar.VariableValue("coop") != 0) { if (SV_MAIN.maxclients.value <= 1 || SV_MAIN.maxclients.value > 4) { Cvar.FullSet("maxclients", "4", Defines.CVAR_SERVERINFO | Defines.CVAR_LATCH); } } else // non-deathmatch, non-coop is one player { Cvar.FullSet("maxclients", "1", Defines.CVAR_SERVERINFO | Defines.CVAR_LATCH); } SV_INIT.svs.spawncount = Lib.rand(); SV_INIT.svs.clients = new client_t[(int)SV_MAIN.maxclients.value]; for (var n = 0; n < SV_INIT.svs.clients.Length; n++) { SV_INIT.svs.clients[n] = new(); SV_INIT.svs.clients[n].serverindex = n; } SV_INIT.svs.num_client_entities = (int)SV_MAIN.maxclients.value * Defines.UPDATE_BACKUP * 64; //ok. SV_INIT.svs.client_entities = new entity_state_t[SV_INIT.svs.num_client_entities]; for (var n = 0; n < SV_INIT.svs.client_entities.Length; n++) { SV_INIT.svs.client_entities[n] = new(null); } // init network stuff NET.ConfigServer(SV_MAIN.maxclients.value > 1); // heartbeats will always be sent to the id master SV_INIT.svs.last_heartbeat = -99999; // send immediately idmaster = "192.246.40.37:" + Defines.PORT_MASTER; NET.StringToAdr(idmaster, SV_MAIN.master_adr[0]); // init game SV_GAME.SV_InitGameProgs(); for (i = 0; i < SV_MAIN.maxclients.value; i++) { ent = GameBase.g_edicts[i + 1]; SV_INIT.svs.clients[i].edict = ent; SV_INIT.svs.clients[i].lastcmd = new(); } }