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
= 181;
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';
82 TNetFileTransfer
= record
86 size
: Integer; // file size in bytes
88 lastSentChunk
: Integer;
89 lastAckChunk
: Integer;
90 lastAckTime
: Int64; // msecs; if not "in progress", we're waiting for the first ack
92 diskBuffer
: PChar; // of `chunkSize` bytes
101 RequestedFullUpdate
: Boolean;
104 Transfer
: TNetFileTransfer
; // only one transfer may be active
105 NetOut
: array [0..1] of TMsg
;
111 pTNetClient
= ^TNetClient
;
113 AByte
= array of Byte;
116 NetInitDone
: Boolean = False;
117 NetMode
: Byte = NET_NONE
;
118 NetDump
: Boolean = False;
120 NetServerName
: string = 'Unnamed Server';
121 NetPassword
: string = '';
122 NetPort
: Word = 25666;
124 NetAllowRCON
: Boolean = False;
125 NetRCONPassword
: string = '';
127 NetTimeToUpdate
: Cardinal = 0;
128 NetTimeToReliable
: Cardinal = 0;
129 NetTimeToMaster
: Cardinal = 0;
131 NetHost
: pENetHost
= nil;
132 NetPeer
: pENetPeer
= nil;
134 NetAddr
: ENetAddress
;
136 NetPongAddr
: ENetAddress
;
137 NetPongSock
: ENetSocket
= ENET_SOCKET_NULL
;
139 NetUseMaster
: Boolean = True;
140 NetSlistAddr
: ENetAddress
;
141 NetSlistIP
: string = 'mpms.doom2d.org';
142 NetSlistPort
: Word = 25665;
144 NetClientIP
: string = '127.0.0.1';
145 NetClientPort
: Word = 25666;
148 NetBuf
: array [0..1] of TMsg
;
150 NetClients
: array of TNetClient
;
151 NetClientCount
: Byte = 0;
152 NetMaxClients
: Byte = 255;
153 NetBannedHosts
: array of TBanRecord
;
155 NetState
: Integer = NET_STATE_NONE
;
157 NetMyID
: Integer = -1;
158 NetPlrUID1
: Integer = -1;
159 NetPlrUID2
: Integer = -1;
161 NetInterpLevel
: Integer = 1;
162 NetUpdateRate
: Cardinal = 0; // as soon as possible
163 NetRelupdRate
: Cardinal = 18; // around two times a second
164 NetMasterRate
: Cardinal = 60000;
166 NetForcePlayerUpdate
: Boolean = False;
167 NetPredictSelf
: Boolean = True;
168 NetForwardPorts
: Boolean = False;
170 NetGotEverything
: Boolean = False;
171 NetGotKeys
: Boolean = False;
173 {$IFDEF USE_MINIUPNPC}
174 NetPortForwarded
: Word = 0;
175 NetPongForwarded
: Boolean = False;
176 NetIGDControl
: AnsiString;
177 NetIGDService
: TURLStr
;
180 NetPortThread
: TThreadID
= NilThreadId
;
182 NetDumpFile
: TStream
;
184 g_Res_received_map_start
: Boolean = false; // set if we received "map change" event
187 function g_Net_Init(): Boolean;
188 procedure g_Net_Cleanup();
189 procedure g_Net_Free();
190 procedure g_Net_Flush();
192 function g_Net_Host(IPAddr
: LongWord; Port
: enet_uint16
; MaxClients
: Cardinal = 16): Boolean;
193 procedure g_Net_Host_Die();
194 procedure g_Net_Host_Send(ID
: Integer; Reliable
: Boolean; Chan
: Byte = NET_CHAN_GAME
);
195 function g_Net_Host_Update(): enet_size_t
;
197 function g_Net_Connect(IP
: string; Port
: enet_uint16
): Boolean;
198 procedure g_Net_Disconnect(Forced
: Boolean = False);
199 procedure g_Net_Client_Send(Reliable
: Boolean; Chan
: Byte = NET_CHAN_GAME
);
200 function g_Net_Client_Update(): enet_size_t
;
201 function g_Net_Client_UpdateWhileLoading(): enet_size_t
;
203 function g_Net_Client_ByName(Name
: string): pTNetClient
;
204 function g_Net_Client_ByPlayer(PID
: Word): pTNetClient
;
205 function g_Net_ClientName_ByID(ID
: Integer): string;
207 procedure g_Net_SendData(Data
: AByte
; peer
: pENetPeer
; Reliable
: Boolean; Chan
: Byte = NET_CHAN_DOWNLOAD
);
208 //function g_Net_Wait_Event(msgId: Word): TMemoryStream;
209 //function g_Net_Wait_FileInfo (var tf: TNetFileTransfer; asMap: Boolean; out resList: TStringList): Integer;
211 function IpToStr(IP
: LongWord): string;
212 function StrToIp(IPstr
: string; var IP
: LongWord): Boolean;
214 function g_Net_IsHostBanned(IP
: LongWord; Perm
: Boolean = False): Boolean;
215 procedure g_Net_BanHost(IP
: LongWord; Perm
: Boolean = True); overload
;
216 procedure g_Net_BanHost(IP
: string; Perm
: Boolean = True); overload
;
217 function g_Net_UnbanHost(IP
: string): Boolean; overload
;
218 function g_Net_UnbanHost(IP
: LongWord): Boolean; overload
;
219 procedure g_Net_UnbanNonPermHosts();
220 procedure g_Net_SaveBanList();
222 procedure g_Net_DumpStart();
223 procedure g_Net_DumpSendBuffer();
224 procedure g_Net_DumpRecvBuffer(Buf
: penet_uint8
; Len
: LongWord);
225 procedure g_Net_DumpEnd();
227 function g_Net_ForwardPorts(ForwardPongPort
: Boolean = True): Boolean;
228 procedure g_Net_UnforwardPorts();
230 function g_Net_UserRequestExit
: Boolean;
232 function g_Net_SendMapRequest (): Boolean;
233 function g_Net_Wait_MapInfo (var tf
: TNetFileTransfer
; resList
: TStringList
): Integer;
234 function g_Net_RequestResFileInfo (resIndex
: LongInt; out tf
: TNetFileTransfer
): Integer;
235 function g_Net_AbortResTransfer (var tf
: TNetFileTransfer
): Boolean;
236 function g_Net_ReceiveResourceFile (resIndex
: LongInt; var tf
: TNetFileTransfer
; strm
: TStream
): Integer;
243 e_input
, g_nethandler
, g_netmsg
, g_netmaster
, g_player
, g_window
, g_console
,
244 g_main
, g_game
, g_language
, g_weapons
, utils
, ctypes
,
248 FILE_CHUNK_SIZE
= 8192;
251 g_Net_DownloadTimeout
: Single;
254 { /// SERVICE FUNCTIONS /// }
255 procedure clearNetClientTransfers (var nc
: TNetClient
);
257 nc
.Transfer
.stream
.Free
;
258 nc
.Transfer
.diskName
:= ''; // just in case
259 if (nc
.Transfer
.diskBuffer
<> nil) then FreeMem(nc
.Transfer
.diskBuffer
);
260 nc
.Transfer
.stream
:= nil;
261 nc
.Transfer
.diskBuffer
:= nil;
265 procedure clearNetClient (var nc
: TNetClient
);
267 clearNetClientTransfers(nc
);
270 procedure clearNetClients (clearArray
: Boolean);
274 for f
:= Low(NetClients
) to High(NetClients
) do clearNetClient(NetClients
[f
]);
275 if (clearArray
) then SetLength(NetClients
, 0);
279 function g_Net_FindSlot(): Integer;
288 for I
:= Low(NetClients
) to High(NetClients
) do
290 if NetClients
[I
].Used
then
299 if C
>= NetMaxClients
then
307 if (Length(NetClients
) >= NetMaxClients
) then
311 SetLength(NetClients
, Length(NetClients
) + 1);
312 N
:= High(NetClients
);
318 NetClients
[N
].Used
:= True;
319 NetClients
[N
].ID
:= N
;
320 NetClients
[N
].RequestedFullUpdate
:= False;
321 NetClients
[N
].RCONAuth
:= False;
322 NetClients
[N
].Voted
:= False;
323 NetClients
[N
].Player
:= 0;
324 clearNetClientTransfers(NetClients
[N
]); // just in case
330 function g_Net_Init(): Boolean;
338 NetBuf
[NET_UNRELIABLE
].Clear();
339 NetBuf
[NET_RELIABLE
].Clear();
340 //SetLength(NetClients, 0);
341 clearNetClients(true); // clear array
347 NetAddr
.port
:= 25666;
348 SetLength(NetBannedHosts
, 0);
349 if FileExists(DataDir
+ BANLIST_FILENAME
) then
351 Assign(F
, DataDir
+ BANLIST_FILENAME
);
356 if StrToIp(IPstr
, IP
) then
363 Result
:= (enet_initialize() = 0);
366 procedure g_Net_Flush();
370 F
, Chan
: enet_uint32
;
374 Chan
:= NET_CHAN_GAME
;
376 if NetMode
= NET_SERVER
then
377 for T
:= NET_UNRELIABLE
to NET_RELIABLE
do
379 if NetBuf
[T
].CurSize
> 0 then
381 P
:= enet_packet_create(NetBuf
[T
].Data
, NetBuf
[T
].CurSize
, F
);
382 if not Assigned(P
) then continue
;
383 enet_host_broadcast(NetHost
, Chan
, P
);
387 for I
:= Low(NetClients
) to High(NetClients
) do
389 if not NetClients
[I
].Used
then continue
;
390 if NetClients
[I
].NetOut
[T
].CurSize
<= 0 then continue
;
391 P
:= enet_packet_create(NetClients
[I
].NetOut
[T
].Data
, NetClients
[I
].NetOut
[T
].CurSize
, F
);
392 if not Assigned(P
) then continue
;
393 enet_peer_send(NetClients
[I
].Peer
, Chan
, P
);
394 NetClients
[I
].NetOut
[T
].Clear();
397 // next and last iteration is always RELIABLE
398 F
:= LongWord(ENET_PACKET_FLAG_RELIABLE
);
399 Chan
:= NET_CHAN_IMPORTANT
;
401 else if NetMode
= NET_CLIENT
then
402 for T
:= NET_UNRELIABLE
to NET_RELIABLE
do
404 if NetBuf
[T
].CurSize
> 0 then
406 P
:= enet_packet_create(NetBuf
[T
].Data
, NetBuf
[T
].CurSize
, F
);
407 if not Assigned(P
) then continue
;
408 enet_peer_send(NetPeer
, Chan
, P
);
411 // next and last iteration is always RELIABLE
412 F
:= LongWord(ENET_PACKET_FLAG_RELIABLE
);
413 Chan
:= NET_CHAN_IMPORTANT
;
417 procedure g_Net_Cleanup();
421 NetBuf
[NET_UNRELIABLE
].Clear();
422 NetBuf
[NET_RELIABLE
].Clear();
424 //SetLength(NetClients, 0);
425 clearNetClients(true); // clear array
435 NetState
:= NET_STATE_NONE
;
437 NetPongSock
:= ENET_SOCKET_NULL
;
439 NetTimeToMaster
:= 0;
440 NetTimeToUpdate
:= 0;
441 NetTimeToReliable
:= 0;
445 if NetPortThread
<> NilThreadId
then
446 WaitForThreadTerminate(NetPortThread
, 66666);
448 NetPortThread
:= NilThreadId
;
449 g_Net_UnforwardPorts();
455 procedure g_Net_Free();
460 NetInitDone
:= False;
464 { /// SERVER FUNCTIONS /// }
467 function ForwardThread(Param
: Pointer): PtrInt
;
470 if not g_Net_ForwardPorts() then Result
:= -1;
473 function g_Net_Host(IPAddr
: LongWord; Port
: enet_uint16
; MaxClients
: Cardinal = 16): Boolean;
475 if NetMode
<> NET_NONE
then
477 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_INGAME
]);
484 g_Console_Add(_lc
[I_NET_MSG
] + Format(_lc
[I_NET_MSG_HOST
], [Port
]));
485 if not NetInitDone
then
487 if (not g_Net_Init()) then
489 g_Console_Add(_lc
[I_NET_MSG_FERROR
] + _lc
[I_NET_ERR_ENET
]);
497 NetAddr
.host
:= IPAddr
;
498 NetAddr
.port
:= Port
;
500 if NetForwardPorts
then NetPortThread
:= BeginThread(ForwardThread
);
502 NetHost
:= enet_host_create(@NetAddr
, NET_MAXCLIENTS
, NET_CHANS
, 0, 0);
504 if (NetHost
= nil) then
506 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + Format(_lc
[I_NET_ERR_HOST
], [Port
]));
512 NetPongSock
:= enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM
);
513 if NetPongSock
<> ENET_SOCKET_NULL
then
515 NetPongAddr
.host
:= IPAddr
;
516 NetPongAddr
.port
:= NET_PING_PORT
;
517 if enet_socket_bind(NetPongSock
, @NetPongAddr
) < 0 then
519 enet_socket_destroy(NetPongSock
);
520 NetPongSock
:= ENET_SOCKET_NULL
;
523 enet_socket_set_option(NetPongSock
, ENET_SOCKOPT_NONBLOCK
, 1);
526 NetMode
:= NET_SERVER
;
528 NetBuf
[NET_UNRELIABLE
].Clear();
529 NetBuf
[NET_RELIABLE
].Clear();
535 procedure g_Net_Host_Die();
539 if NetMode
<> NET_SERVER
then Exit
;
541 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_HOST_DISCALL
]);
542 for I
:= 0 to High(NetClients
) do
543 if NetClients
[I
].Used
then
544 enet_peer_disconnect(NetClients
[I
].Peer
, NET_DISC_DOWN
);
546 while enet_host_service(NetHost
, @NetEvent
, 1000) > 0 do
547 if NetEvent
.kind
= ENET_EVENT_TYPE_RECEIVE
then
548 enet_packet_destroy(NetEvent
.packet
);
550 for I
:= 0 to High(NetClients
) do
551 if NetClients
[I
].Used
then
553 FreeMemory(NetClients
[I
].Peer
^.data
);
554 NetClients
[I
].Peer
^.data
:= nil;
555 enet_peer_reset(NetClients
[I
].Peer
);
556 NetClients
[I
].Peer
:= nil;
557 NetClients
[I
].Used
:= False;
558 NetClients
[I
].NetOut
[NET_UNRELIABLE
].Free();
559 NetClients
[I
].NetOut
[NET_RELIABLE
].Free();
562 clearNetClients(false); // don't clear array
563 if (NetMPeer
<> nil) and (NetMHost
<> nil) then g_Net_Slist_Disconnect
;
564 if NetPongSock
<> ENET_SOCKET_NULL
then
565 enet_socket_destroy(NetPongSock
);
567 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_HOST_DIE
]);
568 enet_host_destroy(NetHost
);
573 e_WriteLog('NET: Server stopped', TMsgType
.Notify
);
577 procedure g_Net_Host_Send(ID
: Integer; Reliable
: Boolean; Chan
: Byte = NET_CHAN_GAME
);
588 if ID
> High(NetClients
) then Exit
;
589 if NetClients
[ID
].Peer
= nil then Exit
;
591 NetClients
[ID
].NetOut
[T
].Write(Integer(NetOut
.CurSize
));
592 NetClients
[ID
].NetOut
[T
].Write(NetOut
);
597 NetBuf
[T
].Write(Integer(NetOut
.CurSize
));
598 NetBuf
[T
].Write(NetOut
);
601 if NetDump
then g_Net_DumpSendBuffer();
605 procedure g_Net_Host_CheckPings();
611 Ping
: array [0..9] of Byte;
614 if NetPongSock
= ENET_SOCKET_NULL
then Exit
;
616 Buf
.data
:= Addr(Ping
[0]);
617 Buf
.dataLength
:= 2+8;
621 Len
:= enet_socket_receive(NetPongSock
, @ClAddr
, @Buf
, 1);
622 if Len
< 0 then Exit
;
624 if (Ping
[0] = Ord('D')) and (Ping
[1] = Ord('F')) then
626 ClTime
:= Int64(Addr(Ping
[2])^);
629 NetOut
.Write(Byte(Ord('D')));
630 NetOut
.Write(Byte(Ord('F')));
631 NetOut
.Write(NetPort
);
632 NetOut
.Write(ClTime
);
633 g_Net_Slist_WriteInfo();
635 if gPlayer1
<> nil then Inc(NPl
);
636 if gPlayer2
<> nil then Inc(NPl
);
638 NetOut
.Write(gNumBots
);
640 Buf
.data
:= NetOut
.Data
;
641 Buf
.dataLength
:= NetOut
.CurSize
;
642 enet_socket_send(NetPongSock
, @ClAddr
, @Buf
, 1);
650 // server packet type
651 NTF_SERVER_DONE
= 10; // done with this file
652 NTF_SERVER_FILE_INFO
= 11; // sent after client request
653 NTF_SERVER_CHUNK
= 12; // next chunk; chunk number follows
654 NTF_SERVER_ABORT
= 13; // server abort
655 NTF_SERVER_MAP_INFO
= 14;
657 // client packet type
658 NTF_CLIENT_MAP_REQUEST
= 100; // map file request; also, returns list of additional wads to download
659 NTF_CLIENT_FILE_REQUEST
= 101; // resource file request (by index)
660 NTF_CLIENT_ABORT
= 102; // do not send requested file, or abort current transfer
661 NTF_CLIENT_START
= 103; // start transfer; client may resume download by sending non-zero starting chunk
662 NTF_CLIENT_ACK
= 104; // chunk ack; chunk number follows
665 procedure KillClientByFT (var nc
: TNetClient
);
667 e_LogWritefln('disconnected client #%d due to file transfer error', [nc
.ID
], TMsgType
.Warning
);
668 enet_peer_disconnect(nc
.Peer
, NET_DISC_FILE_TIMEOUT
);
669 clearNetClientTransfers(nc
);
673 procedure ProcessHostFileTransfers (var nc
: TNetClient
);
675 tf
: ^TNetFileTransfer
;
683 if (tf
.stream
= nil) then exit
;;
685 // arbitrary timeout number
686 if (ct
-tf
.lastAckTime
>= 5000) then
691 // check if we need to send something
692 if (not tf
.inProgress
) then exit
; // waiting for the initial ack
693 // ok, we're sending chunks
694 if (tf
.lastAckChunk
<> tf
.lastSentChunk
) then exit
;
695 Inc(tf
.lastSentChunk
);
696 // do it one chunk at a time; client ack will advance our chunk counter
697 chunks
:= (tf
.size
+tf
.chunkSize
-1) div tf
.chunkSize
;
699 if (tf
.lastSentChunk
> chunks
) then
705 omsg
.Alloc(NET_BUFSIZE
);
708 if (tf
.lastSentChunk
= chunks
) then
710 // we're done with this file
711 e_LogWritefln('download: client #%d, DONE sending chunks #%d/#%d', [nc
.ID
, tf
.lastSentChunk
, chunks
]);
712 omsg
.Write(Byte(NTF_SERVER_DONE
));
713 clearNetClientTransfers(nc
);
718 omsg
.Write(Byte(NTF_SERVER_CHUNK
));
719 omsg
.Write(LongInt(tf
.lastSentChunk
));
721 rd
:= tf
.size
-(tf
.lastSentChunk
*tf
.chunkSize
);
722 if (rd
> tf
.chunkSize
) then rd
:= tf
.chunkSize
;
723 omsg
.Write(LongInt(rd
));
724 e_LogWritefln('download: client #%d, sending chunk #%d/#%d (%d bytes)', [nc
.ID
, tf
.lastSentChunk
, chunks
, rd
]);
725 //FIXME: check for errors here
727 tf
.stream
.Seek(tf
.lastSentChunk
*tf
.chunkSize
, soFromBeginning
);
728 tf
.stream
.ReadBuffer(tf
.diskBuffer
^, rd
);
729 omsg
.WriteData(tf
.diskBuffer
, rd
);
736 pkt
:= enet_packet_create(omsg
.Data
, omsg
.CurSize
, ENET_PACKET_FLAG_RELIABLE
);
737 if not Assigned(pkt
) then
742 if (enet_peer_send(nc
.Peer
, NET_CHAN_DOWNLOAD_EX
, pkt
) <> 0) then
753 // received packet is in `NetEvent`
754 procedure ProcessDownloadExPacket ();
762 tf
: ^TNetFileTransfer
;
772 // find client index by peer
773 for f
:= Low(NetClients
) to High(NetClients
) do
775 if (not NetClients
[f
].Used
) then continue
;
776 //if (NetClients[f].Transfer.stream = nil) then continue;
777 if (NetClients
[f
].Peer
= NetEvent
.peer
) then
783 e_LogWritefln('RECEIVE: dlpacket; client=%d (datalen=%u)', [nid
, NetEvent
.packet
^.dataLength
]);
785 if (nid
< 0) then exit
; // wtf?!
786 nc
:= @NetClients
[nid
];
788 if (NetEvent
.packet
^.dataLength
= 0) then
794 tf
:= @NetClients
[nid
].Transfer
;
795 tf
.lastAckTime
:= GetTimerMS();
797 cmd
:= Byte(NetEvent
.packet
^.data
^);
798 e_LogWritefln('RECEIVE: nid=%d; cmd=%u', [nid
, cmd
]);
800 NTF_CLIENT_FILE_REQUEST
: // file request
802 if (tf
.stream
<> nil) then
807 if (NetEvent
.packet
^.dataLength
< 2) then
812 // new transfer request; build packet
813 if not msg
.Init(NetEvent
.packet
^.data
+1, NetEvent
.packet
^.dataLength
-1, True) then
818 // get resource index
819 ridx
:= msg
.ReadLongInt();
820 if (ridx
< -1) or (ridx
>= gExternalResources
.Count
) then
822 e_LogWritefln('Invalid resource index %d', [ridx
], TMsgType
.Warning
);
826 if (ridx
< 0) then fname
:= MapsDir
+gGameSettings
.WAD
else fname
:= gExternalResources
[ridx
];
827 if (length(fname
) = 0) then
829 e_WriteLog('Invalid filename: '+fname
, TMsgType
.Warning
);
833 tf
.diskName
:= findDiskWad(fname
);
834 if (length(tf
.diskName
) = 0) then tf
.diskName
:= findDiskWad(GameDir
+'/wads/'+fname
);
835 if (length(tf
.diskName
) = 0) then
837 e_LogWritefln('NETWORK: file "%s" not found!', [fname
], TMsgType
.Fatal
);
843 tf
.hash
:= MD5File(tf
.diskName
);
844 // create file stream
845 tf
.diskName
:= findDiskWad(fname
);
847 tf
.stream
:= openDiskFileRO(tf
.diskName
);
851 if (tf
.stream
= nil) then
853 e_WriteLog(Format('NETWORK: file "%s" not found!', [fname
]), TMsgType
.Fatal
);
857 e_LogWritefln('client #%d requested resource #%d (file is `%s` : `%s`)', [nc
.ID
, ridx
, fname
, tf
.diskName
]);
858 tf
.size
:= tf
.stream
.size
;
859 tf
.chunkSize
:= FILE_CHUNK_SIZE
; // arbitrary
860 tf
.lastSentChunk
:= -1;
861 tf
.lastAckChunk
:= -1;
862 tf
.lastAckTime
:= GetTimerMS();
863 tf
.inProgress
:= False; // waiting for the first ACK or for the cancel
864 GetMem(tf
.diskBuffer
, tf
.chunkSize
);
865 // sent file info message
866 omsg
.Alloc(NET_BUFSIZE
);
869 omsg
.Write(Byte(NTF_SERVER_FILE_INFO
));
872 omsg
.Write(tf
.chunkSize
);
873 omsg
.Write(ExtractFileName(fname
));
874 pkt
:= enet_packet_create(omsg
.Data
, omsg
.CurSize
, ENET_PACKET_FLAG_RELIABLE
);
875 if not Assigned(pkt
) then
880 if (enet_peer_send(nc
.Peer
, NET_CHAN_DOWNLOAD_EX
, pkt
) <> 0) then
889 NTF_CLIENT_ABORT
: // do not send requested file, or abort current transfer
891 e_LogWritefln('client #%d aborted file transfer', [nc
.ID
]);
892 clearNetClientTransfers(nc
^);
894 NTF_CLIENT_START
: // start transfer; client may resume download by sending non-zero starting chunk
896 if not Assigned(tf
.stream
) then
901 if (tf
.lastSentChunk
<> -1) or (tf
.lastAckChunk
<> -1) or (tf
.inProgress
) then
903 // double ack, get lost
907 if (NetEvent
.packet
^.dataLength
< 2) then
913 if not msg
.Init(NetEvent
.packet
^.data
+1, NetEvent
.packet
^.dataLength
-1, True) then
918 chunk
:= msg
.ReadLongInt();
919 if (chunk
< 0) or (chunk
> (tf
.size
+tf
.chunkSize
-1) div tf
.chunkSize
) then
924 e_LogWritefln('client #%d started file transfer from chunk %d', [nc
.ID
, chunk
]);
925 // start sending chunks
926 tf
.inProgress
:= True;
927 tf
.lastSentChunk
:= chunk
-1;
928 tf
.lastAckChunk
:= chunk
-1;
930 NTF_CLIENT_ACK
: // chunk ack; chunk number follows
932 if not Assigned(tf
.stream
) then
937 if (tf
.lastSentChunk
< 0) or (not tf
.inProgress
) then
939 // double ack, get lost
943 if (NetEvent
.packet
^.dataLength
< 2) then
949 if not msg
.Init(NetEvent
.packet
^.data
+1, NetEvent
.packet
^.dataLength
-1, True) then
954 chunk
:= msg
.ReadLongInt();
955 if (chunk
< 0) or (chunk
> (tf
.size
+tf
.chunkSize
-1) div tf
.chunkSize
) then
960 // do it this way, so client may seek, or request retransfers for some reason
961 tf
.lastAckChunk
:= chunk
;
962 tf
.lastSentChunk
:= chunk
;
963 e_LogWritefln('client #%d acked file transfer chunk %d', [nc
.ID
, chunk
]);
965 NTF_CLIENT_MAP_REQUEST
:
967 e_LogWritefln('client #%d requested map info', [nc
.ID
]);
968 omsg
.Alloc(NET_BUFSIZE
);
971 dfn
:= findDiskWad(MapsDir
+gGameSettings
.WAD
);
972 if (dfn
= '') then dfn
:= '!wad_not_found!.wad'; //FIXME
974 st
:= openDiskFileRO(dfn
);
975 if not assigned(st
) then exit
; //wtf?!
979 omsg
.Write(Byte(NTF_SERVER_MAP_INFO
));
981 omsg
.Write(gGameSettings
.WAD
);
986 // number of external resources for map
987 omsg
.Write(LongInt(gExternalResources
.Count
));
988 // external resource names
989 for f
:= 0 to gExternalResources
.Count
-1 do
991 omsg
.Write(ExtractFileName(gExternalResources
[f
])); // GameDir+'/wads/'+ResList.Strings[i]
994 pkt
:= enet_packet_create(omsg
.Data
, omsg
.CurSize
, ENET_PACKET_FLAG_RELIABLE
);
995 if not Assigned(pkt
) then exit
;
996 if (enet_peer_send(nc
.Peer
, NET_CHAN_DOWNLOAD_EX
, pkt
) <> 0) then exit
;
1003 KillClientByFT(NetClients
[nid
]);
1010 function g_Net_Host_Update(): enet_size_t
;
1022 if NetUseMaster
then g_Net_Slist_Check
;
1023 g_Net_Host_CheckPings
;
1025 // process file transfers
1026 for f
:= Low(NetClients
) to High(NetClients
) do
1028 if (not NetClients
[f
].Used
) then continue
;
1029 if (NetClients
[f
].Transfer
.stream
= nil) then continue
;
1030 ProcessHostFileTransfers(NetClients
[f
]);
1033 while (enet_host_service(NetHost
, @NetEvent
, 0) > 0) do
1035 case (NetEvent
.kind
) of
1036 ENET_EVENT_TYPE_CONNECT
:
1038 IP
:= IpToStr(NetEvent
.Peer
^.address
.host
);
1039 Port
:= NetEvent
.Peer
^.address
.port
;
1040 g_Console_Add(_lc
[I_NET_MSG
] +
1041 Format(_lc
[I_NET_MSG_HOST_CONN
], [IP
, Port
]));
1043 if (NetEvent
.data
<> NET_PROTOCOL_VER
) then
1045 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_HOST_REJECT
] +
1046 _lc
[I_NET_DISC_PROTOCOL
]);
1047 NetEvent
.peer
^.data
:= GetMemory(SizeOf(Byte));
1048 Byte(NetEvent
.peer
^.data
^) := 255;
1049 enet_peer_disconnect(NetEvent
.peer
, NET_DISC_PROTOCOL
);
1050 enet_host_flush(NetHost
);
1054 ID
:= g_Net_FindSlot();
1058 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_HOST_REJECT
] +
1059 _lc
[I_NET_DISC_FULL
]);
1060 NetEvent
.Peer
^.data
:= GetMemory(SizeOf(Byte));
1061 Byte(NetEvent
.peer
^.data
^) := 255;
1062 enet_peer_disconnect(NetEvent
.peer
, NET_DISC_FULL
);
1063 enet_host_flush(NetHost
);
1067 NetClients
[ID
].Peer
:= NetEvent
.peer
;
1068 NetClients
[ID
].Peer
^.data
:= GetMemory(SizeOf(Byte));
1069 Byte(NetClients
[ID
].Peer
^.data
^) := ID
;
1070 NetClients
[ID
].State
:= NET_STATE_AUTH
;
1071 NetClients
[ID
].RCONAuth
:= False;
1072 NetClients
[ID
].NetOut
[NET_UNRELIABLE
].Alloc(NET_BUFSIZE
*2);
1073 NetClients
[ID
].NetOut
[NET_RELIABLE
].Alloc(NET_BUFSIZE
*2);
1074 clearNetClientTransfers(NetClients
[ID
]); // just in case
1076 enet_peer_timeout(NetEvent
.peer
, ENET_PEER_TIMEOUT_LIMIT
* 2, ENET_PEER_TIMEOUT_MINIMUM
* 2, ENET_PEER_TIMEOUT_MAXIMUM
* 2);
1078 Inc(NetClientCount
);
1079 g_Console_Add(_lc
[I_NET_MSG
] + Format(_lc
[I_NET_MSG_HOST_ADD
], [ID
]));
1082 ENET_EVENT_TYPE_RECEIVE
:
1084 //e_LogWritefln('RECEIVE: chan=%u', [NetEvent.channelID]);
1085 if (NetEvent
.channelID
= NET_CHAN_DOWNLOAD_EX
) then
1087 ProcessDownloadExPacket();
1091 ID
:= Byte(NetEvent
.peer
^.data
^);
1092 if ID
> High(NetClients
) then Exit
;
1093 TC
:= @NetClients
[ID
];
1095 if NetDump
then g_Net_DumpRecvBuffer(NetEvent
.packet
^.data
, NetEvent
.packet
^.dataLength
);
1096 g_Net_Host_HandlePacket(TC
, NetEvent
.packet
, g_Net_HostMsgHandler
);
1100 ENET_EVENT_TYPE_DISCONNECT
:
1102 ID
:= Byte(NetEvent
.peer
^.data
^);
1103 if ID
> High(NetClients
) then Exit
;
1104 clearNetClient(NetClients
[ID
]);
1105 TC
:= @NetClients
[ID
];
1106 if TC
= nil then Exit
;
1108 if not (TC
^.Used
) then Exit
;
1110 TP
:= g_Player_Get(TC
^.Player
);
1115 TP
.Kill(K_SIMPLEKILL
, 0, HIT_DISCON
);
1116 g_Console_Add(Format(_lc
[I_PLAYER_LEAVE
], [TP
.Name
]), True);
1117 e_WriteLog('NET: Client ' + TP
.Name
+ ' [' + IntToStr(ID
) + '] disconnected.', TMsgType
.Notify
);
1118 g_Player_Remove(TP
.UID
);
1122 TC
^.State
:= NET_STATE_NONE
;
1125 TC
^.RequestedFullUpdate
:= False;
1126 TC
^.NetOut
[NET_UNRELIABLE
].Free();
1127 TC
^.NetOut
[NET_RELIABLE
].Free();
1129 FreeMemory(NetEvent
.peer
^.data
);
1130 NetEvent
.peer
^.data
:= nil;
1131 g_Console_Add(_lc
[I_NET_MSG
] + Format(_lc
[I_NET_MSG_HOST_DISC
], [ID
]));
1132 Dec(NetClientCount
);
1134 if NetUseMaster
then g_Net_Slist_Update
;
1141 { /// CLIENT FUNCTIONS /// }
1144 procedure g_Net_Disconnect(Forced
: Boolean = False);
1146 if NetMode
<> NET_CLIENT
then Exit
;
1147 if (NetHost
= nil) or (NetPeer
= nil) then Exit
;
1151 enet_peer_disconnect(NetPeer
, NET_DISC_NONE
);
1153 while (enet_host_service(NetHost
, @NetEvent
, 1500) > 0) do
1155 if (NetEvent
.kind
= ENET_EVENT_TYPE_DISCONNECT
) then
1161 if (NetEvent
.kind
= ENET_EVENT_TYPE_RECEIVE
) then
1162 enet_packet_destroy(NetEvent
.packet
);
1165 if NetPeer
<> nil then
1167 enet_peer_reset(NetPeer
);
1173 e_WriteLog('NET: Kicked from server: ' + IntToStr(NetEvent
.data
), TMsgType
.Notify
);
1174 if (NetEvent
.data
<= NET_DISC_MAX
) then
1175 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_KICK
] +
1176 _lc
[TStrings_Locale(Cardinal(I_NET_DISC_NONE
) + NetEvent
.data
)], True);
1179 if NetHost
<> nil then
1181 enet_host_destroy(NetHost
);
1184 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_CLIENT_DISC
]);
1187 e_WriteLog('NET: Disconnected', TMsgType
.Notify
);
1190 procedure g_Net_Client_Send(Reliable
: Boolean; Chan
: Byte = NET_CHAN_GAME
);
1197 T
:= NET_UNRELIABLE
;
1200 NetBuf
[T
].Write(Integer(NetOut
.CurSize
));
1201 NetBuf
[T
].Write(NetOut
);
1203 if NetDump
then g_Net_DumpSendBuffer();
1205 g_Net_Flush(); // FIXME: for now, send immediately
1208 function g_Net_Client_Update(): enet_size_t
;
1211 while (enet_host_service(NetHost
, @NetEvent
, 0) > 0) do
1213 case NetEvent
.kind
of
1214 ENET_EVENT_TYPE_RECEIVE
:
1216 if (NetEvent
.channelID
= NET_CHAN_DOWNLOAD_EX
) then continue
; // ignore all download packets, they're processed by separate code
1217 if NetDump
then g_Net_DumpRecvBuffer(NetEvent
.packet
^.data
, NetEvent
.packet
^.dataLength
);
1218 g_Net_Client_HandlePacket(NetEvent
.packet
, g_Net_ClientMsgHandler
);
1221 ENET_EVENT_TYPE_DISCONNECT
:
1223 g_Net_Disconnect(True);
1231 function g_Net_Client_UpdateWhileLoading(): enet_size_t
;
1234 while (enet_host_service(NetHost
, @NetEvent
, 0) > 0) do
1236 case NetEvent
.kind
of
1237 ENET_EVENT_TYPE_RECEIVE
:
1239 if (NetEvent
.channelID
= NET_CHAN_DOWNLOAD_EX
) then continue
; // ignore all download packets, they're processed by separate code
1240 if NetDump
then g_Net_DumpRecvBuffer(NetEvent
.packet
^.data
, NetEvent
.packet
^.dataLength
);
1241 g_Net_Client_HandlePacket(NetEvent
.packet
, g_Net_ClientLightMsgHandler
);
1244 ENET_EVENT_TYPE_DISCONNECT
:
1246 g_Net_Disconnect(True);
1255 function g_Net_Connect(IP
: string; Port
: enet_uint16
): Boolean;
1258 TimeoutTime
, T
: Int64;
1260 if NetMode
<> NET_NONE
then
1262 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_ERR_INGAME
], True);
1269 g_Console_Add(_lc
[I_NET_MSG
] + Format(_lc
[I_NET_MSG_CLIENT_CONN
],
1271 if not NetInitDone
then
1273 if (not g_Net_Init()) then
1275 g_Console_Add(_lc
[I_NET_MSG_FERROR
] + _lc
[I_NET_ERR_ENET
], True);
1280 NetInitDone
:= True;
1283 NetHost
:= enet_host_create(nil, 1, NET_CHANS
, 0, 0);
1285 if (NetHost
= nil) then
1287 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CLIENT
], True);
1293 enet_address_set_host(@NetAddr
, PChar(Addr(IP
[1])));
1294 NetAddr
.port
:= Port
;
1296 NetPeer
:= enet_host_connect(NetHost
, @NetAddr
, NET_CHANS
, NET_PROTOCOL_VER
);
1298 if (NetPeer
= nil) then
1300 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CLIENT
], True);
1301 enet_host_destroy(NetHost
);
1307 // предупредить что ждем слишком долго через N секунд
1308 TimeoutTime
:= GetTimer() + NET_CONNECT_TIMEOUT
;
1313 while (enet_host_service(NetHost
, @NetEvent
, 0) > 0) do
1315 if (NetEvent
.kind
= ENET_EVENT_TYPE_CONNECT
) then
1317 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_CLIENT_DONE
]);
1318 NetMode
:= NET_CLIENT
;
1320 enet_peer_timeout(NetPeer
, ENET_PEER_TIMEOUT_LIMIT
* 2, ENET_PEER_TIMEOUT_MINIMUM
* 2, ENET_PEER_TIMEOUT_MAXIMUM
* 2);
1322 NetClientPort
:= Port
;
1330 if T
> TimeoutTime
then
1332 TimeoutTime
:= T
+ NET_CONNECT_TIMEOUT
* 100; // одного предупреждения хватит
1333 g_Console_Add(_lc
[I_NET_MSG_TIMEOUT_WARN
], True);
1334 g_Console_Add(Format(_lc
[I_NET_MSG_PORTS
], [Integer(Port
), Integer(NET_PING_PORT
)]), True);
1337 ProcessLoading(true);
1339 if e_KeyPressed(IK_SPACE
) or e_KeyPressed(IK_ESCAPE
) or e_KeyPressed(VK_ESCAPE
) or
1340 e_KeyPressed(JOY0_JUMP
) or e_KeyPressed(JOY1_JUMP
) or e_KeyPressed(JOY2_JUMP
) or e_KeyPressed(JOY3_JUMP
) then
1344 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_TIMEOUT
], True);
1345 g_Console_Add(Format(_lc
[I_NET_MSG_PORTS
], [Integer(Port
), Integer(NET_PING_PORT
)]), True);
1346 if NetPeer
<> nil then enet_peer_reset(NetPeer
);
1347 if NetHost
<> nil then
1349 enet_host_destroy(NetHost
);
1356 function IpToStr(IP
: LongWord): string;
1361 Result
:= IntToStr(PByte(Ptr
+ 0)^) + '.';
1362 Result
:= Result
+ IntToStr(PByte(Ptr
+ 1)^) + '.';
1363 Result
:= Result
+ IntToStr(PByte(Ptr
+ 2)^) + '.';
1364 Result
:= Result
+ IntToStr(PByte(Ptr
+ 3)^);
1367 function StrToIp(IPstr
: string; var IP
: LongWord): Boolean;
1371 Result
:= enet_address_set_host(@EAddr
, PChar(@IPstr
[1])) = 0;
1375 function g_Net_Client_ByName(Name
: string): pTNetClient
;
1381 for a
:= Low(NetClients
) to High(NetClients
) do
1382 if (NetClients
[a
].Used
) and (NetClients
[a
].State
= NET_STATE_GAME
) then
1384 pl
:= g_Player_Get(NetClients
[a
].Player
);
1385 if pl
= nil then continue
;
1386 if Copy(LowerCase(pl
.Name
), 1, Length(Name
)) <> LowerCase(Name
) then continue
;
1387 if NetClients
[a
].Peer
<> nil then
1389 Result
:= @NetClients
[a
];
1395 function g_Net_Client_ByPlayer(PID
: Word): pTNetClient
;
1400 for a
:= Low(NetClients
) to High(NetClients
) do
1401 if (NetClients
[a
].Used
) and (NetClients
[a
].State
= NET_STATE_GAME
) then
1402 if NetClients
[a
].Player
= PID
then
1404 Result
:= @NetClients
[a
];
1409 function g_Net_ClientName_ByID(ID
: Integer): string;
1415 if ID
= NET_EVERYONE
then
1417 for a
:= Low(NetClients
) to High(NetClients
) do
1418 if (NetClients
[a
].ID
= ID
) and (NetClients
[a
].Used
) and (NetClients
[a
].State
= NET_STATE_GAME
) then
1420 pl
:= g_Player_Get(NetClients
[a
].Player
);
1421 if pl
= nil then Exit
;
1426 procedure g_Net_SendData(Data
: AByte
; peer
: pENetPeer
; Reliable
: Boolean; Chan
: Byte = NET_CHAN_DOWNLOAD
);
1430 dataLength
: Cardinal;
1432 dataLength
:= Length(Data
);
1435 F
:= LongWord(ENET_PACKET_FLAG_RELIABLE
)
1439 if (peer
<> nil) then
1441 P
:= enet_packet_create(@Data
[0], dataLength
, F
);
1442 if not Assigned(P
) then Exit
;
1443 enet_peer_send(peer
, Chan
, P
);
1447 P
:= enet_packet_create(@Data
[0], dataLength
, F
);
1448 if not Assigned(P
) then Exit
;
1449 enet_host_broadcast(NetHost
, Chan
, P
);
1452 enet_host_flush(NetHost
);
1455 function g_Net_UserRequestExit
: Boolean;
1457 Result
:= e_KeyPressed(IK_SPACE
) or
1458 e_KeyPressed(IK_ESCAPE
) or
1459 e_KeyPressed(VK_ESCAPE
) or
1460 e_KeyPressed(JOY0_JUMP
) or
1461 e_KeyPressed(JOY1_JUMP
) or
1462 e_KeyPressed(JOY2_JUMP
) or
1463 e_KeyPressed(JOY3_JUMP
)
1467 function g_Net_Wait_Event(msgId: Word): TMemoryStream;
1472 stream: TMemoryStream;
1475 FillChar(ev, SizeOf(ev), 0);
1478 status := enet_host_service(NetHost, @ev, Trunc(g_Net_DownloadTimeout * 1000));
1482 ENET_EVENT_TYPE_RECEIVE:
1484 Ptr := ev.packet^.data;
1485 rMsgId := Byte(Ptr^);
1486 if rMsgId = msgId then
1488 stream := TMemoryStream.Create;
1489 stream.SetSize(ev.packet^.dataLength);
1490 stream.WriteBuffer(Ptr^, ev.packet^.dataLength);
1491 stream.Seek(0, soFromBeginning);
1492 status := 1 (* received *)
1496 (* looks that game state always received, so ignore it *)
1497 e_LogWritefln('g_Net_Wait_Event(%s): skip message %s', [msgId, rMsgId]);
1498 status := 2 (* continue *)
1501 ENET_EVENT_TYPE_DISCONNECT:
1503 if (ev.data <= NET_DISC_MAX) then
1504 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' ' + _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + ev.data)], True);
1505 status := -2 (* error: disconnected *)
1508 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' unknown ENet event ' + IntToStr(Ord(ev.kind)), True);
1509 status := -3 (* error: unknown event *)
1511 enet_packet_destroy(ev.packet)
1515 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' timeout reached', True);
1516 status := 0 (* error: timeout *)
1518 ProcessLoading(true);
1519 until (status <> 2) or g_Net_UserRequestExit();
1525 function getNewTimeoutEnd (): Int64;
1527 result
:= GetTimerMS();
1528 if (g_Net_DownloadTimeout
<= 0) then
1530 result
:= result
+1000*60*3; // 3 minutes
1534 result
:= result
+trunc(g_Net_DownloadTimeout
*1000);
1539 function g_Net_SendMapRequest (): Boolean;
1545 e_LogWritefln('sending map request...', []);
1547 msg
.Alloc(NET_BUFSIZE
);
1550 msg
.Write(Byte(NTF_CLIENT_MAP_REQUEST
));
1551 e_LogWritefln(' request size is %d', [msg
.CurSize
]);
1552 pkt
:= enet_packet_create(msg
.Data
, msg
.CurSize
, ENET_PACKET_FLAG_RELIABLE
);
1553 if not Assigned(pkt
) then exit
;
1554 if (enet_peer_send(NetPeer
, NET_CHAN_DOWNLOAD_EX
, pkt
) <> 0) then exit
;
1555 enet_host_flush(NetHost
);
1563 // returns `false` on error or user abort
1572 // 2 on server abort
1573 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
1574 function g_Net_Wait_MapInfo (var tf
: TNetFileTransfer
; resList
: TStringList
): Integer;
1580 freePacket
: Boolean = false;
1586 FillChar(ev
, SizeOf(ev
), 0);
1589 ett
:= getNewTimeoutEnd();
1591 status
:= enet_host_service(NetHost
, @ev
, 300);
1592 if (status
< 0) then
1594 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' network error', True);
1598 if (status
= 0) then
1600 // check for timeout
1604 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' timeout reached', True);
1613 ENET_EVENT_TYPE_RECEIVE
:
1616 if (ev
.channelID
<> NET_CHAN_DOWNLOAD_EX
) then
1618 //e_LogWritefln('g_Net_Wait_MapInfo: skip message from non-transfer channel', []);
1619 freePacket
:= false;
1620 g_Net_Client_HandlePacket(ev
.packet
, g_Net_ClientLightMsgHandler
);
1621 if (g_Res_received_map_start
) then begin result
:= -666; exit
; end;
1625 ett
:= getNewTimeoutEnd();
1626 if (ev
.packet
.dataLength
< 1) then
1628 e_LogWritefln('g_Net_Wait_MapInfo: invalid server packet (no data)', []);
1632 Ptr
:= ev
.packet
^.data
;
1633 rMsgId
:= Byte(Ptr
^);
1634 e_LogWritefln('g_Net_Wait_MapInfo: got message %u from server (dataLength=%u)', [rMsgId
, ev
.packet
^.dataLength
]);
1635 if (rMsgId
= NTF_SERVER_FILE_INFO
) then
1637 e_LogWritefln('g_Net_Wait_MapInfo: waiting for map info reply, but got file info reply', []);
1641 else if (rMsgId
= NTF_SERVER_ABORT
) then
1643 e_LogWritefln('g_Net_Wait_MapInfo: server aborted transfer', []);
1647 else if (rMsgId
= NTF_SERVER_MAP_INFO
) then
1649 e_LogWritefln('g_Net_Wait_MapInfo: creating map info packet...', []);
1650 if not msg
.Init(ev
.packet
^.data
+1, ev
.packet
^.dataLength
-1, True) then exit
;
1651 e_LogWritefln('g_Net_Wait_MapInfo: parsing map info packet (rd=%d; max=%d)...', [msg
.ReadCount
, msg
.MaxSize
]);
1654 tf
.diskName
:= msg
.ReadString();
1655 e_LogWritefln('g_Net_Wait_MapInfo: map wad is `%s`', [tf
.diskName
]);
1657 tf
.hash
:= msg
.ReadMD5();
1659 tf
.size
:= msg
.ReadLongInt();
1660 e_LogWritefln('g_Net_Wait_MapInfo: map wad size is %d', [tf
.size
]);
1661 // number of external resources for map
1662 rc
:= msg
.ReadLongInt();
1663 if (rc
< 0) or (rc
> 1024) then
1665 e_LogWritefln('g_Net_Wait_Event: invalid number of map external resources (%d)', [rc
]);
1669 e_LogWritefln('g_Net_Wait_MapInfo: map external resource count is %d', [rc
]);
1670 // external resource names
1671 for f
:= 0 to rc
-1 do
1673 s
:= ExtractFileName(msg
.ReadString());
1674 if (length(s
) = 0) then
1681 e_LogWritefln('g_Net_Wait_MapInfo: got map info', []);
1682 Result
:= 0; // success
1687 e_LogWritefln('g_Net_Wait_Event: invalid server packet type', []);
1693 ENET_EVENT_TYPE_DISCONNECT
:
1695 if (ev
.data
<= NET_DISC_MAX
) then
1696 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' ' + _lc
[TStrings_Locale(Cardinal(I_NET_DISC_NONE
) + ev
.data
)], True);
1702 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' unknown ENet event ' + IntToStr(Ord(ev
.kind
)), True);
1707 if (freePacket
) then begin freePacket
:= false; enet_packet_destroy(ev
.packet
); end;
1709 ProcessLoading(true);
1710 if g_Net_UserRequestExit() then
1712 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' user abort', True);
1718 if (freePacket
) then enet_packet_destroy(ev
.packet
);
1723 // returns `false` on error or user abort
1725 // diskName (actually, base name)
1733 // 2 on server abort
1734 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
1735 function g_Net_RequestResFileInfo (resIndex
: LongInt; out tf
: TNetFileTransfer
): Integer;
1741 freePacket
: Boolean = false;
1747 msg
.Alloc(NET_BUFSIZE
);
1750 msg
.Write(Byte(NTF_CLIENT_FILE_REQUEST
));
1751 msg
.Write(resIndex
);
1752 pkt
:= enet_packet_create(msg
.Data
, msg
.CurSize
, ENET_PACKET_FLAG_RELIABLE
);
1753 if not Assigned(pkt
) then
1758 if (enet_peer_send(NetPeer
, NET_CHAN_DOWNLOAD_EX
, pkt
) <> 0) then
1767 FillChar(ev
, SizeOf(ev
), 0);
1770 ett
:= getNewTimeoutEnd();
1772 status
:= enet_host_service(NetHost
, @ev
, 300);
1773 if (status
< 0) then
1775 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' network error', True);
1779 if (status
= 0) then
1781 // check for timeout
1785 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' timeout reached', True);
1794 ENET_EVENT_TYPE_RECEIVE
:
1797 if (ev
.channelID
<> NET_CHAN_DOWNLOAD_EX
) then
1799 //e_LogWriteln('g_Net_Wait_Event: skip message from non-transfer channel');
1800 freePacket
:= false;
1801 g_Net_Client_HandlePacket(ev
.packet
, g_Net_ClientLightMsgHandler
);
1802 if (g_Res_received_map_start
) then begin result
:= -666; exit
; end;
1806 ett
:= getNewTimeoutEnd();
1807 if (ev
.packet
.dataLength
< 1) then
1809 e_LogWriteln('g_Net_Wait_Event: invalid server packet (no data)');
1813 Ptr
:= ev
.packet
^.data
;
1814 rMsgId
:= Byte(Ptr
^);
1815 e_LogWritefln('received transfer packet with id %d (%u bytes)', [rMsgId
, ev
.packet
^.dataLength
]);
1816 if (rMsgId
= NTF_SERVER_FILE_INFO
) then
1818 if not msg
.Init(ev
.packet
^.data
+1, ev
.packet
^.dataLength
-1, True) then exit
;
1819 tf
.hash
:= msg
.ReadMD5();
1820 tf
.size
:= msg
.ReadLongInt();
1821 tf
.chunkSize
:= msg
.ReadLongInt();
1822 tf
.diskName
:= ExtractFileName(msg
.readString());
1823 if (tf
.size
< 0) or (tf
.chunkSize
<> FILE_CHUNK_SIZE
) or (length(tf
.diskName
) = 0) then
1825 e_LogWritefln('g_Net_RequestResFileInfo: invalid file info packet', []);
1829 e_LogWritefln('got file info for resource #%d: size=%d; name=%s', [resIndex
, tf
.size
, tf
.diskName
]);
1830 Result
:= 0; // success
1833 else if (rMsgId
= NTF_SERVER_ABORT
) then
1835 e_LogWriteln('g_Net_RequestResFileInfo: server aborted transfer');
1839 else if (rMsgId
= NTF_SERVER_MAP_INFO
) then
1841 e_LogWriteln('g_Net_RequestResFileInfo: waiting for map info reply, but got file info reply');
1847 e_LogWriteln('g_Net_RequestResFileInfo: invalid server packet type');
1853 ENET_EVENT_TYPE_DISCONNECT
:
1855 if (ev
.data
<= NET_DISC_MAX
) then
1856 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' ' + _lc
[TStrings_Locale(Cardinal(I_NET_DISC_NONE
) + ev
.data
)], True);
1862 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' unknown ENet event ' + IntToStr(Ord(ev
.kind
)), True);
1867 if (freePacket
) then begin freePacket
:= false; enet_packet_destroy(ev
.packet
); end;
1869 ProcessLoading(true);
1870 if g_Net_UserRequestExit() then
1872 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' user abort', True);
1878 if (freePacket
) then enet_packet_destroy(ev
.packet
);
1883 function g_Net_AbortResTransfer (var tf
: TNetFileTransfer
): Boolean;
1889 e_LogWritefln('aborting file transfer...', []);
1891 msg
.Alloc(NET_BUFSIZE
);
1894 msg
.Write(Byte(NTF_CLIENT_ABORT
));
1895 pkt
:= enet_packet_create(msg
.Data
, msg
.CurSize
, ENET_PACKET_FLAG_RELIABLE
);
1896 if not Assigned(pkt
) then exit
;
1897 if (enet_peer_send(NetPeer
, NET_CHAN_DOWNLOAD_EX
, pkt
) <> 0) then exit
;
1898 enet_host_flush(NetHost
);
1906 // returns `false` on error or user abort
1915 // 2 on server abort
1916 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
1917 function g_Net_ReceiveResourceFile (resIndex
: LongInt; var tf
: TNetFileTransfer
; strm
: TStream
): Integer;
1924 freePacket
: Boolean = false;
1927 nextChunk
: Integer = 0;
1934 msg
.Alloc(NET_BUFSIZE
);
1937 msg
.Write(Byte(NTF_CLIENT_START
));
1938 msg
.Write(LongInt(0));
1939 pkt
:= enet_packet_create(msg
.Data
, msg
.CurSize
, ENET_PACKET_FLAG_RELIABLE
);
1940 if not Assigned(pkt
) then exit
;
1941 if (enet_peer_send(NetPeer
, NET_CHAN_DOWNLOAD_EX
, pkt
) <> 0) then exit
;
1946 // wait for reply data
1947 FillChar(ev
, SizeOf(ev
), 0);
1949 GetMem(buf
, tf
.chunkSize
);
1951 ett
:= getNewTimeoutEnd();
1953 status
:= enet_host_service(NetHost
, @ev
, 300);
1954 if (status
< 0) then
1956 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' network error', True);
1960 if (status
= 0) then
1962 // check for timeout
1966 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' timeout reached', True);
1975 ENET_EVENT_TYPE_RECEIVE
:
1978 if (ev
.channelID
<> NET_CHAN_DOWNLOAD_EX
) then
1980 //e_LogWritefln('g_Net_Wait_Event: skip message from non-transfer channel', []);
1981 freePacket
:= false;
1982 g_Net_Client_HandlePacket(ev
.packet
, g_Net_ClientLightMsgHandler
);
1983 if (g_Res_received_map_start
) then begin result
:= -666; exit
; end;
1987 ett
:= getNewTimeoutEnd();
1988 if (ev
.packet
.dataLength
< 1) then
1990 e_LogWritefln('g_Net_ReceiveResourceFile: invalid server packet (no data)', []);
1994 Ptr
:= ev
.packet
^.data
;
1995 rMsgId
:= Byte(Ptr
^);
1996 if (rMsgId
= NTF_SERVER_DONE
) then
1998 e_LogWritefln('file transfer complete.', []);
2002 else if (rMsgId
= NTF_SERVER_CHUNK
) then
2004 if not msg
.Init(ev
.packet
^.data
+1, ev
.packet
^.dataLength
-1, True) then exit
;
2005 chunk
:= msg
.ReadLongInt();
2006 csize
:= msg
.ReadLongInt();
2007 if (chunk
<> nextChunk
) then
2009 e_LogWritefln('received chunk %d, but expected chunk %d', [chunk
, nextChunk
]);
2013 if (csize
< 0) or (csize
> tf
.chunkSize
) then
2015 e_LogWritefln('received chunk with size %d, but expected chunk size is %d', [csize
, tf
.chunkSize
]);
2019 e_LogWritefln('got chunk #%d of #%d (csize=%d)', [chunk
, (tf
.size
+tf
.chunkSize
-1) div tf
.chunkSize
, csize
]);
2020 msg
.ReadData(buf
, csize
);
2021 strm
.WriteBuffer(buf
^, csize
);
2022 nextChunk
:= chunk
+1;
2024 omsg
.Alloc(NET_BUFSIZE
);
2027 omsg
.Write(Byte(NTF_CLIENT_ACK
));
2028 omsg
.Write(LongInt(chunk
));
2029 pkt
:= enet_packet_create(omsg
.Data
, omsg
.CurSize
, ENET_PACKET_FLAG_RELIABLE
);
2030 if not Assigned(pkt
) then exit
;
2031 if (enet_peer_send(NetPeer
, NET_CHAN_DOWNLOAD_EX
, pkt
) <> 0) then exit
;
2036 else if (rMsgId
= NTF_SERVER_ABORT
) then
2038 e_LogWritefln('g_Net_ReceiveResourceFile: server aborted transfer', []);
2044 e_LogWritefln('g_Net_ReceiveResourceFile: invalid server packet type', []);
2050 ENET_EVENT_TYPE_DISCONNECT
:
2052 if (ev
.data
<= NET_DISC_MAX
) then
2053 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' ' + _lc
[TStrings_Locale(Cardinal(I_NET_DISC_NONE
) + ev
.data
)], True);
2059 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' unknown ENet event ' + IntToStr(Ord(ev
.kind
)), True);
2064 if (freePacket
) then begin freePacket
:= false; enet_packet_destroy(ev
.packet
); end;
2066 ProcessLoading(true);
2067 if g_Net_UserRequestExit() then
2069 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' user abort', True);
2076 if (freePacket
) then enet_packet_destroy(ev
.packet
);
2081 function g_Net_IsHostBanned(IP
: LongWord; Perm
: Boolean = False): Boolean;
2086 if NetBannedHosts
= nil then
2088 for I
:= 0 to High(NetBannedHosts
) do
2089 if (NetBannedHosts
[I
].IP
= IP
) and ((not Perm
) or (NetBannedHosts
[I
].Perm
)) then
2096 procedure g_Net_BanHost(IP
: LongWord; Perm
: Boolean = True); overload
;
2102 if g_Net_IsHostBanned(IP
, Perm
) then
2106 for I
:= Low(NetBannedHosts
) to High(NetBannedHosts
) do
2107 if NetBannedHosts
[I
].IP
= 0 then
2115 SetLength(NetBannedHosts
, Length(NetBannedHosts
) + 1);
2116 P
:= High(NetBannedHosts
);
2119 NetBannedHosts
[P
].IP
:= IP
;
2120 NetBannedHosts
[P
].Perm
:= Perm
;
2123 procedure g_Net_BanHost(IP
: string; Perm
: Boolean = True); overload
;
2128 b
:= StrToIp(IP
, a
);
2130 g_Net_BanHost(a
, Perm
);
2133 procedure g_Net_UnbanNonPermHosts();
2137 if NetBannedHosts
= nil then
2139 for I
:= Low(NetBannedHosts
) to High(NetBannedHosts
) do
2140 if (NetBannedHosts
[I
].IP
> 0) and not NetBannedHosts
[I
].Perm
then
2142 NetBannedHosts
[I
].IP
:= 0;
2143 NetBannedHosts
[I
].Perm
:= True;
2147 function g_Net_UnbanHost(IP
: string): Boolean; overload
;
2151 Result
:= StrToIp(IP
, a
);
2153 Result
:= g_Net_UnbanHost(a
);
2156 function g_Net_UnbanHost(IP
: LongWord): Boolean; overload
;
2163 if NetBannedHosts
= nil then
2165 for I
:= 0 to High(NetBannedHosts
) do
2166 if NetBannedHosts
[I
].IP
= IP
then
2168 NetBannedHosts
[I
].IP
:= 0;
2169 NetBannedHosts
[I
].Perm
:= True;
2171 // no break here to clear all bans of this host, perm and non-perm
2175 procedure g_Net_SaveBanList();
2180 Assign(F
, DataDir
+ BANLIST_FILENAME
);
2182 if NetBannedHosts
<> nil then
2183 for I
:= 0 to High(NetBannedHosts
) do
2184 if NetBannedHosts
[I
].Perm
and (NetBannedHosts
[I
].IP
> 0) then
2185 Writeln(F
, IpToStr(NetBannedHosts
[I
].IP
));
2189 procedure g_Net_DumpStart();
2191 if NetMode
= NET_SERVER
then
2192 NetDumpFile
:= createDiskFile(NETDUMP_FILENAME
+ '_server')
2194 NetDumpFile
:= createDiskFile(NETDUMP_FILENAME
+ '_client');
2197 procedure g_Net_DumpSendBuffer();
2199 writeInt(NetDumpFile
, gTime
);
2200 writeInt(NetDumpFile
, LongWord(NetOut
.CurSize
));
2201 writeInt(NetDumpFile
, Byte(1));
2202 NetDumpFile
.WriteBuffer(NetOut
.Data
^, NetOut
.CurSize
);
2205 procedure g_Net_DumpRecvBuffer(Buf
: penet_uint8
; Len
: LongWord);
2207 if (Buf
= nil) or (Len
= 0) then Exit
;
2208 writeInt(NetDumpFile
, gTime
);
2209 writeInt(NetDumpFile
, Len
);
2210 writeInt(NetDumpFile
, Byte(0));
2211 NetDumpFile
.WriteBuffer(Buf
^, Len
);
2214 procedure g_Net_DumpEnd();
2220 function g_Net_ForwardPorts(ForwardPongPort
: Boolean = True): Boolean;
2221 {$IFDEF USE_MINIUPNPC}
2226 LanAddr
: array [0..255] of Char;
2227 StrPort
: AnsiString;
2232 if NetPortForwarded
= NetPort
then
2238 NetPongForwarded
:= False;
2239 NetPortForwarded
:= 0;
2241 DevList
:= upnpDiscover(1000, nil, nil, 0, 0, 2, Addr(Err
));
2242 if DevList
= nil then
2244 conwritefln('port forwarding failed: upnpDiscover() failed: %d', [Err
]);
2248 I
:= UPNP_GetValidIGD(DevList
, @Urls
, @Data
, Addr(LanAddr
[0]), 256);
2252 conwriteln('port forwarding failed: could not find an IGD device on this LAN');
2253 FreeUPNPDevList(DevList
);
2254 FreeUPNPUrls(@Urls
);
2258 StrPort
:= IntToStr(NetPort
);
2259 I
:= UPNP_AddPortMapping(
2260 Urls
.controlURL
, Addr(data
.first
.servicetype
[1]),
2261 PChar(StrPort
), PChar(StrPort
), Addr(LanAddr
[0]), PChar('D2DF'),
2262 PChar('UDP'), nil, PChar('0')
2267 conwritefln('forwarding port %d failed: error %d', [NetPort
, I
]);
2268 FreeUPNPDevList(DevList
);
2269 FreeUPNPUrls(@Urls
);
2273 if ForwardPongPort
then
2275 StrPort
:= IntToStr(NET_PING_PORT
);
2276 I
:= UPNP_AddPortMapping(
2277 Urls
.controlURL
, Addr(data
.first
.servicetype
[1]),
2278 PChar(StrPort
), PChar(StrPort
), Addr(LanAddr
[0]), PChar('D2DF'),
2279 PChar('UDP'), nil, PChar('0')
2284 conwritefln('forwarding port %d failed: error %d', [NetPort
+ 1, I
]);
2285 NetPongForwarded
:= False;
2289 conwritefln('forwarded port %d successfully', [NetPort
+ 1]);
2290 NetPongForwarded
:= True;
2294 conwritefln('forwarded port %d successfully', [NetPort
]);
2295 NetIGDControl
:= AnsiString(Urls
.controlURL
);
2296 NetIGDService
:= data
.first
.servicetype
;
2297 NetPortForwarded
:= NetPort
;
2299 FreeUPNPDevList(DevList
);
2300 FreeUPNPUrls(@Urls
);
2309 procedure g_Net_UnforwardPorts();
2310 {$IFDEF USE_MINIUPNPC}
2313 StrPort
: AnsiString;
2315 if NetPortForwarded
= 0 then Exit
;
2317 conwriteln('unforwarding ports...');
2319 StrPort
:= IntToStr(NetPortForwarded
);
2320 I
:= UPNP_DeletePortMapping(
2321 PChar(NetIGDControl
), Addr(NetIGDService
[1]),
2322 PChar(StrPort
), PChar('UDP'), nil
2324 conwritefln(' port %d: %d', [NetPortForwarded
, I
]);
2326 if NetPongForwarded
then
2328 NetPongForwarded
:= False;
2329 StrPort
:= IntToStr(NetPortForwarded
+ 1);
2330 I
:= UPNP_DeletePortMapping(
2331 PChar(NetIGDControl
), Addr(NetIGDService
[1]),
2332 PChar(StrPort
), PChar('UDP'), nil
2334 conwritefln(' port %d: %d', [NetPortForwarded
+ 1, I
]);
2337 NetPortForwarded
:= 0;
2345 conRegVar('cl_downloadtimeout', @g_Net_DownloadTimeout
, 0.0, 1000000.0, '', 'timeout in seconds, 0 to disable it');
2346 SetLength(NetClients
, 0);
2347 g_Net_DownloadTimeout
:= 60;
2348 NetIn
.Alloc(NET_BUFSIZE
);
2349 NetOut
.Alloc(NET_BUFSIZE
);
2350 NetBuf
[NET_UNRELIABLE
].Alloc(NET_BUFSIZE
*2);
2351 NetBuf
[NET_RELIABLE
].Alloc(NET_BUFSIZE
*2);
2355 NetBuf
[NET_UNRELIABLE
].Free();
2356 NetBuf
[NET_RELIABLE
].Free();