📄 sv_main.pas
字号:
Result := status;
end;
(*
================
SVC_Status
Responds with all the info that qplug or qspy can see
================
*)
procedure SVC_Status ();
begin
Netchan_OutOfBandPrint (NS_SERVER, net_from, 'print'#10'%s', [SV_StatusString()]);
{$ifdef neverever}
Com_BeginRedirect (RD_PACKET, sv_outputbuf, SV_OUTPUTBUF_LENGTH, SV_FlushRedirect);
Com_Printf (SV_StatusString());
Com_EndRedirect ();
{$endif}
end;
(*
================
SVC_Ack
================
*)
procedure SVC_Ack ();
begin
Com_Printf ('Ping acknowledge from %s'#10, [NET_AdrToString(net_from)]);
end;
(*
================
SVC_Info
Responds with short info for broadcast scans
The second parameter should be the current protocol version number.
================
*)
procedure SVC_Info ();
var
string_: array[0..64-1] of char;
i, count: Integer;
version: integer;
begin
if (maxclients^.value = 1) then
exit; // ignore in single player
version := StrToInt (Cmd_Argv(1));
if (version <> PROTOCOL_VERSION) then
Com_sprintf (string_, sizeof(string_), '%s: wrong version'#10, [hostname^.string_, sizeof(string_)])
else begin
count := 0;
for i := 0 to Round(maxclients^.value)-1 do
if (svs.clients^[i].state >= cs_connected) then
Inc(count);
Com_sprintf (string_, sizeof(string_), '%16s %8s %2i/%2i'#10, [hostname^.string_, sv.name, count, round(maxclients^.value)]);
end;
Netchan_OutOfBandPrint (NS_SERVER, net_from, 'info'#10'%s', [string_]);
end;
(*
================
SVC_Ping
Just responds with an acknowledgement
================
*)
procedure SVC_Ping ();
begin
Netchan_OutOfBandPrint (NS_SERVER, net_from, 'ack', []);
end;
(*
=================
SVC_GetChallenge
Returns a challenge number that can be used
in a subsequent client_connect command.
We do this to prevent denial of service attacks that
flood the server with invalid connection IPs. With a
challenge, they must give a valid IP address.
=================
*)
procedure SVC_GetChallenge ();
var
i: integer;
oldest: integer;
oldestTime: integer;
begin
oldest := 0;
oldestTime := $7fffffff;
// see if we already have a challenge for this ip
i := 0;
while (i < MAX_CHALLENGES) do begin
if (NET_CompareBaseAdr (net_from, svs.challenges[i].adr)) then
break;
if (svs.challenges[i].time < oldestTime) then begin
oldestTime := svs.challenges[i].time;
oldest := i;
end;
Inc(i);
end;
if (i = MAX_CHALLENGES) then begin
// overwrite the oldest
svs.challenges[oldest].challenge := rand() and $7fff;
svs.challenges[oldest].adr := net_from;
svs.challenges[oldest].time := curtime;
i := oldest;
end;
// send it back
Netchan_OutOfBandPrint (NS_SERVER, net_from, 'challenge %i', [svs.challenges[i].challenge]);
end;
(*
==================
SVC_DirectConnect
A connection request that did not come from the master
==================
*)
procedure SVC_DirectConnect ();
var
userinfo: array[0..MAX_INFO_STRING-1] of char;
adr: netadr_t;
i: integer;
cl, newcl: client_p;
temp: client_t;
ent: edict_p;
edictnum: integer;
version: integer;
qport: integer;
challenge: integer;
label
gotnewcl;
begin
adr := net_from;
Com_DPrintf ('SVC_DirectConnect ()'#10);
version := StrToInt(Cmd_Argv(1));
if (version <> PROTOCOL_VERSION) then begin
Netchan_OutOfBandPrint (NS_SERVER, adr, 'print'#10'Server is version %4.2f.'#10, [VERSION]);
Com_DPrintf (' rejected connect from version %i'#10, [version]);
exit;
end;
qport := StrToInt(Cmd_Argv(2));
challenge := StrToInt(Cmd_Argv(3));
strncpy (userinfo, Cmd_Argv(4), sizeof(userinfo)-1);
userinfo[sizeof(userinfo) - 1] := #0;
// force the IP key/value pair so the game can filter based on ip
Info_SetValueForKey (userinfo, 'ip', NET_AdrToString(net_from));
// attractloop servers are ONLY for local clients
if (sv.attractloop) then begin
if (not NET_IsLocalAddress (adr)) then begin
Com_Printf ('Remote connect in attract loop. Ignored.'#10);
Netchan_OutOfBandPrint (NS_SERVER, adr, 'print'#10'Connection refused.'#10, []);
exit;
end;
end;
// see if the challenge is valid
if (not NET_IsLocalAddress (adr)) then begin
for i := 0 to MAX_CHALLENGES-1 do begin
if (NET_CompareBaseAdr (net_from, svs.challenges[i].adr)) then begin
if (challenge = svs.challenges[i].challenge) then
break; // good
Netchan_OutOfBandPrint (NS_SERVER, adr, 'print'#10'Bad challenge.'#10, []);
exit;
end;
end;
if (i = MAX_CHALLENGES) then begin
Netchan_OutOfBandPrint (NS_SERVER, adr, 'print'#10'No challenge for address.'#10, []);
exit;
end;
end;
newcl := @temp;
FillChar (newcl^, sizeof(client_t), #0);
// if there is already a slot for this ip, reuse it
for i := 0 to Round(maxclients^.value)-1 do begin
cl := @svs.clients^[i];
if (cl^.state = cs_free) then
continue;
if NET_CompareBaseAdr (adr, cl^.netchan.remote_address) and
((cl^.netchan.qport = qport) or (adr.port = cl^.netchan.remote_address.port)) then begin
if (not NET_IsLocalAddress (adr)) and ((svs.realtime - cl^.lastconnect) < (sv_reconnect_limit^.value * 1000)) then begin
Com_DPrintf ('%s:reconnect rejected : too soon'#10, [NET_AdrToString (adr)]);
exit;
end;
Com_Printf ('%s:reconnect'#10, [NET_AdrToString (adr)]);
newcl := cl;
goto gotnewcl;
end;
end;
// find a client slot
newcl := nil;
for i := 0 to round(maxclients^.value)-1 do begin
cl := @svs.clients^[i];
if (cl^.state = cs_free) then begin
newcl := cl;
break;
end;
end;
if (newcl = nil) then begin
Netchan_OutOfBandPrint (NS_SERVER, adr, 'print'#10'Server is full.'#10, []);
Com_DPrintf ('Rejected a connection.'#10);
exit;
end;
gotnewcl:
// build a new connection
// accept the new client
// this is the only place a client_t is ever initialized
newcl^ := temp;
sv_client := newcl;
edictnum := (Cardinal(newcl)-Cardinal(svs.clients)) div sizeof(client_s) + 1;
ent := EDICT_NUM(edictnum);
newcl^.edict := ent;
newcl^.challenge := challenge; // save challenge for checksumming
// get the game a chance to reject this connection or modify the userinfo
if (not (ge^.ClientConnect (ent, @userinfo))) then begin
if (Info_ValueForKey (userinfo, 'rejmsg')^ <> #0) then
Netchan_OutOfBandPrint (NS_SERVER, adr, 'print'#10'%s'#10'Connection refused.'#10,
[Info_ValueForKey (userinfo, 'rejmsg')])
else
Netchan_OutOfBandPrint (NS_SERVER, adr, 'print'#10'Connection refused.'#10, [] );
Com_DPrintf ('Game rejected a connection.'#10);
exit;
end;
// parse some info from the info strings
strncpy (newcl^.userinfo, userinfo, sizeof(newcl^.userinfo)-1);
SV_UserinfoChanged (newcl);
// send the connect packet to the client
Netchan_OutOfBandPrint (NS_SERVER, adr, 'client_connect', []);
Netchan_Setup (NS_SERVER, @newcl^.netchan , adr, qport);
newcl^.state := cs_connected;
SZ_Init (newcl^.datagram, @newcl^.datagram_buf, sizeof(newcl^.datagram_buf) );
newcl^.datagram.allowoverflow := true;
newcl^.lastmessage := svs.realtime; // don't timeout
newcl^.lastconnect := svs.realtime;
end;
function Rcon_Validate (): integer;
begin
if (strlen (rcon_password^.string_)=0) then begin
Result := 0;
exit;
end;
if (strcmp (Cmd_Argv(1), rcon_password^.string_)<>0) then begin
Result := 0;
exit;
end;
Result := 1;
end;
(*
===============
SVC_RemoteCommand
A client issued an rcon command.
Shift down the remaining args
Redirect all printfs
===============
*)
procedure SVC_RemoteCommand ();
var
i: integer;
remaining: array[0..1024-1] of char;
begin
i := Rcon_Validate ();
if (i = 0) then
Com_Printf ('Bad rcon from %s:'#10'%s'#10, [NET_AdrToString (net_from), PChar(Pointer(Cardinal(net_message.data)+4))])
else
Com_Printf ('Rcon from %s:'#10'%s'#10, [NET_AdrToString (net_from), PChar(Pointer(Cardinal(net_message.data)+4))]);
Com_BeginRedirect (Integer(RD_PACKET), sv_outputbuf, SV_OUTPUTBUF_LENGTH, SV_FlushRedirect);
if (Rcon_Validate ()=0) then begin
Com_Printf ('Bad rcon_password.'#10);
end
else
begin
remaining[0] := #0;
for i := 2 to Cmd_Argc()-1 do begin
strcat (remaining, Cmd_Argv(i) );
strcat (remaining, ' ');
end;
Cmd_ExecuteString (remaining);
end;
Com_EndRedirect ();
end;
(*
=================
SV_ConnectionlessPacket
A connectionless packet has four leading $ff
characters to distinguish it from a game channel.
Clients that are in the game can still send
connectionless packets.
=================
*)
procedure SV_ConnectionlessPacket ();
var
s: pchar;
c: pchar;
begin
MSG_BeginReading (net_message);
MSG_ReadLong (net_message); // skip the -1 marker
s := MSG_ReadStringLine (net_message);
Cmd_TokenizeString (s, false);
c := Cmd_Argv(0);
Com_DPrintf ('Packet %s : %s'#10, [NET_AdrToString(net_from), c]);
if (strcmp(c, 'ping') = 0) then
SVC_Ping ()
else if (strcmp(c, 'ack') = 0) then
SVC_Ack ()
else if (strcmp(c, 'status') = 0) then
SVC_Status ()
else if (strcmp(c, 'info') = 0) then
SVC_Info ()
else if (strcmp(c, 'getchallenge') = 0) then
SVC_GetChallenge ()
else if (strcmp(c, 'connect') = 0) then
SVC_DirectConnect ()
else if (strcmp(c, 'rcon') = 0) then
SVC_RemoteCommand ()
else
Com_Printf ('bad connectionless packet from %s:'#10'%s'#10
, [NET_AdrToString (net_from), s]);
end;
//============================================================================
(*
===================
SV_CalcPings
Updates the cl^.ping variables
===================
*)
procedure SV_CalcPings ();
var
i, j: integer;
cl: client_p;
total, count: integer;
begin
for i := 0 to round(maxclients^.value)-1 do begin
cl := @svs.clients^[i];
if (cl^.state <> cs_spawned ) then
continue;
{$ifdef neverever}
if (cl^.lastframe > 0)
cl^.frame_latency[sv.framenum&(LATENCY_COUNTS-1)] = sv.framenum - cl^.lastframe + 1;
else
cl^.frame_latency[sv.framenum&(LATENCY_COUNTS-1)] = 0;
{$endif}
total := 0;
count := 0;
for j := 0 to LATENCY_COUNTS-1 do begin
if (cl^.frame_latency[j] > 0) then begin
Inc(count);
total := total + cl^.frame_latency[j];
end;
end;
if (count = 0) then
cl^.ping := 0
else
{$ifdef neverever}
cl^.ping := total*100/count - 100;
{$else}
cl^.ping := total div count;
{$endif}
// let the game dll know about the ping
cl^.edict^.client^.ping := cl^.ping;
end;
end;
(*
===================
SV_GiveMsec
Every few frames, gives all clients an allotment of milliseconds
for their command moves. If they exceed it, assume cheating.
===================
*)
procedure SV_GiveMsec ();
var
i: integer;
cl: client_p;
begin
if (sv.framenum and 15 <> 0) then
exit;
for i := 0 to Round(maxclients^.value)-1 do begin
cl := @svs.clients^[i];
if (cl^.state = cs_free ) then
continue;
cl^.commandMsec := 1800; // 1600 + some slop
end;
end;
(*
=================
SV_ReadPackets
=================
*)
procedure SV_ReadPackets ();
var
i: integer;
cl: client_p;
qport: integer;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -