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
, ENet
, Classes
, md5
, MAPDEF
{$IFDEF USE_MINIUPNPC}, miniupnpc
;{$ELSE};{$ENDIF}
24 NET_PROTOCOL_VER
= 182;
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';
75 {$IF DEFINED(FREEBSD) OR DEFINED(DARWIN)}
82 TNetMapResourceInfo
= record
83 wadName
: AnsiString; // wad file name, without a path
84 size
: Integer; // wad file size (-1: size and hash are not known)
85 hash
: TMD5Digest
; // wad hash
88 TNetMapResourceInfoArray
= array of TNetMapResourceInfo
;
90 TNetFileTransfer
= record
94 size
: Integer; // file size in bytes
96 lastSentChunk
: Integer;
97 lastAckChunk
: Integer;
98 lastAckTime
: Int64; // msecs; if not "in progress", we're waiting for the first ack
100 diskBuffer
: PChar; // of `chunkSize` bytes
110 RequestedFullUpdate
: Boolean;
111 WaitForFirstSpawn
: Boolean; // set to `true` in server, used to spawn a player on first full state request
114 Transfer
: TNetFileTransfer
; // only one transfer may be active
115 NetOut
: array [0..1] of TMsg
;
121 pTNetClient
= ^TNetClient
;
123 AByte
= array of Byte;
126 NetInitDone
: Boolean = False;
127 NetMode
: Byte = NET_NONE
;
128 NetDump
: Boolean = False;
130 NetServerName
: string = 'Unnamed Server';
131 NetPassword
: string = '';
132 NetPort
: Word = 25666;
134 NetAllowRCON
: Boolean = False;
135 NetRCONPassword
: string = '';
137 NetTimeToUpdate
: Cardinal = 0;
138 NetTimeToReliable
: Cardinal = 0;
139 NetTimeToMaster
: Cardinal = 0;
141 NetHost
: pENetHost
= nil;
142 NetPeer
: pENetPeer
= nil;
144 NetAddr
: ENetAddress
;
146 NetPongAddr
: ENetAddress
;
147 NetPongSock
: ENetSocket
= ENET_SOCKET_NULL
;
149 NetUseMaster
: Boolean = True;
150 NetSlistAddr
: ENetAddress
;
151 NetSlistIP
: string = 'mpms.doom2d.org';
152 NetSlistPort
: Word = 25665;
154 NetClientIP
: string = '127.0.0.1';
155 NetClientPort
: Word = 25666;
158 NetBuf
: array [0..1] of TMsg
;
160 NetClients
: array of TNetClient
;
161 NetClientCount
: Byte = 0;
162 NetMaxClients
: Byte = 255;
163 NetBannedHosts
: array of TBanRecord
;
165 NetState
: Integer = NET_STATE_NONE
;
167 NetMyID
: Integer = -1;
168 NetPlrUID1
: Integer = -1;
169 NetPlrUID2
: Integer = -1;
171 NetInterpLevel
: Integer = 1;
172 NetUpdateRate
: Cardinal = 0; // as soon as possible
173 NetRelupdRate
: Cardinal = 18; // around two times a second
174 NetMasterRate
: Cardinal = 60000;
176 NetForcePlayerUpdate
: Boolean = False;
177 NetPredictSelf
: Boolean = True;
178 NetForwardPorts
: Boolean = False;
180 NetGotEverything
: Boolean = False;
181 NetGotKeys
: Boolean = False;
183 {$IFDEF USE_MINIUPNPC}
184 NetPortForwarded
: Word = 0;
185 NetPongForwarded
: Boolean = False;
186 NetIGDControl
: AnsiString;
187 NetIGDService
: TURLStr
;
190 NetPortThread
: TThreadID
= NilThreadId
;
192 NetDumpFile
: TStream
;
194 g_Res_received_map_start
: Integer = 0; // set if we received "map change" event
197 function g_Net_Init(): Boolean;
198 procedure g_Net_Cleanup();
199 procedure g_Net_Free();
200 procedure g_Net_Flush();
202 function g_Net_Host(IPAddr
: LongWord; Port
: enet_uint16
; MaxClients
: Cardinal = 16): Boolean;
203 procedure g_Net_Host_Die();
204 procedure g_Net_Host_Send(ID
: Integer; Reliable
: Boolean; Chan
: Byte = NET_CHAN_GAME
);
205 function g_Net_Host_Update(): enet_size_t
;
207 function g_Net_Connect(IP
: string; Port
: enet_uint16
): Boolean;
208 procedure g_Net_Disconnect(Forced
: Boolean = False);
209 procedure g_Net_Client_Send(Reliable
: Boolean; Chan
: Byte = NET_CHAN_GAME
);
210 function g_Net_Client_Update(): enet_size_t
;
211 function g_Net_Client_UpdateWhileLoading(): enet_size_t
;
213 function g_Net_Client_ByName(Name
: string): pTNetClient
;
214 function g_Net_Client_ByPlayer(PID
: Word): pTNetClient
;
215 function g_Net_ClientName_ByID(ID
: Integer): string;
217 procedure g_Net_SendData(Data
: AByte
; peer
: pENetPeer
; Reliable
: Boolean; Chan
: Byte = NET_CHAN_DOWNLOAD
);
218 //function g_Net_Wait_Event(msgId: Word): TMemoryStream;
219 //function g_Net_Wait_FileInfo (var tf: TNetFileTransfer; asMap: Boolean; out resList: TStringList): Integer;
221 function IpToStr(IP
: LongWord): string;
222 function StrToIp(IPstr
: string; var IP
: LongWord): Boolean;
224 function g_Net_IsHostBanned(IP
: LongWord; Perm
: Boolean = False): Boolean;
225 procedure g_Net_BanHost(IP
: LongWord; Perm
: Boolean = True); overload
;
226 procedure g_Net_BanHost(IP
: string; Perm
: Boolean = True); overload
;
227 function g_Net_UnbanHost(IP
: string): Boolean; overload
;
228 function g_Net_UnbanHost(IP
: LongWord): Boolean; overload
;
229 procedure g_Net_UnbanNonPermHosts();
230 procedure g_Net_SaveBanList();
232 procedure g_Net_DumpStart();
233 procedure g_Net_DumpSendBuffer();
234 procedure g_Net_DumpRecvBuffer(Buf
: penet_uint8
; Len
: LongWord);
235 procedure g_Net_DumpEnd();
237 function g_Net_ForwardPorts(ForwardPongPort
: Boolean = True): Boolean;
238 procedure g_Net_UnforwardPorts();
240 function g_Net_UserRequestExit
: Boolean;
242 function g_Net_Wait_MapInfo (var tf
: TNetFileTransfer
; var resList
: TNetMapResourceInfoArray
): Integer;
243 function g_Net_RequestResFileInfo (resIndex
: LongInt; out tf
: TNetFileTransfer
): Integer;
244 function g_Net_AbortResTransfer (var tf
: TNetFileTransfer
): Boolean;
245 function g_Net_ReceiveResourceFile (resIndex
: LongInt; var tf
: TNetFileTransfer
; strm
: TStream
): Integer;
252 e_input
, g_nethandler
, g_netmsg
, g_netmaster
, g_player
, g_window
, g_console
,
253 g_main
, g_game
, g_language
, g_weapons
, utils
, ctypes
, g_system
,
257 FILE_CHUNK_SIZE
= 8192;
260 g_Net_DownloadTimeout
: Single;
264 //**************************************************************************
268 //**************************************************************************
270 procedure clearNetClientTransfers (var nc
: TNetClient
);
272 nc
.Transfer
.stream
.Free
;
273 nc
.Transfer
.diskName
:= ''; // just in case
274 if (nc
.Transfer
.diskBuffer
<> nil) then FreeMem(nc
.Transfer
.diskBuffer
);
275 nc
.Transfer
.stream
:= nil;
276 nc
.Transfer
.diskBuffer
:= nil;
280 procedure clearNetClient (var nc
: TNetClient
);
282 clearNetClientTransfers(nc
);
286 procedure clearNetClients (clearArray
: Boolean);
290 for f
:= Low(NetClients
) to High(NetClients
) do clearNetClient(NetClients
[f
]);
291 if (clearArray
) then SetLength(NetClients
, 0);
295 function g_Net_UserRequestExit (): Boolean;
297 Result
:= {e_KeyPressed(IK_SPACE) or}
298 e_KeyPressed(IK_ESCAPE
) or
299 e_KeyPressed(VK_ESCAPE
) or
300 e_KeyPressed(JOY0_JUMP
) or
301 e_KeyPressed(JOY1_JUMP
) or
302 e_KeyPressed(JOY2_JUMP
) or
303 e_KeyPressed(JOY3_JUMP
)
307 //**************************************************************************
309 // file transfer declaraions and host packet processor
311 //**************************************************************************
314 // server packet type
315 NTF_SERVER_DONE
= 10; // done with this file
316 NTF_SERVER_FILE_INFO
= 11; // sent after client request
317 NTF_SERVER_CHUNK
= 12; // next chunk; chunk number follows
318 NTF_SERVER_ABORT
= 13; // server abort
319 NTF_SERVER_MAP_INFO
= 14;
321 // client packet type
322 NTF_CLIENT_MAP_REQUEST
= 100; // map file request; also, returns list of additional wads to download
323 NTF_CLIENT_FILE_REQUEST
= 101; // resource file request (by index)
324 NTF_CLIENT_ABORT
= 102; // do not send requested file, or abort current transfer
325 NTF_CLIENT_START
= 103; // start transfer; client may resume download by sending non-zero starting chunk
326 NTF_CLIENT_ACK
= 104; // chunk ack; chunk number follows
329 // disconnect client due to some file transfer error
330 procedure killClientByFT (var nc
: TNetClient
);
332 e_LogWritefln('disconnected client #%d due to file transfer error', [nc
.ID
], TMsgType
.Warning
);
333 enet_peer_disconnect(nc
.Peer
, NET_DISC_FILE_TIMEOUT
);
334 clearNetClientTransfers(nc
);
338 // send file transfer message from server to client
339 function ftransSendServerMsg (var nc
: TNetClient
; var m
: TMsg
): Boolean;
344 if (m
.CurSize
< 1) then exit
;
345 pkt
:= enet_packet_create(m
.Data
, m
.CurSize
, ENET_PACKET_FLAG_RELIABLE
);
346 if not Assigned(pkt
) then begin killClientByFT(nc
); exit
; end;
347 if (enet_peer_send(nc
.Peer
, NET_CHAN_DOWNLOAD_EX
, pkt
) <> 0) then begin killClientByFT(nc
); exit
; end;
352 // send file transfer message from client to server
353 function ftransSendClientMsg (var m
: TMsg
): Boolean;
358 if (m
.CurSize
< 1) then exit
;
359 pkt
:= enet_packet_create(m
.Data
, m
.CurSize
, ENET_PACKET_FLAG_RELIABLE
);
360 if not Assigned(pkt
) then exit
;
361 if (enet_peer_send(NetPeer
, NET_CHAN_DOWNLOAD_EX
, pkt
) <> 0) then exit
;
367 procedure ProcessChunkSend (var nc
: TNetClient
);
369 tf
: ^TNetFileTransfer
;
375 if (tf
.stream
= nil) then exit
;
377 // arbitrary timeout number
378 if (ct
-tf
.lastAckTime
>= 5000) then
383 // check if we need to send something
384 if (not tf
.inProgress
) then exit
; // waiting for the initial ack
385 // ok, we're sending chunks
386 if (tf
.lastAckChunk
<> tf
.lastSentChunk
) then exit
;
387 Inc(tf
.lastSentChunk
);
388 // do it one chunk at a time; client ack will advance our chunk counter
389 chunks
:= (tf
.size
+tf
.chunkSize
-1) div tf
.chunkSize
;
391 if (tf
.lastSentChunk
> chunks
) then
398 if (tf
.lastSentChunk
= chunks
) then
400 // we're done with this file
401 e_LogWritefln('download: client #%d, DONE sending chunks #%d/#%d', [nc
.ID
, tf
.lastSentChunk
, chunks
]);
402 trans_omsg
.Write(Byte(NTF_SERVER_DONE
));
403 clearNetClientTransfers(nc
);
408 trans_omsg
.Write(Byte(NTF_SERVER_CHUNK
));
409 trans_omsg
.Write(LongInt(tf
.lastSentChunk
));
411 rd
:= tf
.size
-(tf
.lastSentChunk
*tf
.chunkSize
);
412 if (rd
> tf
.chunkSize
) then rd
:= tf
.chunkSize
;
413 trans_omsg
.Write(LongInt(rd
));
414 //e_LogWritefln('download: client #%d, sending chunk #%d/#%d (%d bytes)', [nc.ID, tf.lastSentChunk, chunks, rd]);
415 //FIXME: check for errors here
417 tf
.stream
.Seek(tf
.lastSentChunk
*tf
.chunkSize
, soFromBeginning
);
418 tf
.stream
.ReadBuffer(tf
.diskBuffer
^, rd
);
419 trans_omsg
.WriteData(tf
.diskBuffer
, rd
);
426 ftransSendServerMsg(nc
, trans_omsg
);
430 // server file transfer packet processor
431 // received packet is in `NetEvent`
432 procedure ProcessDownloadExPacket ();
439 tf
: ^TNetFileTransfer
;
449 // find client index by peer
450 for f
:= Low(NetClients
) to High(NetClients
) do
452 if (not NetClients
[f
].Used
) then continue
;
453 if (NetClients
[f
].Peer
= NetEvent
.peer
) then
459 //e_LogWritefln('RECEIVE: dlpacket; client=%d (datalen=%u)', [nid, NetEvent.packet^.dataLength]);
461 if (nid
< 0) then exit
; // wtf?!
462 nc
:= @NetClients
[nid
];
464 if (NetEvent
.packet
^.dataLength
= 0) then
470 tf
:= @NetClients
[nid
].Transfer
;
471 tf
.lastAckTime
:= GetTimerMS();
473 cmd
:= Byte(NetEvent
.packet
^.data
^);
474 //e_LogWritefln('RECEIVE: nid=%d; cmd=%u', [nid, cmd]);
476 NTF_CLIENT_FILE_REQUEST
: // file request
478 if (tf
.stream
<> nil) then
483 if (NetEvent
.packet
^.dataLength
< 2) then
488 // new transfer request; build packet
489 if not msg
.Init(NetEvent
.packet
^.data
+1, NetEvent
.packet
^.dataLength
-1, True) then
494 // get resource index
495 ridx
:= msg
.ReadLongInt();
496 if (ridx
< -1) or (ridx
>= length(gExternalResources
)) then
498 e_LogWritefln('Invalid resource index %d', [ridx
], TMsgType
.Warning
);
502 if (ridx
< 0) then fname
:= MapsDir
+gGameSettings
.WAD
else fname
:= {GameDir+'/wads/'+}gExternalResources
[ridx
].diskName
;
503 if (length(fname
) = 0) then
505 e_WriteLog('Invalid filename: '+fname
, TMsgType
.Warning
);
509 tf
.diskName
:= findDiskWad(fname
);
510 //if (length(tf.diskName) = 0) then tf.diskName := findDiskWad(GameDir+'/wads/'+fname);
511 if (length(tf
.diskName
) = 0) then
513 e_LogWritefln('NETWORK: file "%s" not found!', [fname
], TMsgType
.Fatal
);
518 //tf.hash := MD5File(tf.diskName);
519 if (ridx
< 0) then tf
.hash
:= gWADHash
else tf
.hash
:= gExternalResources
[ridx
].hash
;
520 // create file stream
521 tf
.diskName
:= findDiskWad(fname
);
523 tf
.stream
:= openDiskFileRO(tf
.diskName
);
527 if (tf
.stream
= nil) then
529 e_WriteLog(Format('NETWORK: file "%s" not found!', [fname
]), TMsgType
.Fatal
);
533 e_LogWritefln('client #%d requested resource #%d (file is `%s` : `%s`)', [nc
.ID
, ridx
, fname
, tf
.diskName
]);
534 tf
.size
:= tf
.stream
.size
;
535 tf
.chunkSize
:= FILE_CHUNK_SIZE
; // arbitrary
536 tf
.lastSentChunk
:= -1;
537 tf
.lastAckChunk
:= -1;
538 tf
.lastAckTime
:= GetTimerMS();
539 tf
.inProgress
:= False; // waiting for the first ACK or for the cancel
540 GetMem(tf
.diskBuffer
, tf
.chunkSize
);
541 // sent file info message
543 trans_omsg
.Write(Byte(NTF_SERVER_FILE_INFO
));
544 trans_omsg
.Write(tf
.hash
);
545 trans_omsg
.Write(tf
.size
);
546 trans_omsg
.Write(tf
.chunkSize
);
547 trans_omsg
.Write(ExtractFileName(fname
));
548 if not ftransSendServerMsg(nc
^, trans_omsg
) then exit
;
550 NTF_CLIENT_ABORT
: // do not send requested file, or abort current transfer
552 e_LogWritefln('client #%d aborted file transfer', [nc
.ID
]);
553 clearNetClientTransfers(nc
^);
555 NTF_CLIENT_START
: // start transfer; client may resume download by sending non-zero starting chunk
557 if not Assigned(tf
.stream
) then
562 if (tf
.lastSentChunk
<> -1) or (tf
.lastAckChunk
<> -1) or (tf
.inProgress
) then
564 // double ack, get lost
568 if (NetEvent
.packet
^.dataLength
< 2) then
574 if not msg
.Init(NetEvent
.packet
^.data
+1, NetEvent
.packet
^.dataLength
-1, True) then
579 chunk
:= msg
.ReadLongInt();
580 if (chunk
< 0) or (chunk
> (tf
.size
+tf
.chunkSize
-1) div tf
.chunkSize
) then
585 e_LogWritefln('client #%d started file transfer from chunk %d', [nc
.ID
, chunk
]);
586 // start sending chunks
587 tf
.inProgress
:= True;
588 tf
.lastSentChunk
:= chunk
-1;
589 tf
.lastAckChunk
:= chunk
-1;
590 ProcessChunkSend(nc
^);
592 NTF_CLIENT_ACK
: // chunk ack; chunk number follows
594 if not Assigned(tf
.stream
) then
599 if (tf
.lastSentChunk
< 0) or (not tf
.inProgress
) then
601 // double ack, get lost
605 if (NetEvent
.packet
^.dataLength
< 2) then
611 if not msg
.Init(NetEvent
.packet
^.data
+1, NetEvent
.packet
^.dataLength
-1, True) then
616 chunk
:= msg
.ReadLongInt();
617 if (chunk
< 0) or (chunk
> (tf
.size
+tf
.chunkSize
-1) div tf
.chunkSize
) then
622 // do it this way, so client may seek, or request retransfers for some reason
623 tf
.lastAckChunk
:= chunk
;
624 tf
.lastSentChunk
:= chunk
;
625 //e_LogWritefln('client #%d acked file transfer chunk %d', [nc.ID, chunk]);
626 ProcessChunkSend(nc
^);
628 NTF_CLIENT_MAP_REQUEST
:
630 e_LogWritefln('client #%d requested map info', [nc
.ID
]);
632 dfn
:= findDiskWad(MapsDir
+gGameSettings
.WAD
);
633 if (dfn
= '') then dfn
:= '!wad_not_found!.wad'; //FIXME
634 //md5 := MD5File(dfn);
636 if (not GetDiskFileInfo(dfn
, fi
)) then
638 e_LogWritefln('client #%d requested map info, but i cannot get file info', [nc
.ID
]);
644 st := openDiskFileRO(dfn);
645 if not assigned(st) then exit; //wtf?!
650 trans_omsg
.Write(Byte(NTF_SERVER_MAP_INFO
));
652 trans_omsg
.Write(gGameSettings
.WAD
);
654 trans_omsg
.Write(md5
);
656 trans_omsg
.Write(size
);
657 // number of external resources for map
658 trans_omsg
.Write(LongInt(length(gExternalResources
)));
659 // external resource names
660 for f
:= 0 to High(gExternalResources
) do
663 //trans_omsg.Write(ExtractFileName(gExternalResources[f])); // GameDir+'/wads/'+ResList.Strings[i]
665 trans_omsg
.Write('!');
666 trans_omsg
.Write(LongInt(gExternalResources
[f
].size
));
667 trans_omsg
.Write(gExternalResources
[f
].hash
);
668 trans_omsg
.Write(ExtractFileName(gExternalResources
[f
].diskName
));
671 if not ftransSendServerMsg(nc
^, trans_omsg
) then exit
;
682 //**************************************************************************
684 // file transfer crap (both client and server)
686 //**************************************************************************
688 function getNewTimeoutEnd (): Int64;
690 result
:= GetTimerMS();
691 if (g_Net_DownloadTimeout
<= 0) then
693 result
:= result
+1000*60*3; // 3 minutes
697 result
:= result
+trunc(g_Net_DownloadTimeout
*1000);
702 // send map request to server, and wait for "map info" server reply
704 // returns `false` on error or user abort
706 // diskName: map wad file name (without a path)
707 // hash: map wad hash
708 // size: map wad size
709 // chunkSize: set too
710 // resList: list of resource wads
716 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
717 function g_Net_Wait_MapInfo (var tf
: TNetFileTransfer
; var resList
: TNetMapResourceInfoArray
): Integer;
723 freePacket
: Boolean = false;
728 ri
: ^TNetMapResourceInfo
;
730 SetLength(resList
, 0);
734 trans_omsg
.Write(Byte(NTF_CLIENT_MAP_REQUEST
));
735 if not ftransSendClientMsg(trans_omsg
) then begin result
:= -1; exit
; end;
737 FillChar(ev
, SizeOf(ev
), 0);
740 ett
:= getNewTimeoutEnd();
742 status
:= enet_host_service(NetHost
, @ev
, 300);
745 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' network error', True);
755 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' timeout reached', True);
764 ENET_EVENT_TYPE_RECEIVE
:
767 if (ev
.channelID
<> NET_CHAN_DOWNLOAD_EX
) then
769 //e_LogWritefln('g_Net_Wait_MapInfo: skip message from non-transfer channel', []);
771 g_Net_Client_HandlePacket(ev
.packet
, g_Net_ClientLightMsgHandler
);
772 if (g_Res_received_map_start
< 0) then begin result
:= -666; exit
; end;
776 ett
:= getNewTimeoutEnd();
777 if (ev
.packet
.dataLength
< 1) then
779 e_LogWritefln('g_Net_Wait_MapInfo: invalid server packet (no data)', []);
783 Ptr
:= ev
.packet
^.data
;
784 rMsgId
:= Byte(Ptr
^);
785 e_LogWritefln('g_Net_Wait_MapInfo: got message %u from server (dataLength=%u)', [rMsgId
, ev
.packet
^.dataLength
]);
786 if (rMsgId
= NTF_SERVER_FILE_INFO
) then
788 e_LogWritefln('g_Net_Wait_MapInfo: waiting for map info reply, but got file info reply', []);
792 else if (rMsgId
= NTF_SERVER_ABORT
) then
794 e_LogWritefln('g_Net_Wait_MapInfo: server aborted transfer', []);
798 else if (rMsgId
= NTF_SERVER_MAP_INFO
) then
800 e_LogWritefln('g_Net_Wait_MapInfo: creating map info packet...', []);
801 if not msg
.Init(ev
.packet
^.data
+1, ev
.packet
^.dataLength
-1, True) then exit
;
802 e_LogWritefln('g_Net_Wait_MapInfo: parsing map info packet (rd=%d; max=%d)...', [msg
.ReadCount
, msg
.MaxSize
]);
803 SetLength(resList
, 0); // just in case
805 tf
.diskName
:= msg
.ReadString();
806 e_LogWritefln('g_Net_Wait_MapInfo: map wad is `%s`', [tf
.diskName
]);
808 tf
.hash
:= msg
.ReadMD5();
810 tf
.size
:= msg
.ReadLongInt();
811 e_LogWritefln('g_Net_Wait_MapInfo: map wad size is %d', [tf
.size
]);
812 // number of external resources for map
813 rc
:= msg
.ReadLongInt();
814 if (rc
< 0) or (rc
> 1024) then
816 e_LogWritefln('g_Net_Wait_Event: invalid number of map external resources (%d)', [rc
]);
820 e_LogWritefln('g_Net_Wait_MapInfo: map external resource count is %d', [rc
]);
821 SetLength(resList
, rc
);
822 // external resource names
823 for f
:= 0 to rc
-1 do
826 s
:= msg
.ReadString();
827 if (length(s
) = 0) then begin result
:= -1; exit
; end;
831 ri
.size
:= msg
.ReadLongInt();
832 ri
.hash
:= msg
.ReadMD5();
833 ri
.wadName
:= ExtractFileName(msg
.ReadString());
834 if (length(ri
.wadName
) = 0) or (ri
.size
< 0) then begin result
:= -1; exit
; end;
838 // old-style packet, only name
839 ri
.wadName
:= ExtractFileName(s
);
840 if (length(ri
.wadName
) = 0) then begin result
:= -1; exit
; end;
841 ri
.size
:= -1; // unknown
844 e_LogWritefln('g_Net_Wait_MapInfo: got map info', []);
845 Result
:= 0; // success
850 e_LogWritefln('g_Net_Wait_Event: invalid server packet type', []);
856 ENET_EVENT_TYPE_DISCONNECT
:
858 if (ev
.data
<= NET_DISC_MAX
) then
859 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' ' + _lc
[TStrings_Locale(Cardinal(I_NET_DISC_NONE
) + ev
.data
)], True);
865 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' unknown ENet event ' + IntToStr(Ord(ev
.kind
)), True);
870 if (freePacket
) then begin freePacket
:= false; enet_packet_destroy(ev
.packet
); end;
873 if g_Net_UserRequestExit() then
875 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' user abort', True);
881 if (freePacket
) then enet_packet_destroy(ev
.packet
);
886 // send file request to server, and wait for server reply
888 // returns `false` on error or user abort
890 // diskName (actually, base name)
899 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
900 function g_Net_RequestResFileInfo (resIndex
: LongInt; out tf
: TNetFileTransfer
): Integer;
906 freePacket
: Boolean = false;
912 trans_omsg
.Write(Byte(NTF_CLIENT_FILE_REQUEST
));
913 trans_omsg
.Write(resIndex
);
914 if not ftransSendClientMsg(trans_omsg
) then begin result
:= -1; exit
; end;
916 FillChar(ev
, SizeOf(ev
), 0);
919 ett
:= getNewTimeoutEnd();
921 status
:= enet_host_service(NetHost
, @ev
, 300);
924 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' network error', True);
934 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' timeout reached', True);
943 ENET_EVENT_TYPE_RECEIVE
:
946 if (ev
.channelID
<> NET_CHAN_DOWNLOAD_EX
) then
948 //e_LogWriteln('g_Net_Wait_Event: skip message from non-transfer channel');
950 g_Net_Client_HandlePacket(ev
.packet
, g_Net_ClientLightMsgHandler
);
951 if (g_Res_received_map_start
< 0) then begin result
:= -666; exit
; end;
955 ett
:= getNewTimeoutEnd();
956 if (ev
.packet
.dataLength
< 1) then
958 e_LogWriteln('g_Net_Wait_Event: invalid server packet (no data)');
962 Ptr
:= ev
.packet
^.data
;
963 rMsgId
:= Byte(Ptr
^);
964 e_LogWritefln('received transfer packet with id %d (%u bytes)', [rMsgId
, ev
.packet
^.dataLength
]);
965 if (rMsgId
= NTF_SERVER_FILE_INFO
) then
967 if not msg
.Init(ev
.packet
^.data
+1, ev
.packet
^.dataLength
-1, True) then exit
;
968 tf
.hash
:= msg
.ReadMD5();
969 tf
.size
:= msg
.ReadLongInt();
970 tf
.chunkSize
:= msg
.ReadLongInt();
971 tf
.diskName
:= ExtractFileName(msg
.readString());
972 if (tf
.size
< 0) or (tf
.chunkSize
<> FILE_CHUNK_SIZE
) or (length(tf
.diskName
) = 0) then
974 e_LogWritefln('g_Net_RequestResFileInfo: invalid file info packet', []);
978 e_LogWritefln('got file info for resource #%d: size=%d; name=%s', [resIndex
, tf
.size
, tf
.diskName
]);
979 Result
:= 0; // success
982 else if (rMsgId
= NTF_SERVER_ABORT
) then
984 e_LogWriteln('g_Net_RequestResFileInfo: server aborted transfer');
988 else if (rMsgId
= NTF_SERVER_MAP_INFO
) then
990 e_LogWriteln('g_Net_RequestResFileInfo: waiting for map info reply, but got file info reply');
996 e_LogWriteln('g_Net_RequestResFileInfo: invalid server packet type');
1002 ENET_EVENT_TYPE_DISCONNECT
:
1004 if (ev
.data
<= NET_DISC_MAX
) then
1005 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' ' + _lc
[TStrings_Locale(Cardinal(I_NET_DISC_NONE
) + ev
.data
)], True);
1011 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' unknown ENet event ' + IntToStr(Ord(ev
.kind
)), True);
1016 if (freePacket
) then begin freePacket
:= false; enet_packet_destroy(ev
.packet
); end;
1019 if g_Net_UserRequestExit() then
1021 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' user abort', True);
1027 if (freePacket
) then enet_packet_destroy(ev
.packet
);
1032 // call this to cancel file transfer requested by `g_Net_RequestResFileInfo()`
1033 function g_Net_AbortResTransfer (var tf
: TNetFileTransfer
): Boolean;
1036 e_LogWritefln('aborting file transfer...', []);
1039 trans_omsg
.Write(Byte(NTF_CLIENT_ABORT
));
1040 result
:= ftransSendClientMsg(trans_omsg
);
1041 if result
then enet_host_flush(NetHost
);
1045 // call this to start file transfer requested by `g_Net_RequestResFileInfo()`
1047 // returns `false` on error or user abort
1056 // 2 on server abort
1057 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
1058 function g_Net_ReceiveResourceFile (resIndex
: LongInt; var tf
: TNetFileTransfer
; strm
: TStream
): Integer;
1064 freePacket
: Boolean = false;
1067 nextChunk
: Integer = 0;
1068 chunkTotal
: Integer;
1075 tf
.resumed
:= false;
1076 e_LogWritefln('file `%s`, size=%d (%d)', [tf
.diskName
, Integer(strm
.size
), tf
.size
], TMsgType
.Notify
);
1077 // check if we should resume downloading
1078 resumed
:= (strm
.size
> tf
.chunkSize
) and (strm
.size
< tf
.size
);
1081 trans_omsg
.Write(Byte(NTF_CLIENT_START
));
1082 if resumed
then chunk
:= strm
.size
div tf
.chunkSize
else chunk
:= 0;
1083 trans_omsg
.Write(LongInt(chunk
));
1084 if not ftransSendClientMsg(trans_omsg
) then begin result
:= -1; exit
; end;
1086 strm
.Seek(chunk
*tf
.chunkSize
, soFromBeginning
);
1087 chunkTotal
:= (tf
.size
+tf
.chunkSize
-1) div tf
.chunkSize
;
1088 e_LogWritefln('receiving file `%s` (%d chunks)', [tf
.diskName
, chunkTotal
], TMsgType
.Notify
);
1089 g_Game_SetLoadingText('downloading "'+ExtractFileName(tf
.diskName
)+'"', chunkTotal
, False);
1090 tf
.resumed
:= resumed
;
1092 if (chunk
> 0) then g_Game_StepLoading(chunk
);
1095 // wait for reply data
1096 FillChar(ev
, SizeOf(ev
), 0);
1098 GetMem(buf
, tf
.chunkSize
);
1100 ett
:= getNewTimeoutEnd();
1102 //stx := -GetTimerMS();
1103 status
:= enet_host_service(NetHost
, @ev
, 300);
1104 if (status
< 0) then
1106 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' network error', True);
1110 if (status
= 0) then
1112 // check for timeout
1116 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' timeout reached', True);
1125 ENET_EVENT_TYPE_RECEIVE
:
1128 if (ev
.channelID
<> NET_CHAN_DOWNLOAD_EX
) then
1130 //e_LogWritefln('g_Net_Wait_Event: skip message from non-transfer channel', []);
1131 freePacket
:= false;
1132 g_Net_Client_HandlePacket(ev
.packet
, g_Net_ClientLightMsgHandler
);
1133 if (g_Res_received_map_start
< 0) then begin result
:= -666; exit
; end;
1137 //stx := stx+GetTimerMS();
1138 //e_LogWritefln('g_Net_ReceiveResourceFile: stx=%d', [Integer(stx)]);
1139 //stx := -GetTimerMS();
1140 ett
:= getNewTimeoutEnd();
1141 if (ev
.packet
.dataLength
< 1) then
1143 e_LogWritefln('g_Net_ReceiveResourceFile: invalid server packet (no data)', []);
1147 Ptr
:= ev
.packet
^.data
;
1148 rMsgId
:= Byte(Ptr
^);
1149 if (rMsgId
= NTF_SERVER_DONE
) then
1151 e_LogWritefln('file transfer complete.', []);
1155 else if (rMsgId
= NTF_SERVER_CHUNK
) then
1157 if not msg
.Init(ev
.packet
^.data
+1, ev
.packet
^.dataLength
-1, True) then exit
;
1158 chunk
:= msg
.ReadLongInt();
1159 csize
:= msg
.ReadLongInt();
1160 if (chunk
<> nextChunk
) then
1162 e_LogWritefln('received chunk %d, but expected chunk %d', [chunk
, nextChunk
]);
1166 if (csize
< 0) or (csize
> tf
.chunkSize
) then
1168 e_LogWritefln('received chunk with size %d, but expected chunk size is %d', [csize
, tf
.chunkSize
]);
1172 //e_LogWritefln('got chunk #%d of #%d (csize=%d)', [chunk, (tf.size+tf.chunkSize-1) div tf.chunkSize, csize]);
1173 msg
.ReadData(buf
, csize
);
1174 strm
.WriteBuffer(buf
^, csize
);
1175 nextChunk
:= chunk
+1;
1176 g_Game_StepLoading();
1179 trans_omsg
.Write(Byte(NTF_CLIENT_ACK
));
1180 trans_omsg
.Write(LongInt(chunk
));
1181 if not ftransSendClientMsg(trans_omsg
) then begin result
:= -1; exit
; end;
1183 else if (rMsgId
= NTF_SERVER_ABORT
) then
1185 e_LogWritefln('g_Net_ReceiveResourceFile: server aborted transfer', []);
1191 e_LogWritefln('g_Net_ReceiveResourceFile: invalid server packet type', []);
1195 //stx := stx+GetTimerMS();
1196 //e_LogWritefln('g_Net_ReceiveResourceFile: process stx=%d', [Integer(stx)]);
1199 ENET_EVENT_TYPE_DISCONNECT
:
1201 if (ev
.data
<= NET_DISC_MAX
) then
1202 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' ' + _lc
[TStrings_Locale(Cardinal(I_NET_DISC_NONE
) + ev
.data
)], True);
1208 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' unknown ENet event ' + IntToStr(Ord(ev
.kind
)), True);
1213 if (freePacket
) then begin freePacket
:= false; enet_packet_destroy(ev
.packet
); end;
1216 if g_Net_UserRequestExit() then
1218 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' user abort', True);
1225 if (freePacket
) then enet_packet_destroy(ev
.packet
);
1230 //**************************************************************************
1234 //**************************************************************************
1236 function g_Net_FindSlot(): Integer;
1245 for I
:= Low(NetClients
) to High(NetClients
) do
1247 if NetClients
[I
].Used
then
1256 if C
>= NetMaxClients
then
1264 if (Length(NetClients
) >= NetMaxClients
) then
1268 SetLength(NetClients
, Length(NetClients
) + 1);
1269 N
:= High(NetClients
);
1275 NetClients
[N
].Used
:= True;
1276 NetClients
[N
].ID
:= N
;
1277 NetClients
[N
].RequestedFullUpdate
:= False;
1278 NetClients
[N
].WaitForFirstSpawn
:= False;
1279 NetClients
[N
].RCONAuth
:= False;
1280 NetClients
[N
].Voted
:= False;
1281 NetClients
[N
].Player
:= 0;
1282 clearNetClientTransfers(NetClients
[N
]); // just in case
1288 function g_Net_Init(): Boolean;
1296 NetBuf
[NET_UNRELIABLE
].Clear();
1297 NetBuf
[NET_RELIABLE
].Clear();
1298 //SetLength(NetClients, 0);
1299 clearNetClients(true); // clear array
1305 NetAddr
.port
:= 25666;
1306 SetLength(NetBannedHosts
, 0);
1307 if FileExists(DataDir
+ BANLIST_FILENAME
) then
1309 Assign(F
, DataDir
+ BANLIST_FILENAME
);
1314 if StrToIp(IPstr
, IP
) then
1318 g_Net_SaveBanList();
1321 Result
:= (enet_initialize() = 0);
1324 procedure g_Net_Flush();
1328 F
, Chan
: enet_uint32
;
1332 Chan
:= NET_CHAN_GAME
;
1334 if NetMode
= NET_SERVER
then
1335 for T
:= NET_UNRELIABLE
to NET_RELIABLE
do
1337 if NetBuf
[T
].CurSize
> 0 then
1339 P
:= enet_packet_create(NetBuf
[T
].Data
, NetBuf
[T
].CurSize
, F
);
1340 if not Assigned(P
) then continue
;
1341 enet_host_broadcast(NetHost
, Chan
, P
);
1345 for I
:= Low(NetClients
) to High(NetClients
) do
1347 if not NetClients
[I
].Used
then continue
;
1348 if NetClients
[I
].NetOut
[T
].CurSize
<= 0 then continue
;
1349 P
:= enet_packet_create(NetClients
[I
].NetOut
[T
].Data
, NetClients
[I
].NetOut
[T
].CurSize
, F
);
1350 if not Assigned(P
) then continue
;
1351 enet_peer_send(NetClients
[I
].Peer
, Chan
, P
);
1352 NetClients
[I
].NetOut
[T
].Clear();
1355 // next and last iteration is always RELIABLE
1356 F
:= LongWord(ENET_PACKET_FLAG_RELIABLE
);
1357 Chan
:= NET_CHAN_IMPORTANT
;
1359 else if NetMode
= NET_CLIENT
then
1360 for T
:= NET_UNRELIABLE
to NET_RELIABLE
do
1362 if NetBuf
[T
].CurSize
> 0 then
1364 P
:= enet_packet_create(NetBuf
[T
].Data
, NetBuf
[T
].CurSize
, F
);
1365 if not Assigned(P
) then continue
;
1366 enet_peer_send(NetPeer
, Chan
, P
);
1369 // next and last iteration is always RELIABLE
1370 F
:= LongWord(ENET_PACKET_FLAG_RELIABLE
);
1371 Chan
:= NET_CHAN_IMPORTANT
;
1375 procedure g_Net_Cleanup();
1379 NetBuf
[NET_UNRELIABLE
].Clear();
1380 NetBuf
[NET_RELIABLE
].Clear();
1382 //SetLength(NetClients, 0);
1383 clearNetClients(true); // clear array
1384 NetClientCount
:= 0;
1388 g_Net_Slist_Disconnect(false); // do not spam console
1392 NetState
:= NET_STATE_NONE
;
1394 NetPongSock
:= ENET_SOCKET_NULL
;
1396 NetTimeToMaster
:= 0;
1397 NetTimeToUpdate
:= 0;
1398 NetTimeToReliable
:= 0;
1400 NetMode
:= NET_NONE
;
1402 if NetPortThread
<> NilThreadId
then
1403 WaitForThreadTerminate(NetPortThread
, 66666);
1405 NetPortThread
:= NilThreadId
;
1406 g_Net_UnforwardPorts();
1412 procedure g_Net_Free();
1416 enet_deinitialize();
1417 NetInitDone
:= False;
1421 //**************************************************************************
1425 //**************************************************************************
1427 function ForwardThread(Param
: Pointer): PtrInt
;
1430 if not g_Net_ForwardPorts() then Result
:= -1;
1433 function g_Net_Host(IPAddr
: LongWord; Port
: enet_uint16
; MaxClients
: Cardinal = 16): Boolean;
1435 if NetMode
<> NET_NONE
then
1437 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_INGAME
]);
1444 g_Console_Add(_lc
[I_NET_MSG
] + Format(_lc
[I_NET_MSG_HOST
], [Port
]));
1445 if not NetInitDone
then
1447 if (not g_Net_Init()) then
1449 g_Console_Add(_lc
[I_NET_MSG_FERROR
] + _lc
[I_NET_ERR_ENET
]);
1454 NetInitDone
:= True;
1457 NetAddr
.host
:= IPAddr
;
1458 NetAddr
.port
:= Port
;
1460 if NetForwardPorts
then NetPortThread
:= BeginThread(ForwardThread
);
1462 NetHost
:= enet_host_create(@NetAddr
, NET_MAXCLIENTS
, NET_CHANS
, 0, 0);
1464 if (NetHost
= nil) then
1466 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + Format(_lc
[I_NET_ERR_HOST
], [Port
]));
1472 NetPongSock
:= enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM
);
1473 if NetPongSock
<> ENET_SOCKET_NULL
then
1475 NetPongAddr
.host
:= IPAddr
;
1476 NetPongAddr
.port
:= NET_PING_PORT
;
1477 if enet_socket_bind(NetPongSock
, @NetPongAddr
) < 0 then
1479 enet_socket_destroy(NetPongSock
);
1480 NetPongSock
:= ENET_SOCKET_NULL
;
1483 enet_socket_set_option(NetPongSock
, ENET_SOCKOPT_NONBLOCK
, 1);
1486 NetMode
:= NET_SERVER
;
1488 NetBuf
[NET_UNRELIABLE
].Clear();
1489 NetBuf
[NET_RELIABLE
].Clear();
1495 procedure g_Net_Host_Die();
1499 if NetMode
<> NET_SERVER
then Exit
;
1501 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_HOST_DISCALL
]);
1502 for I
:= 0 to High(NetClients
) do
1503 if NetClients
[I
].Used
then
1504 enet_peer_disconnect(NetClients
[I
].Peer
, NET_DISC_DOWN
);
1506 while enet_host_service(NetHost
, @NetEvent
, 1000) > 0 do
1507 if NetEvent
.kind
= ENET_EVENT_TYPE_RECEIVE
then
1508 enet_packet_destroy(NetEvent
.packet
);
1510 for I
:= 0 to High(NetClients
) do
1511 if NetClients
[I
].Used
then
1513 FreeMemory(NetClients
[I
].Peer
^.data
);
1514 NetClients
[I
].Peer
^.data
:= nil;
1515 enet_peer_reset(NetClients
[I
].Peer
);
1516 NetClients
[I
].Peer
:= nil;
1517 NetClients
[I
].Used
:= False;
1518 NetClients
[I
].NetOut
[NET_UNRELIABLE
].Free();
1519 NetClients
[I
].NetOut
[NET_RELIABLE
].Free();
1522 clearNetClients(false); // don't clear array
1523 if (g_Net_Slist_IsConnectionActive
) then g_Net_Slist_Disconnect
;
1524 if NetPongSock
<> ENET_SOCKET_NULL
then
1525 enet_socket_destroy(NetPongSock
);
1527 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_HOST_DIE
]);
1528 enet_host_destroy(NetHost
);
1530 NetMode
:= NET_NONE
;
1533 e_WriteLog('NET: Server stopped', TMsgType
.Notify
);
1537 procedure g_Net_Host_Send(ID
: Integer; Reliable
: Boolean; Chan
: Byte = NET_CHAN_GAME
);
1544 T
:= NET_UNRELIABLE
;
1548 if ID
> High(NetClients
) then Exit
;
1549 if NetClients
[ID
].Peer
= nil then Exit
;
1551 NetClients
[ID
].NetOut
[T
].Write(Integer(NetOut
.CurSize
));
1552 NetClients
[ID
].NetOut
[T
].Write(NetOut
);
1557 NetBuf
[T
].Write(Integer(NetOut
.CurSize
));
1558 NetBuf
[T
].Write(NetOut
);
1561 if NetDump
then g_Net_DumpSendBuffer();
1565 procedure g_Net_Host_CheckPings();
1567 ClAddr
: ENetAddress
;
1571 Ping
: array [0..9] of Byte;
1574 if NetPongSock
= ENET_SOCKET_NULL
then Exit
;
1576 Buf
.data
:= Addr(Ping
[0]);
1577 Buf
.dataLength
:= 2+8;
1581 Len
:= enet_socket_receive(NetPongSock
, @ClAddr
, @Buf
, 1);
1582 if Len
< 0 then Exit
;
1584 if (Ping
[0] = Ord('D')) and (Ping
[1] = Ord('F')) then
1586 ClTime
:= Int64(Addr(Ping
[2])^);
1589 NetOut
.Write(Byte(Ord('D')));
1590 NetOut
.Write(Byte(Ord('F')));
1591 NetOut
.Write(NetPort
);
1592 NetOut
.Write(ClTime
);
1593 g_Net_Slist_WriteInfo();
1595 if gPlayer1
<> nil then Inc(NPl
);
1596 if gPlayer2
<> nil then Inc(NPl
);
1598 NetOut
.Write(gNumBots
);
1600 Buf
.data
:= NetOut
.Data
;
1601 Buf
.dataLength
:= NetOut
.CurSize
;
1602 enet_socket_send(NetPongSock
, @ClAddr
, @Buf
, 1);
1609 function g_Net_Host_Update(): enet_size_t
;
1620 if NetUseMaster
then g_Net_Slist_Check
;
1621 g_Net_Host_CheckPings
;
1623 while (enet_host_service(NetHost
, @NetEvent
, 0) > 0) do
1625 case (NetEvent
.kind
) of
1626 ENET_EVENT_TYPE_CONNECT
:
1628 IP
:= IpToStr(NetEvent
.Peer
^.address
.host
);
1629 Port
:= NetEvent
.Peer
^.address
.port
;
1630 g_Console_Add(_lc
[I_NET_MSG
] +
1631 Format(_lc
[I_NET_MSG_HOST_CONN
], [IP
, Port
]));
1633 if (NetEvent
.data
<> NET_PROTOCOL_VER
) then
1635 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_HOST_REJECT
] +
1636 _lc
[I_NET_DISC_PROTOCOL
]);
1637 NetEvent
.peer
^.data
:= GetMemory(SizeOf(Byte));
1638 Byte(NetEvent
.peer
^.data
^) := 255;
1639 enet_peer_disconnect(NetEvent
.peer
, NET_DISC_PROTOCOL
);
1640 enet_host_flush(NetHost
);
1644 ID
:= g_Net_FindSlot();
1648 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_HOST_REJECT
] +
1649 _lc
[I_NET_DISC_FULL
]);
1650 NetEvent
.Peer
^.data
:= GetMemory(SizeOf(Byte));
1651 Byte(NetEvent
.peer
^.data
^) := 255;
1652 enet_peer_disconnect(NetEvent
.peer
, NET_DISC_FULL
);
1653 enet_host_flush(NetHost
);
1657 NetClients
[ID
].Peer
:= NetEvent
.peer
;
1658 NetClients
[ID
].Peer
^.data
:= GetMemory(SizeOf(Byte));
1659 Byte(NetClients
[ID
].Peer
^.data
^) := ID
;
1660 NetClients
[ID
].State
:= NET_STATE_AUTH
;
1661 NetClients
[ID
].RCONAuth
:= False;
1662 NetClients
[ID
].NetOut
[NET_UNRELIABLE
].Alloc(NET_BUFSIZE
*2);
1663 NetClients
[ID
].NetOut
[NET_RELIABLE
].Alloc(NET_BUFSIZE
*2);
1664 clearNetClientTransfers(NetClients
[ID
]); // just in case
1666 enet_peer_timeout(NetEvent
.peer
, ENET_PEER_TIMEOUT_LIMIT
* 2, ENET_PEER_TIMEOUT_MINIMUM
* 2, ENET_PEER_TIMEOUT_MAXIMUM
* 2);
1668 Inc(NetClientCount
);
1669 g_Console_Add(_lc
[I_NET_MSG
] + Format(_lc
[I_NET_MSG_HOST_ADD
], [ID
]));
1672 ENET_EVENT_TYPE_RECEIVE
:
1674 //e_LogWritefln('RECEIVE: chan=%u', [NetEvent.channelID]);
1675 if (NetEvent
.channelID
= NET_CHAN_DOWNLOAD_EX
) then
1677 ProcessDownloadExPacket();
1681 ID
:= Byte(NetEvent
.peer
^.data
^);
1682 if ID
> High(NetClients
) then Exit
;
1683 TC
:= @NetClients
[ID
];
1685 if NetDump
then g_Net_DumpRecvBuffer(NetEvent
.packet
^.data
, NetEvent
.packet
^.dataLength
);
1686 g_Net_Host_HandlePacket(TC
, NetEvent
.packet
, g_Net_HostMsgHandler
);
1690 ENET_EVENT_TYPE_DISCONNECT
:
1692 ID
:= Byte(NetEvent
.peer
^.data
^);
1693 if ID
> High(NetClients
) then Exit
;
1694 clearNetClient(NetClients
[ID
]);
1695 TC
:= @NetClients
[ID
];
1696 if TC
= nil then Exit
;
1698 if not (TC
^.Used
) then Exit
;
1700 TP
:= g_Player_Get(TC
^.Player
);
1705 TP
.Kill(K_SIMPLEKILL
, 0, HIT_DISCON
);
1706 g_Console_Add(Format(_lc
[I_PLAYER_LEAVE
], [TP
.Name
]), True);
1707 e_WriteLog('NET: Client ' + TP
.Name
+ ' [' + IntToStr(ID
) + '] disconnected.', TMsgType
.Notify
);
1708 g_Player_Remove(TP
.UID
);
1712 TC
^.State
:= NET_STATE_NONE
;
1715 TC
^.RequestedFullUpdate
:= False;
1716 TC
^.WaitForFirstSpawn
:= False;
1717 TC
^.NetOut
[NET_UNRELIABLE
].Free();
1718 TC
^.NetOut
[NET_RELIABLE
].Free();
1720 FreeMemory(NetEvent
.peer
^.data
);
1721 NetEvent
.peer
^.data
:= nil;
1722 g_Console_Add(_lc
[I_NET_MSG
] + Format(_lc
[I_NET_MSG_HOST_DISC
], [ID
]));
1723 Dec(NetClientCount
);
1725 if NetUseMaster
then g_Net_Slist_Update
;
1732 //**************************************************************************
1736 //**************************************************************************
1738 procedure g_Net_Disconnect(Forced
: Boolean = False);
1740 if NetMode
<> NET_CLIENT
then Exit
;
1741 if (NetHost
= nil) or (NetPeer
= nil) then Exit
;
1745 enet_peer_disconnect(NetPeer
, NET_DISC_NONE
);
1747 while (enet_host_service(NetHost
, @NetEvent
, 1500) > 0) do
1749 if (NetEvent
.kind
= ENET_EVENT_TYPE_DISCONNECT
) then
1755 if (NetEvent
.kind
= ENET_EVENT_TYPE_RECEIVE
) then
1756 enet_packet_destroy(NetEvent
.packet
);
1759 if NetPeer
<> nil then
1761 enet_peer_reset(NetPeer
);
1767 e_WriteLog('NET: Kicked from server: ' + IntToStr(NetEvent
.data
), TMsgType
.Notify
);
1768 if (NetEvent
.data
<= NET_DISC_MAX
) then
1769 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_KICK
] +
1770 _lc
[TStrings_Locale(Cardinal(I_NET_DISC_NONE
) + NetEvent
.data
)], True);
1773 if NetHost
<> nil then
1775 enet_host_destroy(NetHost
);
1778 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_CLIENT_DISC
]);
1781 e_WriteLog('NET: Disconnected', TMsgType
.Notify
);
1784 procedure g_Net_Client_Send(Reliable
: Boolean; Chan
: Byte = NET_CHAN_GAME
);
1791 T
:= NET_UNRELIABLE
;
1794 NetBuf
[T
].Write(Integer(NetOut
.CurSize
));
1795 NetBuf
[T
].Write(NetOut
);
1797 if NetDump
then g_Net_DumpSendBuffer();
1799 g_Net_Flush(); // FIXME: for now, send immediately
1802 function g_Net_Client_Update(): enet_size_t
;
1805 while (enet_host_service(NetHost
, @NetEvent
, 0) > 0) do
1807 case NetEvent
.kind
of
1808 ENET_EVENT_TYPE_RECEIVE
:
1810 if (NetEvent
.channelID
= NET_CHAN_DOWNLOAD_EX
) then continue
; // ignore all download packets, they're processed by separate code
1811 if NetDump
then g_Net_DumpRecvBuffer(NetEvent
.packet
^.data
, NetEvent
.packet
^.dataLength
);
1812 g_Net_Client_HandlePacket(NetEvent
.packet
, g_Net_ClientMsgHandler
);
1815 ENET_EVENT_TYPE_DISCONNECT
:
1817 g_Net_Disconnect(True);
1825 function g_Net_Client_UpdateWhileLoading(): enet_size_t
;
1828 while (enet_host_service(NetHost
, @NetEvent
, 0) > 0) do
1830 case NetEvent
.kind
of
1831 ENET_EVENT_TYPE_RECEIVE
:
1833 if (NetEvent
.channelID
= NET_CHAN_DOWNLOAD_EX
) then continue
; // ignore all download packets, they're processed by separate code
1834 if NetDump
then g_Net_DumpRecvBuffer(NetEvent
.packet
^.data
, NetEvent
.packet
^.dataLength
);
1835 g_Net_Client_HandlePacket(NetEvent
.packet
, g_Net_ClientLightMsgHandler
);
1838 ENET_EVENT_TYPE_DISCONNECT
:
1840 g_Net_Disconnect(True);
1849 function g_Net_Connect(IP
: string; Port
: enet_uint16
): Boolean;
1852 TimeoutTime
, T
: Int64;
1854 if NetMode
<> NET_NONE
then
1856 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_ERR_INGAME
], True);
1863 g_Console_Add(_lc
[I_NET_MSG
] + Format(_lc
[I_NET_MSG_CLIENT_CONN
],
1865 if not NetInitDone
then
1867 if (not g_Net_Init()) then
1869 g_Console_Add(_lc
[I_NET_MSG_FERROR
] + _lc
[I_NET_ERR_ENET
], True);
1874 NetInitDone
:= True;
1877 NetHost
:= enet_host_create(nil, 1, NET_CHANS
, 0, 0);
1879 if (NetHost
= nil) then
1881 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CLIENT
], True);
1887 enet_address_set_host(@NetAddr
, PChar(Addr(IP
[1])));
1888 NetAddr
.port
:= Port
;
1890 NetPeer
:= enet_host_connect(NetHost
, @NetAddr
, NET_CHANS
, NET_PROTOCOL_VER
);
1892 if (NetPeer
= nil) then
1894 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CLIENT
], True);
1895 enet_host_destroy(NetHost
);
1901 // предупредить что ждем слишком долго через N секунд
1902 TimeoutTime
:= sys_GetTicks() + NET_CONNECT_TIMEOUT
;
1907 while (enet_host_service(NetHost
, @NetEvent
, 0) > 0) do
1909 if (NetEvent
.kind
= ENET_EVENT_TYPE_CONNECT
) then
1911 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_CLIENT_DONE
]);
1912 NetMode
:= NET_CLIENT
;
1914 enet_peer_timeout(NetPeer
, ENET_PEER_TIMEOUT_LIMIT
* 2, ENET_PEER_TIMEOUT_MINIMUM
* 2, ENET_PEER_TIMEOUT_MAXIMUM
* 2);
1916 NetClientPort
:= Port
;
1923 T
:= sys_GetTicks();
1924 if T
> TimeoutTime
then
1926 TimeoutTime
:= T
+ NET_CONNECT_TIMEOUT
* 100; // одного предупреждения хватит
1927 g_Console_Add(_lc
[I_NET_MSG_TIMEOUT_WARN
], True);
1928 g_Console_Add(Format(_lc
[I_NET_MSG_PORTS
], [Integer(Port
), Integer(NET_PING_PORT
)]), True);
1931 ProcessLoading(true);
1933 if e_KeyPressed(IK_SPACE
) or e_KeyPressed(IK_ESCAPE
) or e_KeyPressed(VK_ESCAPE
) or
1934 e_KeyPressed(JOY0_JUMP
) or e_KeyPressed(JOY1_JUMP
) or e_KeyPressed(JOY2_JUMP
) or e_KeyPressed(JOY3_JUMP
) then
1938 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_TIMEOUT
], True);
1939 g_Console_Add(Format(_lc
[I_NET_MSG_PORTS
], [Integer(Port
), Integer(NET_PING_PORT
)]), True);
1940 if NetPeer
<> nil then enet_peer_reset(NetPeer
);
1941 if NetHost
<> nil then
1943 enet_host_destroy(NetHost
);
1950 function IpToStr(IP
: LongWord): string;
1955 Result
:= IntToStr(PByte(Ptr
+ 0)^) + '.';
1956 Result
:= Result
+ IntToStr(PByte(Ptr
+ 1)^) + '.';
1957 Result
:= Result
+ IntToStr(PByte(Ptr
+ 2)^) + '.';
1958 Result
:= Result
+ IntToStr(PByte(Ptr
+ 3)^);
1961 function StrToIp(IPstr
: string; var IP
: LongWord): Boolean;
1965 Result
:= enet_address_set_host(@EAddr
, PChar(@IPstr
[1])) = 0;
1969 function g_Net_Client_ByName(Name
: string): pTNetClient
;
1975 for a
:= Low(NetClients
) to High(NetClients
) do
1976 if (NetClients
[a
].Used
) and (NetClients
[a
].State
= NET_STATE_GAME
) then
1978 pl
:= g_Player_Get(NetClients
[a
].Player
);
1979 if pl
= nil then continue
;
1980 if Copy(LowerCase(pl
.Name
), 1, Length(Name
)) <> LowerCase(Name
) then continue
;
1981 if NetClients
[a
].Peer
<> nil then
1983 Result
:= @NetClients
[a
];
1989 function g_Net_Client_ByPlayer(PID
: Word): pTNetClient
;
1994 for a
:= Low(NetClients
) to High(NetClients
) do
1995 if (NetClients
[a
].Used
) and (NetClients
[a
].State
= NET_STATE_GAME
) then
1996 if NetClients
[a
].Player
= PID
then
1998 Result
:= @NetClients
[a
];
2003 function g_Net_ClientName_ByID(ID
: Integer): string;
2009 if ID
= NET_EVERYONE
then
2011 for a
:= Low(NetClients
) to High(NetClients
) do
2012 if (NetClients
[a
].ID
= ID
) and (NetClients
[a
].Used
) and (NetClients
[a
].State
= NET_STATE_GAME
) then
2014 pl
:= g_Player_Get(NetClients
[a
].Player
);
2015 if pl
= nil then Exit
;
2020 procedure g_Net_SendData(Data
: AByte
; peer
: pENetPeer
; Reliable
: Boolean; Chan
: Byte = NET_CHAN_DOWNLOAD
);
2024 dataLength
: Cardinal;
2026 dataLength
:= Length(Data
);
2029 F
:= LongWord(ENET_PACKET_FLAG_RELIABLE
)
2033 if (peer
<> nil) then
2035 P
:= enet_packet_create(@Data
[0], dataLength
, F
);
2036 if not Assigned(P
) then Exit
;
2037 enet_peer_send(peer
, Chan
, P
);
2041 P
:= enet_packet_create(@Data
[0], dataLength
, F
);
2042 if not Assigned(P
) then Exit
;
2043 enet_host_broadcast(NetHost
, Chan
, P
);
2046 enet_host_flush(NetHost
);
2049 function g_Net_IsHostBanned(IP
: LongWord; Perm
: Boolean = False): Boolean;
2054 if NetBannedHosts
= nil then
2056 for I
:= 0 to High(NetBannedHosts
) do
2057 if (NetBannedHosts
[I
].IP
= IP
) and ((not Perm
) or (NetBannedHosts
[I
].Perm
)) then
2064 procedure g_Net_BanHost(IP
: LongWord; Perm
: Boolean = True); overload
;
2070 if g_Net_IsHostBanned(IP
, Perm
) then
2074 for I
:= Low(NetBannedHosts
) to High(NetBannedHosts
) do
2075 if NetBannedHosts
[I
].IP
= 0 then
2083 SetLength(NetBannedHosts
, Length(NetBannedHosts
) + 1);
2084 P
:= High(NetBannedHosts
);
2087 NetBannedHosts
[P
].IP
:= IP
;
2088 NetBannedHosts
[P
].Perm
:= Perm
;
2091 procedure g_Net_BanHost(IP
: string; Perm
: Boolean = True); overload
;
2096 b
:= StrToIp(IP
, a
);
2098 g_Net_BanHost(a
, Perm
);
2101 procedure g_Net_UnbanNonPermHosts();
2105 if NetBannedHosts
= nil then
2107 for I
:= Low(NetBannedHosts
) to High(NetBannedHosts
) do
2108 if (NetBannedHosts
[I
].IP
> 0) and not NetBannedHosts
[I
].Perm
then
2110 NetBannedHosts
[I
].IP
:= 0;
2111 NetBannedHosts
[I
].Perm
:= True;
2115 function g_Net_UnbanHost(IP
: string): Boolean; overload
;
2119 Result
:= StrToIp(IP
, a
);
2121 Result
:= g_Net_UnbanHost(a
);
2124 function g_Net_UnbanHost(IP
: LongWord): Boolean; overload
;
2131 if NetBannedHosts
= nil then
2133 for I
:= 0 to High(NetBannedHosts
) do
2134 if NetBannedHosts
[I
].IP
= IP
then
2136 NetBannedHosts
[I
].IP
:= 0;
2137 NetBannedHosts
[I
].Perm
:= True;
2139 // no break here to clear all bans of this host, perm and non-perm
2143 procedure g_Net_SaveBanList();
2148 Assign(F
, DataDir
+ BANLIST_FILENAME
);
2150 if NetBannedHosts
<> nil then
2151 for I
:= 0 to High(NetBannedHosts
) do
2152 if NetBannedHosts
[I
].Perm
and (NetBannedHosts
[I
].IP
> 0) then
2153 Writeln(F
, IpToStr(NetBannedHosts
[I
].IP
));
2157 procedure g_Net_DumpStart();
2159 if NetMode
= NET_SERVER
then
2160 NetDumpFile
:= createDiskFile(NETDUMP_FILENAME
+ '_server')
2162 NetDumpFile
:= createDiskFile(NETDUMP_FILENAME
+ '_client');
2165 procedure g_Net_DumpSendBuffer();
2167 writeInt(NetDumpFile
, gTime
);
2168 writeInt(NetDumpFile
, LongWord(NetOut
.CurSize
));
2169 writeInt(NetDumpFile
, Byte(1));
2170 NetDumpFile
.WriteBuffer(NetOut
.Data
^, NetOut
.CurSize
);
2173 procedure g_Net_DumpRecvBuffer(Buf
: penet_uint8
; Len
: LongWord);
2175 if (Buf
= nil) or (Len
= 0) then Exit
;
2176 writeInt(NetDumpFile
, gTime
);
2177 writeInt(NetDumpFile
, Len
);
2178 writeInt(NetDumpFile
, Byte(0));
2179 NetDumpFile
.WriteBuffer(Buf
^, Len
);
2182 procedure g_Net_DumpEnd();
2188 function g_Net_ForwardPorts(ForwardPongPort
: Boolean = True): Boolean;
2189 {$IFDEF USE_MINIUPNPC}
2194 LanAddr
: array [0..255] of Char;
2195 StrPort
: AnsiString;
2200 if NetPortForwarded
= NetPort
then
2206 NetPongForwarded
:= False;
2207 NetPortForwarded
:= 0;
2209 DevList
:= upnpDiscover(1000, nil, nil, 0, 0, 2, Addr(Err
));
2210 if DevList
= nil then
2212 conwritefln('port forwarding failed: upnpDiscover() failed: %d', [Err
]);
2216 I
:= UPNP_GetValidIGD(DevList
, @Urls
, @Data
, Addr(LanAddr
[0]), 256);
2220 conwriteln('port forwarding failed: could not find an IGD device on this LAN');
2221 FreeUPNPDevList(DevList
);
2222 FreeUPNPUrls(@Urls
);
2226 StrPort
:= IntToStr(NetPort
);
2227 I
:= UPNP_AddPortMapping(
2228 Urls
.controlURL
, Addr(data
.first
.servicetype
[1]),
2229 PChar(StrPort
), PChar(StrPort
), Addr(LanAddr
[0]), PChar('D2DF'),
2230 PChar('UDP'), nil, PChar('0')
2235 conwritefln('forwarding port %d failed: error %d', [NetPort
, I
]);
2236 FreeUPNPDevList(DevList
);
2237 FreeUPNPUrls(@Urls
);
2241 if ForwardPongPort
then
2243 StrPort
:= IntToStr(NET_PING_PORT
);
2244 I
:= UPNP_AddPortMapping(
2245 Urls
.controlURL
, Addr(data
.first
.servicetype
[1]),
2246 PChar(StrPort
), PChar(StrPort
), Addr(LanAddr
[0]), PChar('D2DF'),
2247 PChar('UDP'), nil, PChar('0')
2252 conwritefln('forwarding port %d failed: error %d', [NetPort
+ 1, I
]);
2253 NetPongForwarded
:= False;
2257 conwritefln('forwarded port %d successfully', [NetPort
+ 1]);
2258 NetPongForwarded
:= True;
2262 conwritefln('forwarded port %d successfully', [NetPort
]);
2263 NetIGDControl
:= AnsiString(Urls
.controlURL
);
2264 NetIGDService
:= data
.first
.servicetype
;
2265 NetPortForwarded
:= NetPort
;
2267 FreeUPNPDevList(DevList
);
2268 FreeUPNPUrls(@Urls
);
2277 procedure g_Net_UnforwardPorts();
2278 {$IFDEF USE_MINIUPNPC}
2281 StrPort
: AnsiString;
2283 if NetPortForwarded
= 0 then Exit
;
2285 conwriteln('unforwarding ports...');
2287 StrPort
:= IntToStr(NetPortForwarded
);
2288 I
:= UPNP_DeletePortMapping(
2289 PChar(NetIGDControl
), Addr(NetIGDService
[1]),
2290 PChar(StrPort
), PChar('UDP'), nil
2292 conwritefln(' port %d: %d', [NetPortForwarded
, I
]);
2294 if NetPongForwarded
then
2296 NetPongForwarded
:= False;
2297 StrPort
:= IntToStr(NetPortForwarded
+ 1);
2298 I
:= UPNP_DeletePortMapping(
2299 PChar(NetIGDControl
), Addr(NetIGDService
[1]),
2300 PChar(StrPort
), PChar('UDP'), nil
2302 conwritefln(' port %d: %d', [NetPortForwarded
+ 1, I
]);
2305 NetPortForwarded
:= 0;
2314 conRegVar('cl_downloadtimeout', @g_Net_DownloadTimeout
, 0.0, 1000000.0, '', 'timeout in seconds, 0 to disable it');
2315 SetLength(NetClients
, 0);
2316 g_Net_DownloadTimeout
:= 60;
2317 NetIn
.Alloc(NET_BUFSIZE
);
2318 NetOut
.Alloc(NET_BUFSIZE
);
2319 NetBuf
[NET_UNRELIABLE
].Alloc(NET_BUFSIZE
*2);
2320 NetBuf
[NET_RELIABLE
].Alloc(NET_BUFSIZE
*2);
2321 trans_omsg
.Alloc(NET_BUFSIZE
);
2325 NetBuf
[NET_UNRELIABLE
].Free();
2326 NetBuf
[NET_RELIABLE
].Free();