X-Git-Url: http://deadsoftware.ru/gitweb?p=d2df-sdl.git;a=blobdiff_plain;f=src%2Fgame%2Fg_net.pas;h=edd6024a49af09237130e98d105a0d3d0948efb9;hp=d3e67cfd6ef34c4ad3fdfc9cc0ed8ab1c1f6b2f4;hb=4204edd3c7df01198a2289af4896be0575fff15c;hpb=bdb5b5ae754a9579cb457267ad4ce496bd606389 diff --git a/src/game/g_net.pas b/src/game/g_net.pas index d3e67cf..edd6024 100644 --- a/src/game/g_net.pas +++ b/src/game/g_net.pas @@ -18,10 +18,10 @@ unit g_net; interface uses - e_log, e_msg, ENet, Classes, md5, MAPDEF{$IFDEF USE_MINIUPNPC}, miniupnpc;{$ELSE};{$ENDIF} + e_log, e_msg, utils, ENet, Classes, md5, MAPDEF{$IFDEF USE_MINIUPNPC}, miniupnpc;{$ELSE};{$ENDIF} const - NET_PROTOCOL_VER = 182; + NET_PROTOCOL_VER = 188; NET_MAXCLIENTS = 24; NET_CHANS = 12; @@ -72,13 +72,15 @@ const BANLIST_FILENAME = 'banlist.txt'; NETDUMP_FILENAME = 'netdump'; - {$IFDEF FREEBSD} - NilThreadId = nil; - {$ELSE} - NilThreadId = 0; - {$ENDIF} - type + TNetMapResourceInfo = record + wadName: AnsiString; // wad file name, without a path + size: Integer; // wad file size (-1: size and hash are not known) + hash: TMD5Digest; // wad hash + end; + + TNetMapResourceInfoArray = array of TNetMapResourceInfo; + TNetFileTransfer = record diskName: string; hash: TMD5Digest; @@ -100,8 +102,12 @@ type Peer: pENetPeer; Player: Word; RequestedFullUpdate: Boolean; + WaitForFirstSpawn: Boolean; // set to `true` in server, used to spawn a player on first full state request RCONAuth: Boolean; Voted: Boolean; + Crimes: Integer; + AuthTime: LongWord; + MsgTime: LongWord; Transfer: TNetFileTransfer; // only one transfer may be active NetOut: array [0..1] of TMsg; end; @@ -138,9 +144,7 @@ var NetPongSock: ENetSocket = ENET_SOCKET_NULL; NetUseMaster: Boolean = True; - NetSlistAddr: ENetAddress; - NetSlistIP: string = 'mpms.doom2d.org'; - NetSlistPort: Word = 25665; + NetMasterList: string = 'mpms.doom2d.org:25665, deadsoftware.ru:25665'; NetClientIP: string = '127.0.0.1'; NetClientPort: Word = 25666; @@ -153,6 +157,13 @@ var NetMaxClients: Byte = 255; NetBannedHosts: array of TBanRecord; + NetAutoBanLimit: Integer = 5; + NetAutoBanPerm: Boolean = True; + NetAutoBanWarn: Boolean = False; + + NetAuthTimeout: Integer = 15 * 1000; + NetPacketTimeout: Integer = 30 * 1000; + NetState: Integer = NET_STATE_NONE; NetMyID: Integer = -1; @@ -171,6 +182,8 @@ var NetGotEverything: Boolean = False; NetGotKeys: Boolean = False; + NetDeafLevel: Integer = 0; + {$IFDEF USE_MINIUPNPC} NetPortForwarded: Word = 0; NetPongForwarded: Boolean = False; @@ -220,6 +233,8 @@ function g_Net_UnbanHost(IP: LongWord): Boolean; overload; procedure g_Net_UnbanNonPermHosts(); procedure g_Net_SaveBanList(); +procedure g_Net_Penalize(C: pTNetClient; Reason: string); + procedure g_Net_DumpStart(); procedure g_Net_DumpSendBuffer(); procedure g_Net_DumpRecvBuffer(Buf: penet_uint8; Len: LongWord); @@ -230,28 +245,64 @@ procedure g_Net_UnforwardPorts(); function g_Net_UserRequestExit: Boolean; -function g_Net_Wait_MapInfo (var tf: TNetFileTransfer; resList: TStringList): Integer; +function g_Net_Wait_MapInfo (var tf: TNetFileTransfer; var resList: TNetMapResourceInfoArray): Integer; function g_Net_RequestResFileInfo (resIndex: LongInt; out tf: TNetFileTransfer): Integer; function g_Net_AbortResTransfer (var tf: TNetFileTransfer): Boolean; function g_Net_ReceiveResourceFile (resIndex: LongInt; var tf: TNetFileTransfer; strm: TStream): Integer; +function g_Net_IsNetworkAvailable (): Boolean; +procedure g_Net_InitLowLevel (); +procedure g_Net_DeinitLowLevel (); + +procedure NetServerCVars(P: SSArray); + implementation +// *enet_host_service()* +// fuck! https://www.mail-archive.com/enet-discuss@cubik.org/msg00852.html +// tl;dr: on shitdows, we can get -1 sometimes, and it is *NOT* a failure. +// thank you, enet. let's ignore failures altogether then. + uses SysUtils, - e_input, g_nethandler, g_netmsg, g_netmaster, g_player, g_window, g_console, - g_main, g_game, g_language, g_weapons, utils, ctypes, - g_map; + e_input, e_res, + g_nethandler, g_netmsg, g_netmaster, g_player, g_window, g_console, + g_main, g_game, g_language, g_weapons, ctypes, g_system, g_map; const FILE_CHUNK_SIZE = 8192; var + enet_init_success: Boolean = false; g_Net_DownloadTimeout: Single; trans_omsg: TMsg; +function g_Net_IsNetworkAvailable (): Boolean; +begin + result := enet_init_success; +end; + +procedure g_Net_InitLowLevel (); + var v: ENetVersion; +begin + v := enet_linked_version(); + e_LogWritefln('ENet Version: %s.%s.%s', [ENET_VERSION_GET_MAJOR(v), ENET_VERSION_GET_MINOR(v), ENET_VERSION_GET_PATCH(v)]); + if enet_init_success then raise Exception.Create('wuta?!'); + enet_init_success := (enet_initialize() = 0); +end; + +procedure g_Net_DeinitLowLevel (); +begin + if enet_init_success then + begin + enet_deinitialize(); + enet_init_success := false; + end; +end; + + //************************************************************************** // // SERVICE FUNCTIONS @@ -294,7 +345,6 @@ begin e_KeyPressed(JOY3_JUMP) end; - //************************************************************************** // // file transfer declaraions and host packet processor @@ -323,6 +373,7 @@ begin e_LogWritefln('disconnected client #%d due to file transfer error', [nc.ID], TMsgType.Warning); enet_peer_disconnect(nc.Peer, NET_DISC_FILE_TIMEOUT); clearNetClientTransfers(nc); + g_Net_Slist_ServerPlayerLeaves(); end; @@ -433,8 +484,9 @@ var ridx: Integer; dfn: AnsiString; md5: TMD5Digest; - st: TStream; + //st: TStream; size: LongInt; + fi: TDiskFileInfo; begin // find client index by peer for f := Low(NetClients) to High(NetClients) do @@ -457,6 +509,12 @@ begin exit; end; + // don't time out clients during a file transfer + if (NetAuthTimeout > 0) then + nc^.AuthTime := gTime + NetAuthTimeout; + if (NetPacketTimeout > 0) then + nc^.MsgTime := gTime + NetPacketTimeout; + tf := @NetClients[nid].Transfer; tf.lastAckTime := GetTimerMS(); @@ -483,13 +541,13 @@ begin end; // get resource index ridx := msg.ReadLongInt(); - if (ridx < -1) or (ridx >= gExternalResources.Count) then + if (ridx < -1) or (ridx >= length(gExternalResources)) then begin e_LogWritefln('Invalid resource index %d', [ridx], TMsgType.Warning); killClientByFT(nc^); exit; end; - if (ridx < 0) then fname := MapsDir+gGameSettings.WAD else fname := GameDir+'/wads/'+gExternalResources[ridx]; + if (ridx < 0) then fname := gGameSettings.WAD else fname := gExternalResources[ridx].diskName; if (length(fname) = 0) then begin e_WriteLog('Invalid filename: '+fname, TMsgType.Warning); @@ -497,7 +555,6 @@ begin exit; end; tf.diskName := findDiskWad(fname); - //if (length(tf.diskName) = 0) then tf.diskName := findDiskWad(GameDir+'/wads/'+fname); if (length(tf.diskName) = 0) then begin e_LogWritefln('NETWORK: file "%s" not found!', [fname], TMsgType.Fatal); @@ -505,8 +562,8 @@ begin exit; end; // calculate hash - //TODO: cache hashes - tf.hash := MD5File(tf.diskName); + //tf.hash := MD5File(tf.diskName); + if (ridx < 0) then tf.hash := gWADHash else tf.hash := gExternalResources[ridx].hash; // create file stream tf.diskName := findDiskWad(fname); try @@ -619,27 +676,43 @@ begin begin e_LogWritefln('client #%d requested map info', [nc.ID]); trans_omsg.Clear(); - dfn := findDiskWad(MapsDir+gGameSettings.WAD); + dfn := findDiskWad(gGameSettings.WAD); if (dfn = '') then dfn := '!wad_not_found!.wad'; //FIXME - md5 := MD5File(dfn); + //md5 := MD5File(dfn); + md5 := gWADHash; + if (not GetDiskFileInfo(dfn, fi)) then + begin + e_LogWritefln('client #%d requested map info, but i cannot get file info', [nc.ID]); + killClientByFT(nc^); + exit; + end; + size := fi.size; + { st := openDiskFileRO(dfn); if not assigned(st) then exit; //wtf?! size := st.size; st.Free; + } // packet type trans_omsg.Write(Byte(NTF_SERVER_MAP_INFO)); // map wad name - trans_omsg.Write(gGameSettings.WAD); + trans_omsg.Write(ExtractFileName(gGameSettings.WAD)); // map wad md5 trans_omsg.Write(md5); // map wad size trans_omsg.Write(size); // number of external resources for map - trans_omsg.Write(LongInt(gExternalResources.Count)); + trans_omsg.Write(LongInt(length(gExternalResources))); // external resource names - for f := 0 to gExternalResources.Count-1 do + for f := 0 to High(gExternalResources) do begin - trans_omsg.Write(ExtractFileName(gExternalResources[f])); // GameDir+'/wads/'+ResList.Strings[i] + // old style packet + //trans_omsg.Write(ExtractFileName(gExternalResources[f])); // GameDir+'/wads/'+ResList.Strings[i] + // new style packet + trans_omsg.Write('!'); + trans_omsg.Write(LongInt(gExternalResources[f].size)); + trans_omsg.Write(gExternalResources[f].hash); + trans_omsg.Write(ExtractFileName(gExternalResources[f].diskName)); end; // send packet if not ftransSendServerMsg(nc^, trans_omsg) then exit; @@ -677,16 +750,18 @@ end; // // returns `false` on error or user abort // fills: -// hash -// size -// chunkSize +// diskName: map wad file name (without a path) +// hash: map wad hash +// size: map wad size +// chunkSize: set too +// resList: list of resource wads // returns: // <0 on error // 0 on success // 1 on user abort // 2 on server abort // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info -function g_Net_Wait_MapInfo (var tf: TNetFileTransfer; resList: TStringList): Integer; +function g_Net_Wait_MapInfo (var tf: TNetFileTransfer; var resList: TNetMapResourceInfoArray): Integer; var ev: ENetEvent; rMsgId: Byte; @@ -697,7 +772,10 @@ var status: cint; s: AnsiString; rc, f: LongInt; + ri: ^TNetMapResourceInfo; begin + SetLength(resList, 0); + // send request trans_omsg.Clear(); trans_omsg.Write(Byte(NTF_CLIENT_MAP_REQUEST)); @@ -709,13 +787,15 @@ begin ett := getNewTimeoutEnd(); repeat status := enet_host_service(NetHost, @ev, 300); + { if (status < 0) then begin g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True); Result := -1; exit; end; - if (status = 0) then + } + if (status <= 0) then begin // check for timeout ct := GetTimerMS(); @@ -769,7 +849,7 @@ begin e_LogWritefln('g_Net_Wait_MapInfo: creating map info packet...', []); if not msg.Init(ev.packet^.data+1, ev.packet^.dataLength-1, True) then exit; e_LogWritefln('g_Net_Wait_MapInfo: parsing map info packet (rd=%d; max=%d)...', [msg.ReadCount, msg.MaxSize]); - resList.Clear(); + SetLength(resList, 0); // just in case // map wad name tf.diskName := msg.ReadString(); e_LogWritefln('g_Net_Wait_MapInfo: map wad is `%s`', [tf.diskName]); @@ -787,16 +867,28 @@ begin exit; end; e_LogWritefln('g_Net_Wait_MapInfo: map external resource count is %d', [rc]); + SetLength(resList, rc); // external resource names for f := 0 to rc-1 do begin - s := ExtractFileName(msg.ReadString()); - if (length(s) = 0) then + ri := @resList[f]; + s := msg.ReadString(); + if (length(s) = 0) then begin result := -1; exit; end; + if (s = '!') then + begin + // extended packet + ri.size := msg.ReadLongInt(); + ri.hash := msg.ReadMD5(); + ri.wadName := ExtractFileName(msg.ReadString()); + if (length(ri.wadName) = 0) or (ri.size < 0) then begin result := -1; exit; end; + end + else begin - Result := -1; - exit; + // old-style packet, only name + ri.wadName := ExtractFileName(s); + if (length(ri.wadName) = 0) then begin result := -1; exit; end; + ri.size := -1; // unknown end; - resList.append(s); end; e_LogWritefln('g_Net_Wait_MapInfo: got map info', []); Result := 0; // success @@ -876,13 +968,15 @@ begin ett := getNewTimeoutEnd(); repeat status := enet_host_service(NetHost, @ev, 300); + { if (status < 0) then begin g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True); Result := -1; exit; end; - if (status = 0) then + } + if (status <= 0) then begin // check for timeout ct := GetTimerMS(); @@ -1058,13 +1152,15 @@ begin repeat //stx := -GetTimerMS(); status := enet_host_service(NetHost, @ev, 300); + { if (status < 0) then begin g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True); Result := -1; exit; end; - if (status = 0) then + } + if (status <= 0) then begin // check for timeout ct := GetTimerMS(); @@ -1232,6 +1328,7 @@ begin NetClients[N].Used := True; NetClients[N].ID := N; NetClients[N].RequestedFullUpdate := False; + NetClients[N].WaitForFirstSpawn := False; NetClients[N].RCONAuth := False; NetClients[N].Voted := False; NetClients[N].Player := 0; @@ -1241,11 +1338,13 @@ begin Result := N; end; + function g_Net_Init(): Boolean; var F: TextFile; IPstr: string; IP: LongWord; + path: AnsiString; begin NetIn.Clear(); NetOut.Clear(); @@ -1260,9 +1359,10 @@ begin NetPlrUID2 := -1; NetAddr.port := 25666; SetLength(NetBannedHosts, 0); - if FileExists(DataDir + BANLIST_FILENAME) then + path := BANLIST_FILENAME; + if e_FindResource(DataDirs, path) = true then begin - Assign(F, DataDir + BANLIST_FILENAME); + Assign(F, path); Reset(F); while not EOF(F) do begin @@ -1274,7 +1374,8 @@ begin g_Net_SaveBanList(); end; - Result := (enet_initialize() = 0); + //Result := (enet_initialize() = 0); + Result := enet_init_success; end; procedure g_Net_Flush(); @@ -1341,8 +1442,7 @@ begin NetPeer := nil; NetHost := nil; - NetMPeer := nil; - NetMHost := nil; + g_Net_Slist_ServerClosed(); NetMyID := -1; NetPlrUID1 := -1; NetPlrUID2 := -1; @@ -1370,7 +1470,7 @@ procedure g_Net_Free(); begin g_Net_Cleanup(); - enet_deinitialize(); + //enet_deinitialize(); NetInitDone := False; end; @@ -1414,8 +1514,6 @@ begin NetAddr.host := IPAddr; NetAddr.port := Port; - if NetForwardPorts then NetPortThread := BeginThread(ForwardThread); - NetHost := enet_host_create(@NetAddr, NET_MAXCLIENTS, NET_CHANS, 0, 0); if (NetHost = nil) then @@ -1426,6 +1524,8 @@ begin Exit; end; + if NetForwardPorts then NetPortThread := BeginThread(ForwardThread); + NetPongSock := enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM); if NetPongSock <> ENET_SOCKET_NULL then begin @@ -1472,12 +1572,16 @@ begin enet_peer_reset(NetClients[I].Peer); NetClients[I].Peer := nil; NetClients[I].Used := False; + NetClients[I].Player := 0; + NetClients[I].Crimes := 0; + NetClients[I].AuthTime := 0; + NetClients[I].MsgTime := 0; NetClients[I].NetOut[NET_UNRELIABLE].Free(); NetClients[I].NetOut[NET_RELIABLE].Free(); end; clearNetClients(false); // don't clear array - if (NetMPeer <> nil) and (NetMHost <> nil) then g_Net_Slist_Disconnect; + g_Net_Slist_ServerClosed(); if NetPongSock <> ENET_SOCKET_NULL then enet_socket_destroy(NetPongSock); @@ -1528,7 +1632,7 @@ var Ping: array [0..9] of Byte; NPl: Byte; begin - if NetPongSock = ENET_SOCKET_NULL then Exit; + if (NetPongSock = ENET_SOCKET_NULL) or (NetHost = nil) then Exit; Buf.data := Addr(Ping[0]); Buf.dataLength := 2+8; @@ -1545,9 +1649,9 @@ begin NetOut.Clear(); NetOut.Write(Byte(Ord('D'))); NetOut.Write(Byte(Ord('F'))); - NetOut.Write(NetPort); + NetOut.Write(NetHost.address.port); NetOut.Write(ClTime); - g_Net_Slist_WriteInfo(); + TMasterHost.writeInfo(NetOut); NPl := 0; if gPlayer1 <> nil then Inc(NPl); if gPlayer2 <> nil then Inc(NPl); @@ -1562,6 +1666,77 @@ begin end; end; +procedure g_Net_Host_CheckTimeouts(); +var + ID: Integer; +begin + for ID := Low(NetClients) to High(NetClients) do + begin + with NetClients[ID] do + begin + if (Peer = nil) or (State = NET_STATE_NONE) then continue; + if (State = NET_STATE_AUTH) and (AuthTime > 0) and (AuthTime <= gTime) then + begin + g_Net_Penalize(@NetClients[ID], 'auth taking too long'); + AuthTime := gTime + 500; // do it twice a second to give them a chance + end + else if (State = NET_STATE_GAME) and (MsgTime > 0) and (MsgTime <= gTime) then + begin + g_Net_Penalize(@NetClients[ID], 'message timeout'); + AuthTime := gTime + 500; // do it twice a second to give them a chance + end; + end; + end; +end; + +procedure g_Net_Host_Disconnect_Client(ID: Integer; Force: Boolean = False); +var + TP: TPlayer; + TC: pTNetClient; +begin + TC := @NetClients[ID]; + if (TC = nil) then Exit; + clearNetClient(NetClients[ID]); + if not (TC^.Used) then Exit; + + TP := g_Player_Get(TC^.Player); + + if TP <> nil then + begin + TP.Lives := 0; + TP.Kill(K_SIMPLEKILL, 0, HIT_DISCON); + g_Console_Add(Format(_lc[I_PLAYER_LEAVE], [TP.Name]), True); + e_WriteLog('NET: Client ' + TP.Name + ' [' + IntToStr(TC^.ID) + '] disconnected.', TMsgType.Notify); + g_Player_Remove(TP.UID); + end; + + if (TC^.Peer^.data <> nil) then + begin + FreeMemory(TC^.Peer^.data); + TC^.Peer^.data := nil; + end; + + if (Force) then + enet_peer_reset(TC^.Peer); + + TC^.Used := False; + TC^.State := NET_STATE_NONE; + TC^.Peer := nil; + TC^.Player := 0; + TC^.Crimes := 0; + TC^.AuthTime := 0; + TC^.MsgTime := 0; + TC^.RequestedFullUpdate := False; + TC^.WaitForFirstSpawn := False; + TC^.NetOut[NET_UNRELIABLE].Free(); + TC^.NetOut[NET_RELIABLE].Free(); + + g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_HOST_DISC], [ID])); + Dec(NetClientCount); + + if NetUseMaster then g_Net_Slist_ServerPlayerLeaves(); +end; + function g_Net_Host_Update(): enet_size_t; var @@ -1569,13 +1744,13 @@ var Port: Word; ID: Integer; TC: pTNetClient; - TP: TPlayer; begin IP := ''; Result := 0; - if NetUseMaster then g_Net_Slist_Check; - g_Net_Host_CheckPings; + if NetUseMaster then g_Net_Slist_Pulse(); + g_Net_Host_CheckPings(); + g_Net_Host_CheckTimeouts(); while (enet_host_service(NetHost, @NetEvent, 0) > 0) do begin @@ -1586,11 +1761,14 @@ begin Port := NetEvent.Peer^.address.port; g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_HOST_CONN], [IP, Port])); + e_WriteLog('NET: Connection request from ' + IP + '.', TMsgType.Notify); if (NetEvent.data <> NET_PROTOCOL_VER) then begin g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_REJECT] + _lc[I_NET_DISC_PROTOCOL]); + e_WriteLog('NET: Connection request from ' + IP + ' rejected: version mismatch', + TMsgType.Notify); NetEvent.peer^.data := GetMemory(SizeOf(Byte)); Byte(NetEvent.peer^.data^) := 255; enet_peer_disconnect(NetEvent.peer, NET_DISC_PROTOCOL); @@ -1598,12 +1776,27 @@ begin Exit; end; + if g_Net_IsHostBanned(NetEvent.Peer^.address.host) then + begin + g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_REJECT] + + _lc[I_NET_DISC_BAN]); + e_WriteLog('NET: Connection request from ' + IP + ' rejected: banned', + TMsgType.Notify); + NetEvent.peer^.data := GetMemory(SizeOf(Byte)); + Byte(NetEvent.peer^.data^) := 255; + enet_peer_disconnect(NetEvent.Peer, NET_DISC_BAN); + enet_host_flush(NetHost); + Exit; + end; + ID := g_Net_FindSlot(); if ID < 0 then begin g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_REJECT] + _lc[I_NET_DISC_FULL]); + e_WriteLog('NET: Connection request from ' + IP + ' rejected: server full', + TMsgType.Notify); NetEvent.Peer^.data := GetMemory(SizeOf(Byte)); Byte(NetEvent.peer^.data^) := 255; enet_peer_disconnect(NetEvent.peer, NET_DISC_FULL); @@ -1615,9 +1808,19 @@ begin NetClients[ID].Peer^.data := GetMemory(SizeOf(Byte)); Byte(NetClients[ID].Peer^.data^) := ID; NetClients[ID].State := NET_STATE_AUTH; + NetClients[ID].Player := 0; + NetClients[ID].Crimes := 0; NetClients[ID].RCONAuth := False; NetClients[ID].NetOut[NET_UNRELIABLE].Alloc(NET_BUFSIZE*2); NetClients[ID].NetOut[NET_RELIABLE].Alloc(NET_BUFSIZE*2); + if (NetAuthTimeout > 0) then + NetClients[ID].AuthTime := gTime + NetAuthTimeout + else + NetClients[ID].AuthTime := 0; + if (NetPacketTimeout > 0) then + NetClients[ID].MsgTime := gTime + NetPacketTimeout + else + NetClients[ID].MsgTime := 0; clearNetClientTransfers(NetClients[ID]); // just in case enet_peer_timeout(NetEvent.peer, ENET_PEER_TIMEOUT_LIMIT * 2, ENET_PEER_TIMEOUT_MINIMUM * 2, ENET_PEER_TIMEOUT_MAXIMUM * 2); @@ -1639,6 +1842,9 @@ begin if ID > High(NetClients) then Exit; TC := @NetClients[ID]; + if (NetPacketTimeout > 0) then + TC^.MsgTime := gTime + NetPacketTimeout; + if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength); g_Net_Host_HandlePacket(TC, NetEvent.packet, g_Net_HostMsgHandler); end; @@ -1648,37 +1854,7 @@ begin begin ID := Byte(NetEvent.peer^.data^); if ID > High(NetClients) then Exit; - clearNetClient(NetClients[ID]); - TC := @NetClients[ID]; - if TC = nil then Exit; - - if not (TC^.Used) then Exit; - - TP := g_Player_Get(TC^.Player); - - if TP <> nil then - begin - TP.Lives := 0; - TP.Kill(K_SIMPLEKILL, 0, HIT_DISCON); - g_Console_Add(Format(_lc[I_PLAYER_LEAVE], [TP.Name]), True); - e_WriteLog('NET: Client ' + TP.Name + ' [' + IntToStr(ID) + '] disconnected.', TMsgType.Notify); - g_Player_Remove(TP.UID); - end; - - TC^.Used := False; - TC^.State := NET_STATE_NONE; - TC^.Peer := nil; - TC^.Player := 0; - TC^.RequestedFullUpdate := False; - TC^.NetOut[NET_UNRELIABLE].Free(); - TC^.NetOut[NET_RELIABLE].Free(); - - FreeMemory(NetEvent.peer^.data); - NetEvent.peer^.data := nil; - g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_HOST_DISC], [ID])); - Dec(NetClientCount); - - if NetUseMaster then g_Net_Slist_Update; + g_Net_Host_Disconnect_Client(ID); end; end; end; @@ -1758,7 +1934,7 @@ end; function g_Net_Client_Update(): enet_size_t; begin Result := 0; - while (enet_host_service(NetHost, @NetEvent, 0) > 0) do + while (NetHost <> nil) and (enet_host_service(NetHost, @NetEvent, 0) > 0) do begin case NetEvent.kind of ENET_EVENT_TYPE_RECEIVE: @@ -1855,7 +2031,7 @@ begin end; // предупредить что ждем слишком долго через N секунд - TimeoutTime := GetTimer() + NET_CONNECT_TIMEOUT; + TimeoutTime := sys_GetTicks() + NET_CONNECT_TIMEOUT; OuterLoop := True; while OuterLoop do @@ -1876,7 +2052,7 @@ begin end; end; - T := GetTimer(); + T := sys_GetTicks(); if T > TimeoutTime then begin TimeoutTime := T + NET_CONNECT_TIMEOUT * 100; // одного предупреждения хватит @@ -2100,22 +2276,62 @@ procedure g_Net_SaveBanList(); var F: TextFile; I: Integer; + path: AnsiString; +begin + path := e_GetWriteableDir(DataDirs); + if path <> '' then + begin + path := e_CatPath(path, BANLIST_FILENAME); + Assign(F, path); + Rewrite(F); + if NetBannedHosts <> nil then + for I := 0 to High(NetBannedHosts) do + if NetBannedHosts[I].Perm and (NetBannedHosts[I].IP > 0) then + Writeln(F, IpToStr(NetBannedHosts[I].IP)); + CloseFile(F) + end +end; + +procedure g_Net_Penalize(C: pTNetClient; Reason: string); +var + s: string; begin - Assign(F, DataDir + BANLIST_FILENAME); - Rewrite(F); - if NetBannedHosts <> nil then - for I := 0 to High(NetBannedHosts) do - if NetBannedHosts[I].Perm and (NetBannedHosts[I].IP > 0) then - Writeln(F, IpToStr(NetBannedHosts[I].IP)); - CloseFile(F); + e_LogWritefln('NET: client #%u (cid #%u) triggered a penalty (%d/%d): %s', + [C^.ID, C^.Player, C^.Crimes + 1, NetAutoBanLimit, Reason]); + + if (NetAutoBanLimit <= 0) then Exit; + + if (C^.Crimes >= NetAutoBanLimit) then + begin + // we have tried asking nicely before, now it is time to die + e_LogWritefln('NET: client #%u (cid #%u) force kicked', + [C^.ID, C^.Player]); + g_Net_Host_Disconnect_Client(C^.ID, True); + Exit; + end; + + Inc(C^.Crimes); + + if (NetAutoBanWarn) then + MH_SEND_Chat('You have been warned by the server: ' + Reason, NET_CHAT_SYSTEM, C^.ID); + + if (C^.Crimes >= NetAutoBanLimit) then + begin + s := '#' + IntToStr(C^.ID); // can't be arsed + g_Net_BanHost(C^.Peer^.address.host, NetAutoBanPerm); + enet_peer_disconnect(C^.Peer, NET_DISC_BAN); + g_Console_Add(Format(_lc[I_PLAYER_BAN], [s])); + MH_SEND_GameEvent(NET_EV_PLAYER_BAN, 0, s); + g_Net_Slist_ServerPlayerLeaves(); + end; end; procedure g_Net_DumpStart(); begin if NetMode = NET_SERVER then - NetDumpFile := createDiskFile(NETDUMP_FILENAME + '_server') + NetDumpFile := e_CreateResource(LogDirs, NETDUMP_FILENAME + '_server') else - NetDumpFile := createDiskFile(NETDUMP_FILENAME + '_client'); + NetDumpFile := e_CreateResource(LogDirs, NETDUMP_FILENAME + '_client'); end; procedure g_Net_DumpSendBuffer(); @@ -2153,7 +2369,10 @@ var begin Result := False; - if NetPortForwarded = NetPort then + if NetHost = nil then + exit; + + if NetPortForwarded = NetHost.address.port then begin Result := True; exit; @@ -2179,7 +2398,7 @@ begin exit; end; - StrPort := IntToStr(NetPort); + StrPort := IntToStr(NetHost.address.port); I := UPNP_AddPortMapping( Urls.controlURL, Addr(data.first.servicetype[1]), PChar(StrPort), PChar(StrPort), Addr(LanAddr[0]), PChar('D2DF'), @@ -2188,7 +2407,7 @@ begin if I <> 0 then begin - conwritefln('forwarding port %d failed: error %d', [NetPort, I]); + conwritefln('forwarding port %d failed: error %d', [NetHost.address.port, I]); FreeUPNPDevList(DevList); FreeUPNPUrls(@Urls); exit; @@ -2205,20 +2424,20 @@ begin if I <> 0 then begin - conwritefln('forwarding port %d failed: error %d', [NetPort + 1, I]); + conwritefln('forwarding port %d failed: error %d', [NET_PING_PORT, I]); NetPongForwarded := False; end else begin - conwritefln('forwarded port %d successfully', [NetPort + 1]); + conwritefln('forwarded port %d successfully', [NET_PING_PORT]); NetPongForwarded := True; end; end; - conwritefln('forwarded port %d successfully', [NetPort]); + conwritefln('forwarded port %d successfully', [NetHost.address.port]); NetIGDControl := AnsiString(Urls.controlURL); NetIGDService := data.first.servicetype; - NetPortForwarded := NetPort; + NetPortForwarded := NetHost.address.port; FreeUPNPDevList(DevList); FreeUPNPUrls(@Urls); @@ -2250,12 +2469,12 @@ begin if NetPongForwarded then begin NetPongForwarded := False; - StrPort := IntToStr(NetPortForwarded + 1); + StrPort := IntToStr(NET_PING_PORT); I := UPNP_DeletePortMapping( PChar(NetIGDControl), Addr(NetIGDService[1]), PChar(StrPort), PChar('UDP'), nil ); - conwritefln(' port %d: %d', [NetPortForwarded + 1, I]); + conwritefln(' port %d: %d', [NET_PING_PORT, I]); end; NetPortForwarded := 0; @@ -2265,9 +2484,110 @@ begin end; {$ENDIF} +procedure NetServerCVars(P: SSArray); +var + cmd, s: string; + a, b: Integer; +begin + cmd := LowerCase(P[0]); + case cmd of + 'sv_name': + begin + if (Length(P) > 1) and (Length(P[1]) > 0) then + begin + NetServerName := P[1]; + if Length(NetServerName) > 64 then + SetLength(NetServerName, 64); + g_Net_Slist_ServerRenamed(); + end; + g_Console_Add(cmd + ' "' + NetServerName + '"'); + end; + 'sv_passwd': + begin + if (Length(P) > 1) and (Length(P[1]) > 0) then + begin + NetPassword := P[1]; + if Length(NetPassword) > 24 then + SetLength(NetPassword, 24); + g_Net_Slist_ServerRenamed(); + end; + g_Console_Add(cmd + ' "' + AnsiLowerCase(NetPassword) + '"'); + end; + 'sv_maxplrs': + begin + if (Length(P) > 1) then + begin + NetMaxClients := nclamp(StrToIntDef(P[1], NetMaxClients), 1, NET_MAXCLIENTS); + if g_Game_IsServer and g_Game_IsNet then + begin + b := 0; + for a := 0 to High(NetClients) do + begin + if NetClients[a].Used then + begin + Inc(b); + if b > NetMaxClients then + begin + s := g_Player_Get(NetClients[a].Player).Name; + enet_peer_disconnect(NetClients[a].Peer, NET_DISC_FULL); + g_Console_Add(Format(_lc[I_PLAYER_KICK], [s])); + MH_SEND_GameEvent(NET_EV_PLAYER_KICK, 0, s); + end; + end; + end; + g_Net_Slist_ServerRenamed(); + end; + end; + g_Console_Add(cmd + ' ' + IntToStr(NetMaxClients)); + end; + 'sv_public': + begin + if (Length(P) > 1) then + begin + NetUseMaster := StrToIntDef(P[1], Byte(NetUseMaster)) <> 0; + if NetUseMaster then g_Net_Slist_Public() else g_Net_Slist_Private(); + end; + g_Console_Add(cmd + ' ' + IntToStr(Byte(NetUseMaster))); + end; + 'sv_port': + begin + if (Length(P) > 1) then + begin + if not g_Game_IsNet then + NetPort := nclamp(StrToIntDef(P[1], NetPort), 0, $FFFF) + else + g_Console_Add(_lc[I_MSG_NOT_NETGAME]); + end; + g_Console_Add(cmd + ' ' + IntToStr(Ord(NetUseMaster))); + end; + end; +end; initialization conRegVar('cl_downloadtimeout', @g_Net_DownloadTimeout, 0.0, 1000000.0, '', 'timeout in seconds, 0 to disable it'); + conRegVar('cl_predictself', @NetPredictSelf, '', 'predict local player'); + conRegVar('cl_forceplayerupdate', @NetForcePlayerUpdate, '', 'update net players on NET_MSG_PLRPOS'); + conRegVar('cl_interp', @NetInterpLevel, '', 'net player interpolation steps'); + conRegVar('cl_last_ip', @NetClientIP, '', 'address of the last you have connected to'); + conRegVar('cl_last_port', @NetClientPort, '', 'port of the last server you have connected to'); + conRegVar('cl_deafen', @NetDeafLevel, '', 'filter server messages (0-3)'); + + conRegVar('sv_forwardports', @NetForwardPorts, '', 'forward server port using miniupnpc (requires server restart)'); + conRegVar('sv_rcon', @NetAllowRCON, '', 'enable remote console'); + conRegVar('sv_rcon_password', @NetRCONPassword, '', 'remote console password'); + conRegVar('sv_update_interval', @NetUpdateRate, '', 'unreliable update interval'); + conRegVar('sv_reliable_interval', @NetRelupdRate, '', 'reliable update interval'); + conRegVar('sv_master_interval', @NetMasterRate, '', 'master server update interval'); + + conRegVar('sv_autoban_threshold', @NetAutoBanLimit, '', 'max crimes before autoban (0 = no autoban)'); + conRegVar('sv_autoban_permanent', @NetAutoBanPerm, '', 'whether autobans are permanent'); + conRegVar('sv_autoban_warn', @NetAutoBanWarn, '', 'send warnings to the client when he triggers penalties'); + + conRegVar('sv_auth_timeout', @NetAuthTimeout, '', 'number of msec in which connecting clients must complete auth (0 = unlimited)'); + conRegVar('sv_packet_timeout', @NetPacketTimeout, '', 'number of msec the client must idle to be kicked (0 = unlimited)'); + + conRegVar('net_master_list', @NetMasterList, '', 'list of master servers'); + SetLength(NetClients, 0); g_Net_DownloadTimeout := 60; NetIn.Alloc(NET_BUFSIZE);