1 (* Copyright (C) Doom 2D: Forever Developers
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, version 3 of the License ONLY.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 {$INCLUDE ../shared/a_modes.inc}
21 e_log
, e_msg
, utils
, ENet
, Classes
, md5
, MAPDEF
{$IFDEF USE_MINIUPNPC}, miniupnpc
;{$ELSE};{$ENDIF}
24 NET_PROTOCOL_VER
= 185;
30 NET_CHAN_IMPORTANT
= 1;
33 NET_CHAN_PLAYERPOS
= 4;
35 NET_CHAN_MONSTERPOS
= 6;
36 NET_CHAN_LARGEDATA
= 7;
38 NET_CHAN_DOWNLOAD
= 9;
40 NET_CHAN_DOWNLOAD_EX
= 11;
47 NET_PING_PORT
= $DF2D;
54 NET_DISC_NONE
: enet_uint32
= 0;
55 NET_DISC_PROTOCOL
: enet_uint32
= 1;
56 NET_DISC_VERSION
: enet_uint32
= 2;
57 NET_DISC_FULL
: enet_uint32
= 3;
58 NET_DISC_KICK
: enet_uint32
= 4;
59 NET_DISC_DOWN
: enet_uint32
= 5;
60 NET_DISC_PASSWORD
: enet_uint32
= 6;
61 NET_DISC_TEMPBAN
: enet_uint32
= 7;
62 NET_DISC_BAN
: enet_uint32
= 8;
63 NET_DISC_MAX
: enet_uint32
= 8;
64 NET_DISC_FILE_TIMEOUT
: enet_uint32
= 13;
70 NET_CONNECT_TIMEOUT
= 1000 * 10;
72 BANLIST_FILENAME
= 'banlist.txt';
73 NETDUMP_FILENAME
= 'netdump';
76 TNetMapResourceInfo
= record
77 wadName
: AnsiString; // wad file name, without a path
78 size
: Integer; // wad file size (-1: size and hash are not known)
79 hash
: TMD5Digest
; // wad hash
82 TNetMapResourceInfoArray
= array of TNetMapResourceInfo
;
84 TNetFileTransfer
= record
88 size
: Integer; // file size in bytes
90 lastSentChunk
: Integer;
91 lastAckChunk
: Integer;
92 lastAckTime
: Int64; // msecs; if not "in progress", we're waiting for the first ack
94 diskBuffer
: PChar; // of `chunkSize` bytes
104 RequestedFullUpdate
: Boolean;
105 WaitForFirstSpawn
: Boolean; // set to `true` in server, used to spawn a player on first full state request
108 Transfer
: TNetFileTransfer
; // only one transfer may be active
109 NetOut
: array [0..1] of TMsg
;
115 pTNetClient
= ^TNetClient
;
117 AByte
= array of Byte;
120 NetInitDone
: Boolean = False;
121 NetMode
: Byte = NET_NONE
;
122 NetDump
: Boolean = False;
124 NetServerName
: string = 'Unnamed Server';
125 NetPassword
: string = '';
126 NetPort
: Word = 25666;
128 NetAllowRCON
: Boolean = False;
129 NetRCONPassword
: string = '';
131 NetTimeToUpdate
: Cardinal = 0;
132 NetTimeToReliable
: Cardinal = 0;
133 NetTimeToMaster
: Cardinal = 0;
135 NetHost
: pENetHost
= nil;
136 NetPeer
: pENetPeer
= nil;
138 NetAddr
: ENetAddress
;
140 NetPongAddr
: ENetAddress
;
141 NetPongSock
: ENetSocket
= ENET_SOCKET_NULL
;
143 NetUseMaster
: Boolean = True;
144 NetMasterList
: string = 'mpms.doom2d.org:25665, deadsoftware.ru:25665';
146 NetClientIP
: string = '127.0.0.1';
147 NetClientPort
: Word = 25666;
150 NetBuf
: array [0..1] of TMsg
;
152 NetClients
: array of TNetClient
;
153 NetClientCount
: Byte = 0;
154 NetMaxClients
: Byte = 255;
155 NetBannedHosts
: array of TBanRecord
;
157 NetState
: Integer = NET_STATE_NONE
;
159 NetMyID
: Integer = -1;
160 NetPlrUID1
: Integer = -1;
161 NetPlrUID2
: Integer = -1;
163 NetInterpLevel
: Integer = 1;
164 NetUpdateRate
: Cardinal = 0; // as soon as possible
165 NetRelupdRate
: Cardinal = 18; // around two times a second
166 NetMasterRate
: Cardinal = 60000;
168 NetForcePlayerUpdate
: Boolean = False;
169 NetPredictSelf
: Boolean = True;
170 NetForwardPorts
: Boolean = False;
172 NetGotEverything
: Boolean = False;
173 NetGotKeys
: Boolean = False;
175 NetDeafLevel
: Integer = 0;
177 {$IFDEF USE_MINIUPNPC}
178 NetPortForwarded
: Word = 0;
179 NetPongForwarded
: Boolean = False;
180 NetIGDControl
: AnsiString;
181 NetIGDService
: TURLStr
;
184 NetPortThread
: TThreadID
= NilThreadId
;
186 NetDumpFile
: TStream
;
188 g_Res_received_map_start
: Integer = 0; // set if we received "map change" event
191 function g_Net_Init(): Boolean;
192 procedure g_Net_Cleanup();
193 procedure g_Net_Free();
194 procedure g_Net_Flush();
196 function g_Net_Host(IPAddr
: LongWord; Port
: enet_uint16
; MaxClients
: Cardinal = 16): Boolean;
197 procedure g_Net_Host_Die();
198 procedure g_Net_Host_Send(ID
: Integer; Reliable
: Boolean; Chan
: Byte = NET_CHAN_GAME
);
199 function g_Net_Host_Update(): enet_size_t
;
201 function g_Net_Connect(IP
: string; Port
: enet_uint16
): Boolean;
202 procedure g_Net_Disconnect(Forced
: Boolean = False);
203 procedure g_Net_Client_Send(Reliable
: Boolean; Chan
: Byte = NET_CHAN_GAME
);
204 function g_Net_Client_Update(): enet_size_t
;
205 function g_Net_Client_UpdateWhileLoading(): enet_size_t
;
207 function g_Net_Client_ByName(Name
: string): pTNetClient
;
208 function g_Net_Client_ByPlayer(PID
: Word): pTNetClient
;
209 function g_Net_ClientName_ByID(ID
: Integer): string;
211 procedure g_Net_SendData(Data
: AByte
; peer
: pENetPeer
; Reliable
: Boolean; Chan
: Byte = NET_CHAN_DOWNLOAD
);
212 //function g_Net_Wait_Event(msgId: Word): TMemoryStream;
213 //function g_Net_Wait_FileInfo (var tf: TNetFileTransfer; asMap: Boolean; out resList: TStringList): Integer;
215 function IpToStr(IP
: LongWord): string;
216 function StrToIp(IPstr
: string; var IP
: LongWord): Boolean;
218 function g_Net_IsHostBanned(IP
: LongWord; Perm
: Boolean = False): Boolean;
219 procedure g_Net_BanHost(IP
: LongWord; Perm
: Boolean = True); overload
;
220 procedure g_Net_BanHost(IP
: string; Perm
: Boolean = True); overload
;
221 function g_Net_UnbanHost(IP
: string): Boolean; overload
;
222 function g_Net_UnbanHost(IP
: LongWord): Boolean; overload
;
223 procedure g_Net_UnbanNonPermHosts();
224 procedure g_Net_SaveBanList();
226 procedure g_Net_DumpStart();
227 procedure g_Net_DumpSendBuffer();
228 procedure g_Net_DumpRecvBuffer(Buf
: penet_uint8
; Len
: LongWord);
229 procedure g_Net_DumpEnd();
231 function g_Net_ForwardPorts(ForwardPongPort
: Boolean = True): Boolean;
232 procedure g_Net_UnforwardPorts();
234 function g_Net_UserRequestExit
: Boolean;
236 function g_Net_Wait_MapInfo (var tf
: TNetFileTransfer
; var resList
: TNetMapResourceInfoArray
): Integer;
237 function g_Net_RequestResFileInfo (resIndex
: LongInt; out tf
: TNetFileTransfer
): Integer;
238 function g_Net_AbortResTransfer (var tf
: TNetFileTransfer
): Boolean;
239 function g_Net_ReceiveResourceFile (resIndex
: LongInt; var tf
: TNetFileTransfer
; strm
: TStream
): Integer;
241 function g_Net_IsNetworkAvailable (): Boolean;
242 procedure g_Net_InitLowLevel ();
243 procedure g_Net_DeinitLowLevel ();
245 procedure NetServerCVars(P
: SSArray
);
250 // *enet_host_service()*
251 // fuck! https://www.mail-archive.com/enet-discuss@cubik.org/msg00852.html
252 // tl;dr: on shitdows, we can get -1 sometimes, and it is *NOT* a failure.
253 // thank you, enet. let's ignore failures altogether then.
258 g_nethandler
, g_netmsg
, g_netmaster
, g_player
, g_window
, g_console
,
259 g_main
, g_game
, g_language
, g_weapons
, ctypes
, g_system
, g_map
;
262 FILE_CHUNK_SIZE
= 8192;
265 enet_init_success
: Boolean = false;
266 g_Net_DownloadTimeout
: Single;
270 function g_Net_IsNetworkAvailable (): Boolean;
272 result
:= enet_init_success
;
275 procedure g_Net_InitLowLevel ();
277 if enet_init_success
then raise Exception
.Create('wuta?!');
278 enet_init_success
:= (enet_initialize() = 0);
281 procedure g_Net_DeinitLowLevel ();
283 if enet_init_success
then
286 enet_init_success
:= false;
291 //**************************************************************************
295 //**************************************************************************
297 procedure clearNetClientTransfers (var nc
: TNetClient
);
299 nc
.Transfer
.stream
.Free
;
300 nc
.Transfer
.diskName
:= ''; // just in case
301 if (nc
.Transfer
.diskBuffer
<> nil) then FreeMem(nc
.Transfer
.diskBuffer
);
302 nc
.Transfer
.stream
:= nil;
303 nc
.Transfer
.diskBuffer
:= nil;
307 procedure clearNetClient (var nc
: TNetClient
);
309 clearNetClientTransfers(nc
);
313 procedure clearNetClients (clearArray
: Boolean);
317 for f
:= Low(NetClients
) to High(NetClients
) do clearNetClient(NetClients
[f
]);
318 if (clearArray
) then SetLength(NetClients
, 0);
322 function g_Net_UserRequestExit (): Boolean;
324 Result
:= {e_KeyPressed(IK_SPACE) or}
325 e_KeyPressed(IK_ESCAPE
) or
326 e_KeyPressed(VK_ESCAPE
) or
327 e_KeyPressed(JOY0_JUMP
) or
328 e_KeyPressed(JOY1_JUMP
) or
329 e_KeyPressed(JOY2_JUMP
) or
330 e_KeyPressed(JOY3_JUMP
)
334 //**************************************************************************
336 // file transfer declaraions and host packet processor
338 //**************************************************************************
341 // server packet type
342 NTF_SERVER_DONE
= 10; // done with this file
343 NTF_SERVER_FILE_INFO
= 11; // sent after client request
344 NTF_SERVER_CHUNK
= 12; // next chunk; chunk number follows
345 NTF_SERVER_ABORT
= 13; // server abort
346 NTF_SERVER_MAP_INFO
= 14;
348 // client packet type
349 NTF_CLIENT_MAP_REQUEST
= 100; // map file request; also, returns list of additional wads to download
350 NTF_CLIENT_FILE_REQUEST
= 101; // resource file request (by index)
351 NTF_CLIENT_ABORT
= 102; // do not send requested file, or abort current transfer
352 NTF_CLIENT_START
= 103; // start transfer; client may resume download by sending non-zero starting chunk
353 NTF_CLIENT_ACK
= 104; // chunk ack; chunk number follows
356 // disconnect client due to some file transfer error
357 procedure killClientByFT (var nc
: TNetClient
);
359 e_LogWritefln('disconnected client #%d due to file transfer error', [nc
.ID
], TMsgType
.Warning
);
360 enet_peer_disconnect(nc
.Peer
, NET_DISC_FILE_TIMEOUT
);
361 clearNetClientTransfers(nc
);
362 g_Net_Slist_ServerPlayerLeaves();
366 // send file transfer message from server to client
367 function ftransSendServerMsg (var nc
: TNetClient
; var m
: TMsg
): Boolean;
372 if (m
.CurSize
< 1) then exit
;
373 pkt
:= enet_packet_create(m
.Data
, m
.CurSize
, ENET_PACKET_FLAG_RELIABLE
);
374 if not Assigned(pkt
) then begin killClientByFT(nc
); exit
; end;
375 if (enet_peer_send(nc
.Peer
, NET_CHAN_DOWNLOAD_EX
, pkt
) <> 0) then begin killClientByFT(nc
); exit
; end;
380 // send file transfer message from client to server
381 function ftransSendClientMsg (var m
: TMsg
): Boolean;
386 if (m
.CurSize
< 1) then exit
;
387 pkt
:= enet_packet_create(m
.Data
, m
.CurSize
, ENET_PACKET_FLAG_RELIABLE
);
388 if not Assigned(pkt
) then exit
;
389 if (enet_peer_send(NetPeer
, NET_CHAN_DOWNLOAD_EX
, pkt
) <> 0) then exit
;
395 procedure ProcessChunkSend (var nc
: TNetClient
);
397 tf
: ^TNetFileTransfer
;
403 if (tf
.stream
= nil) then exit
;
405 // arbitrary timeout number
406 if (ct
-tf
.lastAckTime
>= 5000) then
411 // check if we need to send something
412 if (not tf
.inProgress
) then exit
; // waiting for the initial ack
413 // ok, we're sending chunks
414 if (tf
.lastAckChunk
<> tf
.lastSentChunk
) then exit
;
415 Inc(tf
.lastSentChunk
);
416 // do it one chunk at a time; client ack will advance our chunk counter
417 chunks
:= (tf
.size
+tf
.chunkSize
-1) div tf
.chunkSize
;
419 if (tf
.lastSentChunk
> chunks
) then
426 if (tf
.lastSentChunk
= chunks
) then
428 // we're done with this file
429 e_LogWritefln('download: client #%d, DONE sending chunks #%d/#%d', [nc
.ID
, tf
.lastSentChunk
, chunks
]);
430 trans_omsg
.Write(Byte(NTF_SERVER_DONE
));
431 clearNetClientTransfers(nc
);
436 trans_omsg
.Write(Byte(NTF_SERVER_CHUNK
));
437 trans_omsg
.Write(LongInt(tf
.lastSentChunk
));
439 rd
:= tf
.size
-(tf
.lastSentChunk
*tf
.chunkSize
);
440 if (rd
> tf
.chunkSize
) then rd
:= tf
.chunkSize
;
441 trans_omsg
.Write(LongInt(rd
));
442 //e_LogWritefln('download: client #%d, sending chunk #%d/#%d (%d bytes)', [nc.ID, tf.lastSentChunk, chunks, rd]);
443 //FIXME: check for errors here
445 tf
.stream
.Seek(tf
.lastSentChunk
*tf
.chunkSize
, soFromBeginning
);
446 tf
.stream
.ReadBuffer(tf
.diskBuffer
^, rd
);
447 trans_omsg
.WriteData(tf
.diskBuffer
, rd
);
454 ftransSendServerMsg(nc
, trans_omsg
);
458 // server file transfer packet processor
459 // received packet is in `NetEvent`
460 procedure ProcessDownloadExPacket ();
467 tf
: ^TNetFileTransfer
;
477 // find client index by peer
478 for f
:= Low(NetClients
) to High(NetClients
) do
480 if (not NetClients
[f
].Used
) then continue
;
481 if (NetClients
[f
].Peer
= NetEvent
.peer
) then
487 //e_LogWritefln('RECEIVE: dlpacket; client=%d (datalen=%u)', [nid, NetEvent.packet^.dataLength]);
489 if (nid
< 0) then exit
; // wtf?!
490 nc
:= @NetClients
[nid
];
492 if (NetEvent
.packet
^.dataLength
= 0) then
498 tf
:= @NetClients
[nid
].Transfer
;
499 tf
.lastAckTime
:= GetTimerMS();
501 cmd
:= Byte(NetEvent
.packet
^.data
^);
502 //e_LogWritefln('RECEIVE: nid=%d; cmd=%u', [nid, cmd]);
504 NTF_CLIENT_FILE_REQUEST
: // file request
506 if (tf
.stream
<> nil) then
511 if (NetEvent
.packet
^.dataLength
< 2) then
516 // new transfer request; build packet
517 if not msg
.Init(NetEvent
.packet
^.data
+1, NetEvent
.packet
^.dataLength
-1, True) then
522 // get resource index
523 ridx
:= msg
.ReadLongInt();
524 if (ridx
< -1) or (ridx
>= length(gExternalResources
)) then
526 e_LogWritefln('Invalid resource index %d', [ridx
], TMsgType
.Warning
);
530 if (ridx
< 0) then fname
:= gGameSettings
.WAD
else fname
:= gExternalResources
[ridx
].diskName
;
531 if (length(fname
) = 0) then
533 e_WriteLog('Invalid filename: '+fname
, TMsgType
.Warning
);
537 tf
.diskName
:= findDiskWad(fname
);
538 if (length(tf
.diskName
) = 0) then
540 e_LogWritefln('NETWORK: file "%s" not found!', [fname
], TMsgType
.Fatal
);
545 //tf.hash := MD5File(tf.diskName);
546 if (ridx
< 0) then tf
.hash
:= gWADHash
else tf
.hash
:= gExternalResources
[ridx
].hash
;
547 // create file stream
548 tf
.diskName
:= findDiskWad(fname
);
550 tf
.stream
:= openDiskFileRO(tf
.diskName
);
554 if (tf
.stream
= nil) then
556 e_WriteLog(Format('NETWORK: file "%s" not found!', [fname
]), TMsgType
.Fatal
);
560 e_LogWritefln('client #%d requested resource #%d (file is `%s` : `%s`)', [nc
.ID
, ridx
, fname
, tf
.diskName
]);
561 tf
.size
:= tf
.stream
.size
;
562 tf
.chunkSize
:= FILE_CHUNK_SIZE
; // arbitrary
563 tf
.lastSentChunk
:= -1;
564 tf
.lastAckChunk
:= -1;
565 tf
.lastAckTime
:= GetTimerMS();
566 tf
.inProgress
:= False; // waiting for the first ACK or for the cancel
567 GetMem(tf
.diskBuffer
, tf
.chunkSize
);
568 // sent file info message
570 trans_omsg
.Write(Byte(NTF_SERVER_FILE_INFO
));
571 trans_omsg
.Write(tf
.hash
);
572 trans_omsg
.Write(tf
.size
);
573 trans_omsg
.Write(tf
.chunkSize
);
574 trans_omsg
.Write(ExtractFileName(fname
));
575 if not ftransSendServerMsg(nc
^, trans_omsg
) then exit
;
577 NTF_CLIENT_ABORT
: // do not send requested file, or abort current transfer
579 e_LogWritefln('client #%d aborted file transfer', [nc
.ID
]);
580 clearNetClientTransfers(nc
^);
582 NTF_CLIENT_START
: // start transfer; client may resume download by sending non-zero starting chunk
584 if not Assigned(tf
.stream
) then
589 if (tf
.lastSentChunk
<> -1) or (tf
.lastAckChunk
<> -1) or (tf
.inProgress
) then
591 // double ack, get lost
595 if (NetEvent
.packet
^.dataLength
< 2) then
601 if not msg
.Init(NetEvent
.packet
^.data
+1, NetEvent
.packet
^.dataLength
-1, True) then
606 chunk
:= msg
.ReadLongInt();
607 if (chunk
< 0) or (chunk
> (tf
.size
+tf
.chunkSize
-1) div tf
.chunkSize
) then
612 e_LogWritefln('client #%d started file transfer from chunk %d', [nc
.ID
, chunk
]);
613 // start sending chunks
614 tf
.inProgress
:= True;
615 tf
.lastSentChunk
:= chunk
-1;
616 tf
.lastAckChunk
:= chunk
-1;
617 ProcessChunkSend(nc
^);
619 NTF_CLIENT_ACK
: // chunk ack; chunk number follows
621 if not Assigned(tf
.stream
) then
626 if (tf
.lastSentChunk
< 0) or (not tf
.inProgress
) then
628 // double ack, get lost
632 if (NetEvent
.packet
^.dataLength
< 2) then
638 if not msg
.Init(NetEvent
.packet
^.data
+1, NetEvent
.packet
^.dataLength
-1, True) then
643 chunk
:= msg
.ReadLongInt();
644 if (chunk
< 0) or (chunk
> (tf
.size
+tf
.chunkSize
-1) div tf
.chunkSize
) then
649 // do it this way, so client may seek, or request retransfers for some reason
650 tf
.lastAckChunk
:= chunk
;
651 tf
.lastSentChunk
:= chunk
;
652 //e_LogWritefln('client #%d acked file transfer chunk %d', [nc.ID, chunk]);
653 ProcessChunkSend(nc
^);
655 NTF_CLIENT_MAP_REQUEST
:
657 e_LogWritefln('client #%d requested map info', [nc
.ID
]);
659 dfn
:= findDiskWad(gGameSettings
.WAD
);
660 if (dfn
= '') then dfn
:= '!wad_not_found!.wad'; //FIXME
661 //md5 := MD5File(dfn);
663 if (not GetDiskFileInfo(dfn
, fi
)) then
665 e_LogWritefln('client #%d requested map info, but i cannot get file info', [nc
.ID
]);
671 st := openDiskFileRO(dfn);
672 if not assigned(st) then exit; //wtf?!
677 trans_omsg
.Write(Byte(NTF_SERVER_MAP_INFO
));
679 trans_omsg
.Write(ExtractFileName(gGameSettings
.WAD
));
681 trans_omsg
.Write(md5
);
683 trans_omsg
.Write(size
);
684 // number of external resources for map
685 trans_omsg
.Write(LongInt(length(gExternalResources
)));
686 // external resource names
687 for f
:= 0 to High(gExternalResources
) do
690 //trans_omsg.Write(ExtractFileName(gExternalResources[f])); // GameDir+'/wads/'+ResList.Strings[i]
692 trans_omsg
.Write('!');
693 trans_omsg
.Write(LongInt(gExternalResources
[f
].size
));
694 trans_omsg
.Write(gExternalResources
[f
].hash
);
695 trans_omsg
.Write(ExtractFileName(gExternalResources
[f
].diskName
));
698 if not ftransSendServerMsg(nc
^, trans_omsg
) then exit
;
709 //**************************************************************************
711 // file transfer crap (both client and server)
713 //**************************************************************************
715 function getNewTimeoutEnd (): Int64;
717 result
:= GetTimerMS();
718 if (g_Net_DownloadTimeout
<= 0) then
720 result
:= result
+1000*60*3; // 3 minutes
724 result
:= result
+trunc(g_Net_DownloadTimeout
*1000);
729 // send map request to server, and wait for "map info" server reply
731 // returns `false` on error or user abort
733 // diskName: map wad file name (without a path)
734 // hash: map wad hash
735 // size: map wad size
736 // chunkSize: set too
737 // resList: list of resource wads
743 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
744 function g_Net_Wait_MapInfo (var tf
: TNetFileTransfer
; var resList
: TNetMapResourceInfoArray
): Integer;
750 freePacket
: Boolean = false;
755 ri
: ^TNetMapResourceInfo
;
757 SetLength(resList
, 0);
761 trans_omsg
.Write(Byte(NTF_CLIENT_MAP_REQUEST
));
762 if not ftransSendClientMsg(trans_omsg
) then begin result
:= -1; exit
; end;
764 FillChar(ev
, SizeOf(ev
), 0);
767 ett
:= getNewTimeoutEnd();
769 status
:= enet_host_service(NetHost
, @ev
, 300);
773 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True);
778 if (status
<= 0) then
784 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' timeout reached', True);
793 ENET_EVENT_TYPE_RECEIVE
:
796 if (ev
.channelID
<> NET_CHAN_DOWNLOAD_EX
) then
798 //e_LogWritefln('g_Net_Wait_MapInfo: skip message from non-transfer channel', []);
800 g_Net_Client_HandlePacket(ev
.packet
, g_Net_ClientLightMsgHandler
);
801 if (g_Res_received_map_start
< 0) then begin result
:= -666; exit
; end;
805 ett
:= getNewTimeoutEnd();
806 if (ev
.packet
.dataLength
< 1) then
808 e_LogWritefln('g_Net_Wait_MapInfo: invalid server packet (no data)', []);
812 Ptr
:= ev
.packet
^.data
;
813 rMsgId
:= Byte(Ptr
^);
814 e_LogWritefln('g_Net_Wait_MapInfo: got message %u from server (dataLength=%u)', [rMsgId
, ev
.packet
^.dataLength
]);
815 if (rMsgId
= NTF_SERVER_FILE_INFO
) then
817 e_LogWritefln('g_Net_Wait_MapInfo: waiting for map info reply, but got file info reply', []);
821 else if (rMsgId
= NTF_SERVER_ABORT
) then
823 e_LogWritefln('g_Net_Wait_MapInfo: server aborted transfer', []);
827 else if (rMsgId
= NTF_SERVER_MAP_INFO
) then
829 e_LogWritefln('g_Net_Wait_MapInfo: creating map info packet...', []);
830 if not msg
.Init(ev
.packet
^.data
+1, ev
.packet
^.dataLength
-1, True) then exit
;
831 e_LogWritefln('g_Net_Wait_MapInfo: parsing map info packet (rd=%d; max=%d)...', [msg
.ReadCount
, msg
.MaxSize
]);
832 SetLength(resList
, 0); // just in case
834 tf
.diskName
:= msg
.ReadString();
835 e_LogWritefln('g_Net_Wait_MapInfo: map wad is `%s`', [tf
.diskName
]);
837 tf
.hash
:= msg
.ReadMD5();
839 tf
.size
:= msg
.ReadLongInt();
840 e_LogWritefln('g_Net_Wait_MapInfo: map wad size is %d', [tf
.size
]);
841 // number of external resources for map
842 rc
:= msg
.ReadLongInt();
843 if (rc
< 0) or (rc
> 1024) then
845 e_LogWritefln('g_Net_Wait_Event: invalid number of map external resources (%d)', [rc
]);
849 e_LogWritefln('g_Net_Wait_MapInfo: map external resource count is %d', [rc
]);
850 SetLength(resList
, rc
);
851 // external resource names
852 for f
:= 0 to rc
-1 do
855 s
:= msg
.ReadString();
856 if (length(s
) = 0) then begin result
:= -1; exit
; end;
860 ri
.size
:= msg
.ReadLongInt();
861 ri
.hash
:= msg
.ReadMD5();
862 ri
.wadName
:= ExtractFileName(msg
.ReadString());
863 if (length(ri
.wadName
) = 0) or (ri
.size
< 0) then begin result
:= -1; exit
; end;
867 // old-style packet, only name
868 ri
.wadName
:= ExtractFileName(s
);
869 if (length(ri
.wadName
) = 0) then begin result
:= -1; exit
; end;
870 ri
.size
:= -1; // unknown
873 e_LogWritefln('g_Net_Wait_MapInfo: got map info', []);
874 Result
:= 0; // success
879 e_LogWritefln('g_Net_Wait_Event: invalid server packet type', []);
885 ENET_EVENT_TYPE_DISCONNECT
:
887 if (ev
.data
<= NET_DISC_MAX
) then
888 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' ' + _lc
[TStrings_Locale(Cardinal(I_NET_DISC_NONE
) + ev
.data
)], True);
894 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' unknown ENet event ' + IntToStr(Ord(ev
.kind
)), True);
899 if (freePacket
) then begin freePacket
:= false; enet_packet_destroy(ev
.packet
); end;
902 if g_Net_UserRequestExit() then
904 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' user abort', True);
910 if (freePacket
) then enet_packet_destroy(ev
.packet
);
915 // send file request to server, and wait for server reply
917 // returns `false` on error or user abort
919 // diskName (actually, base name)
928 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
929 function g_Net_RequestResFileInfo (resIndex
: LongInt; out tf
: TNetFileTransfer
): Integer;
935 freePacket
: Boolean = false;
941 trans_omsg
.Write(Byte(NTF_CLIENT_FILE_REQUEST
));
942 trans_omsg
.Write(resIndex
);
943 if not ftransSendClientMsg(trans_omsg
) then begin result
:= -1; exit
; end;
945 FillChar(ev
, SizeOf(ev
), 0);
948 ett
:= getNewTimeoutEnd();
950 status
:= enet_host_service(NetHost
, @ev
, 300);
954 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True);
959 if (status
<= 0) then
965 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' timeout reached', True);
974 ENET_EVENT_TYPE_RECEIVE
:
977 if (ev
.channelID
<> NET_CHAN_DOWNLOAD_EX
) then
979 //e_LogWriteln('g_Net_Wait_Event: skip message from non-transfer channel');
981 g_Net_Client_HandlePacket(ev
.packet
, g_Net_ClientLightMsgHandler
);
982 if (g_Res_received_map_start
< 0) then begin result
:= -666; exit
; end;
986 ett
:= getNewTimeoutEnd();
987 if (ev
.packet
.dataLength
< 1) then
989 e_LogWriteln('g_Net_Wait_Event: invalid server packet (no data)');
993 Ptr
:= ev
.packet
^.data
;
994 rMsgId
:= Byte(Ptr
^);
995 e_LogWritefln('received transfer packet with id %d (%u bytes)', [rMsgId
, ev
.packet
^.dataLength
]);
996 if (rMsgId
= NTF_SERVER_FILE_INFO
) then
998 if not msg
.Init(ev
.packet
^.data
+1, ev
.packet
^.dataLength
-1, True) then exit
;
999 tf
.hash
:= msg
.ReadMD5();
1000 tf
.size
:= msg
.ReadLongInt();
1001 tf
.chunkSize
:= msg
.ReadLongInt();
1002 tf
.diskName
:= ExtractFileName(msg
.readString());
1003 if (tf
.size
< 0) or (tf
.chunkSize
<> FILE_CHUNK_SIZE
) or (length(tf
.diskName
) = 0) then
1005 e_LogWritefln('g_Net_RequestResFileInfo: invalid file info packet', []);
1009 e_LogWritefln('got file info for resource #%d: size=%d; name=%s', [resIndex
, tf
.size
, tf
.diskName
]);
1010 Result
:= 0; // success
1013 else if (rMsgId
= NTF_SERVER_ABORT
) then
1015 e_LogWriteln('g_Net_RequestResFileInfo: server aborted transfer');
1019 else if (rMsgId
= NTF_SERVER_MAP_INFO
) then
1021 e_LogWriteln('g_Net_RequestResFileInfo: waiting for map info reply, but got file info reply');
1027 e_LogWriteln('g_Net_RequestResFileInfo: invalid server packet type');
1033 ENET_EVENT_TYPE_DISCONNECT
:
1035 if (ev
.data
<= NET_DISC_MAX
) then
1036 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' ' + _lc
[TStrings_Locale(Cardinal(I_NET_DISC_NONE
) + ev
.data
)], True);
1042 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' unknown ENet event ' + IntToStr(Ord(ev
.kind
)), True);
1047 if (freePacket
) then begin freePacket
:= false; enet_packet_destroy(ev
.packet
); end;
1050 if g_Net_UserRequestExit() then
1052 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' user abort', True);
1058 if (freePacket
) then enet_packet_destroy(ev
.packet
);
1063 // call this to cancel file transfer requested by `g_Net_RequestResFileInfo()`
1064 function g_Net_AbortResTransfer (var tf
: TNetFileTransfer
): Boolean;
1067 e_LogWritefln('aborting file transfer...', []);
1070 trans_omsg
.Write(Byte(NTF_CLIENT_ABORT
));
1071 result
:= ftransSendClientMsg(trans_omsg
);
1072 if result
then enet_host_flush(NetHost
);
1076 // call this to start file transfer requested by `g_Net_RequestResFileInfo()`
1078 // returns `false` on error or user abort
1087 // 2 on server abort
1088 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
1089 function g_Net_ReceiveResourceFile (resIndex
: LongInt; var tf
: TNetFileTransfer
; strm
: TStream
): Integer;
1095 freePacket
: Boolean = false;
1098 nextChunk
: Integer = 0;
1099 chunkTotal
: Integer;
1106 tf
.resumed
:= false;
1107 e_LogWritefln('file `%s`, size=%d (%d)', [tf
.diskName
, Integer(strm
.size
), tf
.size
], TMsgType
.Notify
);
1108 // check if we should resume downloading
1109 resumed
:= (strm
.size
> tf
.chunkSize
) and (strm
.size
< tf
.size
);
1112 trans_omsg
.Write(Byte(NTF_CLIENT_START
));
1113 if resumed
then chunk
:= strm
.size
div tf
.chunkSize
else chunk
:= 0;
1114 trans_omsg
.Write(LongInt(chunk
));
1115 if not ftransSendClientMsg(trans_omsg
) then begin result
:= -1; exit
; end;
1117 strm
.Seek(chunk
*tf
.chunkSize
, soFromBeginning
);
1118 chunkTotal
:= (tf
.size
+tf
.chunkSize
-1) div tf
.chunkSize
;
1119 e_LogWritefln('receiving file `%s` (%d chunks)', [tf
.diskName
, chunkTotal
], TMsgType
.Notify
);
1120 g_Game_SetLoadingText('downloading "'+ExtractFileName(tf
.diskName
)+'"', chunkTotal
, False);
1121 tf
.resumed
:= resumed
;
1123 if (chunk
> 0) then g_Game_StepLoading(chunk
);
1126 // wait for reply data
1127 FillChar(ev
, SizeOf(ev
), 0);
1129 GetMem(buf
, tf
.chunkSize
);
1131 ett
:= getNewTimeoutEnd();
1133 //stx := -GetTimerMS();
1134 status
:= enet_host_service(NetHost
, @ev
, 300);
1136 if (status < 0) then
1138 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True);
1143 if (status
<= 0) then
1145 // check for timeout
1149 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' timeout reached', True);
1158 ENET_EVENT_TYPE_RECEIVE
:
1161 if (ev
.channelID
<> NET_CHAN_DOWNLOAD_EX
) then
1163 //e_LogWritefln('g_Net_Wait_Event: skip message from non-transfer channel', []);
1164 freePacket
:= false;
1165 g_Net_Client_HandlePacket(ev
.packet
, g_Net_ClientLightMsgHandler
);
1166 if (g_Res_received_map_start
< 0) then begin result
:= -666; exit
; end;
1170 //stx := stx+GetTimerMS();
1171 //e_LogWritefln('g_Net_ReceiveResourceFile: stx=%d', [Integer(stx)]);
1172 //stx := -GetTimerMS();
1173 ett
:= getNewTimeoutEnd();
1174 if (ev
.packet
.dataLength
< 1) then
1176 e_LogWritefln('g_Net_ReceiveResourceFile: invalid server packet (no data)', []);
1180 Ptr
:= ev
.packet
^.data
;
1181 rMsgId
:= Byte(Ptr
^);
1182 if (rMsgId
= NTF_SERVER_DONE
) then
1184 e_LogWritefln('file transfer complete.', []);
1188 else if (rMsgId
= NTF_SERVER_CHUNK
) then
1190 if not msg
.Init(ev
.packet
^.data
+1, ev
.packet
^.dataLength
-1, True) then exit
;
1191 chunk
:= msg
.ReadLongInt();
1192 csize
:= msg
.ReadLongInt();
1193 if (chunk
<> nextChunk
) then
1195 e_LogWritefln('received chunk %d, but expected chunk %d', [chunk
, nextChunk
]);
1199 if (csize
< 0) or (csize
> tf
.chunkSize
) then
1201 e_LogWritefln('received chunk with size %d, but expected chunk size is %d', [csize
, tf
.chunkSize
]);
1205 //e_LogWritefln('got chunk #%d of #%d (csize=%d)', [chunk, (tf.size+tf.chunkSize-1) div tf.chunkSize, csize]);
1206 msg
.ReadData(buf
, csize
);
1207 strm
.WriteBuffer(buf
^, csize
);
1208 nextChunk
:= chunk
+1;
1209 g_Game_StepLoading();
1212 trans_omsg
.Write(Byte(NTF_CLIENT_ACK
));
1213 trans_omsg
.Write(LongInt(chunk
));
1214 if not ftransSendClientMsg(trans_omsg
) then begin result
:= -1; exit
; end;
1216 else if (rMsgId
= NTF_SERVER_ABORT
) then
1218 e_LogWritefln('g_Net_ReceiveResourceFile: server aborted transfer', []);
1224 e_LogWritefln('g_Net_ReceiveResourceFile: invalid server packet type', []);
1228 //stx := stx+GetTimerMS();
1229 //e_LogWritefln('g_Net_ReceiveResourceFile: process stx=%d', [Integer(stx)]);
1232 ENET_EVENT_TYPE_DISCONNECT
:
1234 if (ev
.data
<= NET_DISC_MAX
) then
1235 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' ' + _lc
[TStrings_Locale(Cardinal(I_NET_DISC_NONE
) + ev
.data
)], True);
1241 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' unknown ENet event ' + IntToStr(Ord(ev
.kind
)), True);
1246 if (freePacket
) then begin freePacket
:= false; enet_packet_destroy(ev
.packet
); end;
1249 if g_Net_UserRequestExit() then
1251 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' user abort', True);
1258 if (freePacket
) then enet_packet_destroy(ev
.packet
);
1263 //**************************************************************************
1267 //**************************************************************************
1269 function g_Net_FindSlot(): Integer;
1278 for I
:= Low(NetClients
) to High(NetClients
) do
1280 if NetClients
[I
].Used
then
1289 if C
>= NetMaxClients
then
1297 if (Length(NetClients
) >= NetMaxClients
) then
1301 SetLength(NetClients
, Length(NetClients
) + 1);
1302 N
:= High(NetClients
);
1308 NetClients
[N
].Used
:= True;
1309 NetClients
[N
].ID
:= N
;
1310 NetClients
[N
].RequestedFullUpdate
:= False;
1311 NetClients
[N
].WaitForFirstSpawn
:= False;
1312 NetClients
[N
].RCONAuth
:= False;
1313 NetClients
[N
].Voted
:= False;
1314 NetClients
[N
].Player
:= 0;
1315 clearNetClientTransfers(NetClients
[N
]); // just in case
1322 function g_Net_Init(): Boolean;
1331 NetBuf
[NET_UNRELIABLE
].Clear();
1332 NetBuf
[NET_RELIABLE
].Clear();
1333 //SetLength(NetClients, 0);
1334 clearNetClients(true); // clear array
1340 NetAddr
.port
:= 25666;
1341 SetLength(NetBannedHosts
, 0);
1342 path
:= BANLIST_FILENAME
;
1343 if e_FindResource(DataDirs
, path
) = true then
1350 if StrToIp(IPstr
, IP
) then
1354 g_Net_SaveBanList();
1357 //Result := (enet_initialize() = 0);
1358 Result
:= enet_init_success
;
1361 procedure g_Net_Flush();
1365 F
, Chan
: enet_uint32
;
1369 Chan
:= NET_CHAN_GAME
;
1371 if NetMode
= NET_SERVER
then
1372 for T
:= NET_UNRELIABLE
to NET_RELIABLE
do
1374 if NetBuf
[T
].CurSize
> 0 then
1376 P
:= enet_packet_create(NetBuf
[T
].Data
, NetBuf
[T
].CurSize
, F
);
1377 if not Assigned(P
) then continue
;
1378 enet_host_broadcast(NetHost
, Chan
, P
);
1382 for I
:= Low(NetClients
) to High(NetClients
) do
1384 if not NetClients
[I
].Used
then continue
;
1385 if NetClients
[I
].NetOut
[T
].CurSize
<= 0 then continue
;
1386 P
:= enet_packet_create(NetClients
[I
].NetOut
[T
].Data
, NetClients
[I
].NetOut
[T
].CurSize
, F
);
1387 if not Assigned(P
) then continue
;
1388 enet_peer_send(NetClients
[I
].Peer
, Chan
, P
);
1389 NetClients
[I
].NetOut
[T
].Clear();
1392 // next and last iteration is always RELIABLE
1393 F
:= LongWord(ENET_PACKET_FLAG_RELIABLE
);
1394 Chan
:= NET_CHAN_IMPORTANT
;
1396 else if NetMode
= NET_CLIENT
then
1397 for T
:= NET_UNRELIABLE
to NET_RELIABLE
do
1399 if NetBuf
[T
].CurSize
> 0 then
1401 P
:= enet_packet_create(NetBuf
[T
].Data
, NetBuf
[T
].CurSize
, F
);
1402 if not Assigned(P
) then continue
;
1403 enet_peer_send(NetPeer
, Chan
, P
);
1406 // next and last iteration is always RELIABLE
1407 F
:= LongWord(ENET_PACKET_FLAG_RELIABLE
);
1408 Chan
:= NET_CHAN_IMPORTANT
;
1412 procedure g_Net_Cleanup();
1416 NetBuf
[NET_UNRELIABLE
].Clear();
1417 NetBuf
[NET_RELIABLE
].Clear();
1419 //SetLength(NetClients, 0);
1420 clearNetClients(true); // clear array
1421 NetClientCount
:= 0;
1425 g_Net_Slist_ServerClosed();
1429 NetState
:= NET_STATE_NONE
;
1431 NetPongSock
:= ENET_SOCKET_NULL
;
1433 NetTimeToMaster
:= 0;
1434 NetTimeToUpdate
:= 0;
1435 NetTimeToReliable
:= 0;
1437 NetMode
:= NET_NONE
;
1439 if NetPortThread
<> NilThreadId
then
1440 WaitForThreadTerminate(NetPortThread
, 66666);
1442 NetPortThread
:= NilThreadId
;
1443 g_Net_UnforwardPorts();
1449 procedure g_Net_Free();
1453 //enet_deinitialize();
1454 NetInitDone
:= False;
1458 //**************************************************************************
1462 //**************************************************************************
1464 function ForwardThread(Param
: Pointer): PtrInt
;
1467 if not g_Net_ForwardPorts() then Result
:= -1;
1470 function g_Net_Host(IPAddr
: LongWord; Port
: enet_uint16
; MaxClients
: Cardinal = 16): Boolean;
1472 if NetMode
<> NET_NONE
then
1474 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_INGAME
]);
1481 g_Console_Add(_lc
[I_NET_MSG
] + Format(_lc
[I_NET_MSG_HOST
], [Port
]));
1482 if not NetInitDone
then
1484 if (not g_Net_Init()) then
1486 g_Console_Add(_lc
[I_NET_MSG_FERROR
] + _lc
[I_NET_ERR_ENET
]);
1491 NetInitDone
:= True;
1494 NetAddr
.host
:= IPAddr
;
1495 NetAddr
.port
:= Port
;
1497 NetHost
:= enet_host_create(@NetAddr
, NET_MAXCLIENTS
, NET_CHANS
, 0, 0);
1499 if (NetHost
= nil) then
1501 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + Format(_lc
[I_NET_ERR_HOST
], [Port
]));
1507 if NetForwardPorts
then NetPortThread
:= BeginThread(ForwardThread
);
1509 NetPongSock
:= enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM
);
1510 if NetPongSock
<> ENET_SOCKET_NULL
then
1512 NetPongAddr
.host
:= IPAddr
;
1513 NetPongAddr
.port
:= NET_PING_PORT
;
1514 if enet_socket_bind(NetPongSock
, @NetPongAddr
) < 0 then
1516 enet_socket_destroy(NetPongSock
);
1517 NetPongSock
:= ENET_SOCKET_NULL
;
1520 enet_socket_set_option(NetPongSock
, ENET_SOCKOPT_NONBLOCK
, 1);
1523 NetMode
:= NET_SERVER
;
1525 NetBuf
[NET_UNRELIABLE
].Clear();
1526 NetBuf
[NET_RELIABLE
].Clear();
1532 procedure g_Net_Host_Die();
1536 if NetMode
<> NET_SERVER
then Exit
;
1538 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_HOST_DISCALL
]);
1539 for I
:= 0 to High(NetClients
) do
1540 if NetClients
[I
].Used
then
1541 enet_peer_disconnect(NetClients
[I
].Peer
, NET_DISC_DOWN
);
1543 while enet_host_service(NetHost
, @NetEvent
, 1000) > 0 do
1544 if NetEvent
.kind
= ENET_EVENT_TYPE_RECEIVE
then
1545 enet_packet_destroy(NetEvent
.packet
);
1547 for I
:= 0 to High(NetClients
) do
1548 if NetClients
[I
].Used
then
1550 FreeMemory(NetClients
[I
].Peer
^.data
);
1551 NetClients
[I
].Peer
^.data
:= nil;
1552 enet_peer_reset(NetClients
[I
].Peer
);
1553 NetClients
[I
].Peer
:= nil;
1554 NetClients
[I
].Used
:= False;
1555 NetClients
[I
].NetOut
[NET_UNRELIABLE
].Free();
1556 NetClients
[I
].NetOut
[NET_RELIABLE
].Free();
1559 clearNetClients(false); // don't clear array
1560 g_Net_Slist_ServerClosed();
1561 if NetPongSock
<> ENET_SOCKET_NULL
then
1562 enet_socket_destroy(NetPongSock
);
1564 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_HOST_DIE
]);
1565 enet_host_destroy(NetHost
);
1567 NetMode
:= NET_NONE
;
1570 e_WriteLog('NET: Server stopped', TMsgType
.Notify
);
1574 procedure g_Net_Host_Send(ID
: Integer; Reliable
: Boolean; Chan
: Byte = NET_CHAN_GAME
);
1581 T
:= NET_UNRELIABLE
;
1585 if ID
> High(NetClients
) then Exit
;
1586 if NetClients
[ID
].Peer
= nil then Exit
;
1588 NetClients
[ID
].NetOut
[T
].Write(Integer(NetOut
.CurSize
));
1589 NetClients
[ID
].NetOut
[T
].Write(NetOut
);
1594 NetBuf
[T
].Write(Integer(NetOut
.CurSize
));
1595 NetBuf
[T
].Write(NetOut
);
1598 if NetDump
then g_Net_DumpSendBuffer();
1602 procedure g_Net_Host_CheckPings();
1604 ClAddr
: ENetAddress
;
1608 Ping
: array [0..9] of Byte;
1611 if (NetPongSock
= ENET_SOCKET_NULL
) or (NetHost
= nil) then Exit
;
1613 Buf
.data
:= Addr(Ping
[0]);
1614 Buf
.dataLength
:= 2+8;
1618 Len
:= enet_socket_receive(NetPongSock
, @ClAddr
, @Buf
, 1);
1619 if Len
< 0 then Exit
;
1621 if (Ping
[0] = Ord('D')) and (Ping
[1] = Ord('F')) then
1623 ClTime
:= Int64(Addr(Ping
[2])^);
1626 NetOut
.Write(Byte(Ord('D')));
1627 NetOut
.Write(Byte(Ord('F')));
1628 NetOut
.Write(NetHost
.address
.port
);
1629 NetOut
.Write(ClTime
);
1630 TMasterHost
.writeInfo(NetOut
);
1632 if gPlayer1
<> nil then Inc(NPl
);
1633 if gPlayer2
<> nil then Inc(NPl
);
1635 NetOut
.Write(gNumBots
);
1637 Buf
.data
:= NetOut
.Data
;
1638 Buf
.dataLength
:= NetOut
.CurSize
;
1639 enet_socket_send(NetPongSock
, @ClAddr
, @Buf
, 1);
1646 function g_Net_Host_Update(): enet_size_t
;
1657 if NetUseMaster
then g_Net_Slist_Pulse();
1658 g_Net_Host_CheckPings();
1660 while (enet_host_service(NetHost
, @NetEvent
, 0) > 0) do
1662 case (NetEvent
.kind
) of
1663 ENET_EVENT_TYPE_CONNECT
:
1665 IP
:= IpToStr(NetEvent
.Peer
^.address
.host
);
1666 Port
:= NetEvent
.Peer
^.address
.port
;
1667 g_Console_Add(_lc
[I_NET_MSG
] +
1668 Format(_lc
[I_NET_MSG_HOST_CONN
], [IP
, Port
]));
1670 if (NetEvent
.data
<> NET_PROTOCOL_VER
) then
1672 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_HOST_REJECT
] +
1673 _lc
[I_NET_DISC_PROTOCOL
]);
1674 NetEvent
.peer
^.data
:= GetMemory(SizeOf(Byte));
1675 Byte(NetEvent
.peer
^.data
^) := 255;
1676 enet_peer_disconnect(NetEvent
.peer
, NET_DISC_PROTOCOL
);
1677 enet_host_flush(NetHost
);
1681 ID
:= g_Net_FindSlot();
1685 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_HOST_REJECT
] +
1686 _lc
[I_NET_DISC_FULL
]);
1687 NetEvent
.Peer
^.data
:= GetMemory(SizeOf(Byte));
1688 Byte(NetEvent
.peer
^.data
^) := 255;
1689 enet_peer_disconnect(NetEvent
.peer
, NET_DISC_FULL
);
1690 enet_host_flush(NetHost
);
1694 NetClients
[ID
].Peer
:= NetEvent
.peer
;
1695 NetClients
[ID
].Peer
^.data
:= GetMemory(SizeOf(Byte));
1696 Byte(NetClients
[ID
].Peer
^.data
^) := ID
;
1697 NetClients
[ID
].State
:= NET_STATE_AUTH
;
1698 NetClients
[ID
].RCONAuth
:= False;
1699 NetClients
[ID
].NetOut
[NET_UNRELIABLE
].Alloc(NET_BUFSIZE
*2);
1700 NetClients
[ID
].NetOut
[NET_RELIABLE
].Alloc(NET_BUFSIZE
*2);
1701 clearNetClientTransfers(NetClients
[ID
]); // just in case
1703 enet_peer_timeout(NetEvent
.peer
, ENET_PEER_TIMEOUT_LIMIT
* 2, ENET_PEER_TIMEOUT_MINIMUM
* 2, ENET_PEER_TIMEOUT_MAXIMUM
* 2);
1705 Inc(NetClientCount
);
1706 g_Console_Add(_lc
[I_NET_MSG
] + Format(_lc
[I_NET_MSG_HOST_ADD
], [ID
]));
1709 ENET_EVENT_TYPE_RECEIVE
:
1711 //e_LogWritefln('RECEIVE: chan=%u', [NetEvent.channelID]);
1712 if (NetEvent
.channelID
= NET_CHAN_DOWNLOAD_EX
) then
1714 ProcessDownloadExPacket();
1718 ID
:= Byte(NetEvent
.peer
^.data
^);
1719 if ID
> High(NetClients
) then Exit
;
1720 TC
:= @NetClients
[ID
];
1722 if NetDump
then g_Net_DumpRecvBuffer(NetEvent
.packet
^.data
, NetEvent
.packet
^.dataLength
);
1723 g_Net_Host_HandlePacket(TC
, NetEvent
.packet
, g_Net_HostMsgHandler
);
1727 ENET_EVENT_TYPE_DISCONNECT
:
1729 ID
:= Byte(NetEvent
.peer
^.data
^);
1730 if ID
> High(NetClients
) then Exit
;
1731 clearNetClient(NetClients
[ID
]);
1732 TC
:= @NetClients
[ID
];
1733 if TC
= nil then Exit
;
1735 if not (TC
^.Used
) then Exit
;
1737 TP
:= g_Player_Get(TC
^.Player
);
1742 TP
.Kill(K_SIMPLEKILL
, 0, HIT_DISCON
);
1743 g_Console_Add(Format(_lc
[I_PLAYER_LEAVE
], [TP
.Name
]), True);
1744 e_WriteLog('NET: Client ' + TP
.Name
+ ' [' + IntToStr(ID
) + '] disconnected.', TMsgType
.Notify
);
1745 g_Player_Remove(TP
.UID
);
1749 TC
^.State
:= NET_STATE_NONE
;
1752 TC
^.RequestedFullUpdate
:= False;
1753 TC
^.WaitForFirstSpawn
:= False;
1754 TC
^.NetOut
[NET_UNRELIABLE
].Free();
1755 TC
^.NetOut
[NET_RELIABLE
].Free();
1757 FreeMemory(NetEvent
.peer
^.data
);
1758 NetEvent
.peer
^.data
:= nil;
1759 g_Console_Add(_lc
[I_NET_MSG
] + Format(_lc
[I_NET_MSG_HOST_DISC
], [ID
]));
1760 Dec(NetClientCount
);
1762 if NetUseMaster
then g_Net_Slist_ServerPlayerLeaves();
1769 //**************************************************************************
1773 //**************************************************************************
1775 procedure g_Net_Disconnect(Forced
: Boolean = False);
1777 if NetMode
<> NET_CLIENT
then Exit
;
1778 if (NetHost
= nil) or (NetPeer
= nil) then Exit
;
1782 enet_peer_disconnect(NetPeer
, NET_DISC_NONE
);
1784 while (enet_host_service(NetHost
, @NetEvent
, 1500) > 0) do
1786 if (NetEvent
.kind
= ENET_EVENT_TYPE_DISCONNECT
) then
1792 if (NetEvent
.kind
= ENET_EVENT_TYPE_RECEIVE
) then
1793 enet_packet_destroy(NetEvent
.packet
);
1796 if NetPeer
<> nil then
1798 enet_peer_reset(NetPeer
);
1804 e_WriteLog('NET: Kicked from server: ' + IntToStr(NetEvent
.data
), TMsgType
.Notify
);
1805 if (NetEvent
.data
<= NET_DISC_MAX
) then
1806 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_KICK
] +
1807 _lc
[TStrings_Locale(Cardinal(I_NET_DISC_NONE
) + NetEvent
.data
)], True);
1810 if NetHost
<> nil then
1812 enet_host_destroy(NetHost
);
1815 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_CLIENT_DISC
]);
1818 e_WriteLog('NET: Disconnected', TMsgType
.Notify
);
1821 procedure g_Net_Client_Send(Reliable
: Boolean; Chan
: Byte = NET_CHAN_GAME
);
1828 T
:= NET_UNRELIABLE
;
1831 NetBuf
[T
].Write(Integer(NetOut
.CurSize
));
1832 NetBuf
[T
].Write(NetOut
);
1834 if NetDump
then g_Net_DumpSendBuffer();
1836 g_Net_Flush(); // FIXME: for now, send immediately
1839 function g_Net_Client_Update(): enet_size_t
;
1842 while (NetHost
<> nil) and (enet_host_service(NetHost
, @NetEvent
, 0) > 0) do
1844 case NetEvent
.kind
of
1845 ENET_EVENT_TYPE_RECEIVE
:
1847 if (NetEvent
.channelID
= NET_CHAN_DOWNLOAD_EX
) then continue
; // ignore all download packets, they're processed by separate code
1848 if NetDump
then g_Net_DumpRecvBuffer(NetEvent
.packet
^.data
, NetEvent
.packet
^.dataLength
);
1849 g_Net_Client_HandlePacket(NetEvent
.packet
, g_Net_ClientMsgHandler
);
1852 ENET_EVENT_TYPE_DISCONNECT
:
1854 g_Net_Disconnect(True);
1862 function g_Net_Client_UpdateWhileLoading(): enet_size_t
;
1865 while (enet_host_service(NetHost
, @NetEvent
, 0) > 0) do
1867 case NetEvent
.kind
of
1868 ENET_EVENT_TYPE_RECEIVE
:
1870 if (NetEvent
.channelID
= NET_CHAN_DOWNLOAD_EX
) then continue
; // ignore all download packets, they're processed by separate code
1871 if NetDump
then g_Net_DumpRecvBuffer(NetEvent
.packet
^.data
, NetEvent
.packet
^.dataLength
);
1872 g_Net_Client_HandlePacket(NetEvent
.packet
, g_Net_ClientLightMsgHandler
);
1875 ENET_EVENT_TYPE_DISCONNECT
:
1877 g_Net_Disconnect(True);
1886 function g_Net_Connect(IP
: string; Port
: enet_uint16
): Boolean;
1889 TimeoutTime
, T
: Int64;
1891 if NetMode
<> NET_NONE
then
1893 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_ERR_INGAME
], True);
1900 g_Console_Add(_lc
[I_NET_MSG
] + Format(_lc
[I_NET_MSG_CLIENT_CONN
],
1902 if not NetInitDone
then
1904 if (not g_Net_Init()) then
1906 g_Console_Add(_lc
[I_NET_MSG_FERROR
] + _lc
[I_NET_ERR_ENET
], True);
1911 NetInitDone
:= True;
1914 NetHost
:= enet_host_create(nil, 1, NET_CHANS
, 0, 0);
1916 if (NetHost
= nil) then
1918 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CLIENT
], True);
1924 enet_address_set_host(@NetAddr
, PChar(Addr(IP
[1])));
1925 NetAddr
.port
:= Port
;
1927 NetPeer
:= enet_host_connect(NetHost
, @NetAddr
, NET_CHANS
, NET_PROTOCOL_VER
);
1929 if (NetPeer
= nil) then
1931 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CLIENT
], True);
1932 enet_host_destroy(NetHost
);
1938 // предупредить что ждем слишком долго через N секунд
1939 TimeoutTime
:= sys_GetTicks() + NET_CONNECT_TIMEOUT
;
1944 while (enet_host_service(NetHost
, @NetEvent
, 0) > 0) do
1946 if (NetEvent
.kind
= ENET_EVENT_TYPE_CONNECT
) then
1948 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_CLIENT_DONE
]);
1949 NetMode
:= NET_CLIENT
;
1951 enet_peer_timeout(NetPeer
, ENET_PEER_TIMEOUT_LIMIT
* 2, ENET_PEER_TIMEOUT_MINIMUM
* 2, ENET_PEER_TIMEOUT_MAXIMUM
* 2);
1953 NetClientPort
:= Port
;
1960 T
:= sys_GetTicks();
1961 if T
> TimeoutTime
then
1963 TimeoutTime
:= T
+ NET_CONNECT_TIMEOUT
* 100; // одного предупреждения хватит
1964 g_Console_Add(_lc
[I_NET_MSG_TIMEOUT_WARN
], True);
1965 g_Console_Add(Format(_lc
[I_NET_MSG_PORTS
], [Integer(Port
), Integer(NET_PING_PORT
)]), True);
1968 ProcessLoading(true);
1970 if e_KeyPressed(IK_SPACE
) or e_KeyPressed(IK_ESCAPE
) or e_KeyPressed(VK_ESCAPE
) or
1971 e_KeyPressed(JOY0_JUMP
) or e_KeyPressed(JOY1_JUMP
) or e_KeyPressed(JOY2_JUMP
) or e_KeyPressed(JOY3_JUMP
) then
1975 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_TIMEOUT
], True);
1976 g_Console_Add(Format(_lc
[I_NET_MSG_PORTS
], [Integer(Port
), Integer(NET_PING_PORT
)]), True);
1977 if NetPeer
<> nil then enet_peer_reset(NetPeer
);
1978 if NetHost
<> nil then
1980 enet_host_destroy(NetHost
);
1987 function IpToStr(IP
: LongWord): string;
1992 Result
:= IntToStr(PByte(Ptr
+ 0)^) + '.';
1993 Result
:= Result
+ IntToStr(PByte(Ptr
+ 1)^) + '.';
1994 Result
:= Result
+ IntToStr(PByte(Ptr
+ 2)^) + '.';
1995 Result
:= Result
+ IntToStr(PByte(Ptr
+ 3)^);
1998 function StrToIp(IPstr
: string; var IP
: LongWord): Boolean;
2002 Result
:= enet_address_set_host(@EAddr
, PChar(@IPstr
[1])) = 0;
2006 function g_Net_Client_ByName(Name
: string): pTNetClient
;
2012 for a
:= Low(NetClients
) to High(NetClients
) do
2013 if (NetClients
[a
].Used
) and (NetClients
[a
].State
= NET_STATE_GAME
) then
2015 pl
:= g_Player_Get(NetClients
[a
].Player
);
2016 if pl
= nil then continue
;
2017 if Copy(LowerCase(pl
.Name
), 1, Length(Name
)) <> LowerCase(Name
) then continue
;
2018 if NetClients
[a
].Peer
<> nil then
2020 Result
:= @NetClients
[a
];
2026 function g_Net_Client_ByPlayer(PID
: Word): pTNetClient
;
2031 for a
:= Low(NetClients
) to High(NetClients
) do
2032 if (NetClients
[a
].Used
) and (NetClients
[a
].State
= NET_STATE_GAME
) then
2033 if NetClients
[a
].Player
= PID
then
2035 Result
:= @NetClients
[a
];
2040 function g_Net_ClientName_ByID(ID
: Integer): string;
2046 if ID
= NET_EVERYONE
then
2048 for a
:= Low(NetClients
) to High(NetClients
) do
2049 if (NetClients
[a
].ID
= ID
) and (NetClients
[a
].Used
) and (NetClients
[a
].State
= NET_STATE_GAME
) then
2051 pl
:= g_Player_Get(NetClients
[a
].Player
);
2052 if pl
= nil then Exit
;
2057 procedure g_Net_SendData(Data
: AByte
; peer
: pENetPeer
; Reliable
: Boolean; Chan
: Byte = NET_CHAN_DOWNLOAD
);
2061 dataLength
: Cardinal;
2063 dataLength
:= Length(Data
);
2066 F
:= LongWord(ENET_PACKET_FLAG_RELIABLE
)
2070 if (peer
<> nil) then
2072 P
:= enet_packet_create(@Data
[0], dataLength
, F
);
2073 if not Assigned(P
) then Exit
;
2074 enet_peer_send(peer
, Chan
, P
);
2078 P
:= enet_packet_create(@Data
[0], dataLength
, F
);
2079 if not Assigned(P
) then Exit
;
2080 enet_host_broadcast(NetHost
, Chan
, P
);
2083 enet_host_flush(NetHost
);
2086 function g_Net_IsHostBanned(IP
: LongWord; Perm
: Boolean = False): Boolean;
2091 if NetBannedHosts
= nil then
2093 for I
:= 0 to High(NetBannedHosts
) do
2094 if (NetBannedHosts
[I
].IP
= IP
) and ((not Perm
) or (NetBannedHosts
[I
].Perm
)) then
2101 procedure g_Net_BanHost(IP
: LongWord; Perm
: Boolean = True); overload
;
2107 if g_Net_IsHostBanned(IP
, Perm
) then
2111 for I
:= Low(NetBannedHosts
) to High(NetBannedHosts
) do
2112 if NetBannedHosts
[I
].IP
= 0 then
2120 SetLength(NetBannedHosts
, Length(NetBannedHosts
) + 1);
2121 P
:= High(NetBannedHosts
);
2124 NetBannedHosts
[P
].IP
:= IP
;
2125 NetBannedHosts
[P
].Perm
:= Perm
;
2128 procedure g_Net_BanHost(IP
: string; Perm
: Boolean = True); overload
;
2133 b
:= StrToIp(IP
, a
);
2135 g_Net_BanHost(a
, Perm
);
2138 procedure g_Net_UnbanNonPermHosts();
2142 if NetBannedHosts
= nil then
2144 for I
:= Low(NetBannedHosts
) to High(NetBannedHosts
) do
2145 if (NetBannedHosts
[I
].IP
> 0) and not NetBannedHosts
[I
].Perm
then
2147 NetBannedHosts
[I
].IP
:= 0;
2148 NetBannedHosts
[I
].Perm
:= True;
2152 function g_Net_UnbanHost(IP
: string): Boolean; overload
;
2156 Result
:= StrToIp(IP
, a
);
2158 Result
:= g_Net_UnbanHost(a
);
2161 function g_Net_UnbanHost(IP
: LongWord): Boolean; overload
;
2168 if NetBannedHosts
= nil then
2170 for I
:= 0 to High(NetBannedHosts
) do
2171 if NetBannedHosts
[I
].IP
= IP
then
2173 NetBannedHosts
[I
].IP
:= 0;
2174 NetBannedHosts
[I
].Perm
:= True;
2176 // no break here to clear all bans of this host, perm and non-perm
2180 procedure g_Net_SaveBanList();
2186 path
:= e_GetWriteableDir(DataDirs
);
2189 path
:= e_CatPath(path
, BANLIST_FILENAME
);
2192 if NetBannedHosts
<> nil then
2193 for I
:= 0 to High(NetBannedHosts
) do
2194 if NetBannedHosts
[I
].Perm
and (NetBannedHosts
[I
].IP
> 0) then
2195 Writeln(F
, IpToStr(NetBannedHosts
[I
].IP
));
2200 procedure g_Net_DumpStart();
2202 if NetMode
= NET_SERVER
then
2203 NetDumpFile
:= e_CreateResource(LogDirs
, NETDUMP_FILENAME
+ '_server')
2205 NetDumpFile
:= e_CreateResource(LogDirs
, NETDUMP_FILENAME
+ '_client');
2208 procedure g_Net_DumpSendBuffer();
2210 writeInt(NetDumpFile
, gTime
);
2211 writeInt(NetDumpFile
, LongWord(NetOut
.CurSize
));
2212 writeInt(NetDumpFile
, Byte(1));
2213 NetDumpFile
.WriteBuffer(NetOut
.Data
^, NetOut
.CurSize
);
2216 procedure g_Net_DumpRecvBuffer(Buf
: penet_uint8
; Len
: LongWord);
2218 if (Buf
= nil) or (Len
= 0) then Exit
;
2219 writeInt(NetDumpFile
, gTime
);
2220 writeInt(NetDumpFile
, Len
);
2221 writeInt(NetDumpFile
, Byte(0));
2222 NetDumpFile
.WriteBuffer(Buf
^, Len
);
2225 procedure g_Net_DumpEnd();
2231 function g_Net_ForwardPorts(ForwardPongPort
: Boolean = True): Boolean;
2232 {$IFDEF USE_MINIUPNPC}
2237 LanAddr
: array [0..255] of Char;
2238 StrPort
: AnsiString;
2243 if NetHost
= nil then
2246 if NetPortForwarded
= NetHost
.address
.port
then
2252 NetPongForwarded
:= False;
2253 NetPortForwarded
:= 0;
2255 DevList
:= upnpDiscover(1000, nil, nil, 0, 0, 2, Addr(Err
));
2256 if DevList
= nil then
2258 conwritefln('port forwarding failed: upnpDiscover() failed: %d', [Err
]);
2262 I
:= UPNP_GetValidIGD(DevList
, @Urls
, @Data
, Addr(LanAddr
[0]), 256);
2266 conwriteln('port forwarding failed: could not find an IGD device on this LAN');
2267 FreeUPNPDevList(DevList
);
2268 FreeUPNPUrls(@Urls
);
2272 StrPort
:= IntToStr(NetHost
.address
.port
);
2273 I
:= UPNP_AddPortMapping(
2274 Urls
.controlURL
, Addr(data
.first
.servicetype
[1]),
2275 PChar(StrPort
), PChar(StrPort
), Addr(LanAddr
[0]), PChar('D2DF'),
2276 PChar('UDP'), nil, PChar('0')
2281 conwritefln('forwarding port %d failed: error %d', [NetHost
.address
.port
, I
]);
2282 FreeUPNPDevList(DevList
);
2283 FreeUPNPUrls(@Urls
);
2287 if ForwardPongPort
then
2289 StrPort
:= IntToStr(NET_PING_PORT
);
2290 I
:= UPNP_AddPortMapping(
2291 Urls
.controlURL
, Addr(data
.first
.servicetype
[1]),
2292 PChar(StrPort
), PChar(StrPort
), Addr(LanAddr
[0]), PChar('D2DF'),
2293 PChar('UDP'), nil, PChar('0')
2298 conwritefln('forwarding port %d failed: error %d', [NET_PING_PORT
, I
]);
2299 NetPongForwarded
:= False;
2303 conwritefln('forwarded port %d successfully', [NET_PING_PORT
]);
2304 NetPongForwarded
:= True;
2308 conwritefln('forwarded port %d successfully', [NetHost
.address
.port
]);
2309 NetIGDControl
:= AnsiString(Urls
.controlURL
);
2310 NetIGDService
:= data
.first
.servicetype
;
2311 NetPortForwarded
:= NetHost
.address
.port
;
2313 FreeUPNPDevList(DevList
);
2314 FreeUPNPUrls(@Urls
);
2323 procedure g_Net_UnforwardPorts();
2324 {$IFDEF USE_MINIUPNPC}
2327 StrPort
: AnsiString;
2329 if NetPortForwarded
= 0 then Exit
;
2331 conwriteln('unforwarding ports...');
2333 StrPort
:= IntToStr(NetPortForwarded
);
2334 I
:= UPNP_DeletePortMapping(
2335 PChar(NetIGDControl
), Addr(NetIGDService
[1]),
2336 PChar(StrPort
), PChar('UDP'), nil
2338 conwritefln(' port %d: %d', [NetPortForwarded
, I
]);
2340 if NetPongForwarded
then
2342 NetPongForwarded
:= False;
2343 StrPort
:= IntToStr(NET_PING_PORT
);
2344 I
:= UPNP_DeletePortMapping(
2345 PChar(NetIGDControl
), Addr(NetIGDService
[1]),
2346 PChar(StrPort
), PChar('UDP'), nil
2348 conwritefln(' port %d: %d', [NET_PING_PORT
, I
]);
2351 NetPortForwarded
:= 0;
2358 procedure NetServerCVars(P
: SSArray
);
2363 cmd
:= LowerCase(P
[0]);
2367 if (Length(P
) > 1) and (Length(P
[1]) > 0) then
2369 NetServerName
:= P
[1];
2370 if Length(NetServerName
) > 64 then
2371 SetLength(NetServerName
, 64);
2372 g_Net_Slist_ServerRenamed();
2374 g_Console_Add(cmd
+ ' "' + NetServerName
+ '"');
2378 if (Length(P
) > 1) and (Length(P
[1]) > 0) then
2380 NetPassword
:= P
[1];
2381 if Length(NetPassword
) > 24 then
2382 SetLength(NetPassword
, 24);
2383 g_Net_Slist_ServerRenamed();
2385 g_Console_Add(cmd
+ ' "' + AnsiLowerCase(NetPassword
) + '"');
2389 if (Length(P
) > 1) then
2391 NetMaxClients
:= nclamp(StrToIntDef(P
[1], NetMaxClients
), 1, NET_MAXCLIENTS
);
2392 if g_Game_IsServer
and g_Game_IsNet
then
2395 for a
:= 0 to High(NetClients
) do
2397 if NetClients
[a
].Used
then
2400 if b
> NetMaxClients
then
2402 s
:= g_Player_Get(NetClients
[a
].Player
).Name
;
2403 enet_peer_disconnect(NetClients
[a
].Peer
, NET_DISC_FULL
);
2404 g_Console_Add(Format(_lc
[I_PLAYER_KICK
], [s
]));
2405 MH_SEND_GameEvent(NET_EV_PLAYER_KICK
, 0, s
);
2409 g_Net_Slist_ServerRenamed();
2412 g_Console_Add(cmd
+ ' ' + IntToStr(NetMaxClients
));
2416 if (Length(P
) > 1) then
2418 NetUseMaster
:= StrToIntDef(P
[1], Byte(NetUseMaster
)) <> 0;
2419 if NetUseMaster
then g_Net_Slist_Public() else g_Net_Slist_Private();
2421 g_Console_Add(cmd
+ ' ' + IntToStr(Byte(NetUseMaster
)));
2425 if (Length(P
) > 1) then
2427 if not g_Game_IsNet
then
2428 NetPort
:= nclamp(StrToIntDef(P
[1], NetPort
), 0, $FFFF)
2430 g_Console_Add(_lc
[I_MSG_NOT_NETGAME
]);
2432 g_Console_Add(cmd
+ ' ' + IntToStr(Ord(NetUseMaster
)));
2438 conRegVar('cl_downloadtimeout', @g_Net_DownloadTimeout
, 0.0, 1000000.0, '', 'timeout in seconds, 0 to disable it');
2439 conRegVar('cl_predictself', @NetPredictSelf
, '', 'predict local player');
2440 conRegVar('cl_forceplayerupdate', @NetForcePlayerUpdate
, '', 'update net players on NET_MSG_PLRPOS');
2441 conRegVar('cl_interp', @NetInterpLevel
, '', 'net player interpolation steps');
2442 conRegVar('cl_last_ip', @NetClientIP
, '', 'address of the last you have connected to');
2443 conRegVar('cl_last_port', @NetClientPort
, '', 'port of the last server you have connected to');
2444 conRegVar('cl_deafen', @NetDeafLevel
, '', 'filter server messages (0-3)');
2446 conRegVar('sv_forwardports', @NetForwardPorts
, '', 'forward server port using miniupnpc (requires server restart)');
2447 conRegVar('sv_rcon', @NetAllowRCON
, '', 'enable remote console');
2448 conRegVar('sv_rcon_password', @NetRCONPassword
, '', 'remote console password');
2449 conRegVar('sv_update_interval', @NetUpdateRate
, '', 'unreliable update interval');
2450 conRegVar('sv_reliable_interval', @NetRelupdRate
, '', 'reliable update interval');
2451 conRegVar('sv_master_interval', @NetMasterRate
, '', 'master server update interval');
2453 conRegVar('net_master_list', @NetMasterList
, '', 'list of master servers');
2455 SetLength(NetClients
, 0);
2456 g_Net_DownloadTimeout
:= 60;
2457 NetIn
.Alloc(NET_BUFSIZE
);
2458 NetOut
.Alloc(NET_BUFSIZE
);
2459 NetBuf
[NET_UNRELIABLE
].Alloc(NET_BUFSIZE
*2);
2460 NetBuf
[NET_RELIABLE
].Alloc(NET_BUFSIZE
*2);
2461 trans_omsg
.Alloc(NET_BUFSIZE
);
2465 NetBuf
[NET_UNRELIABLE
].Free();
2466 NetBuf
[NET_RELIABLE
].Free();