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';
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
102 RequestedFullUpdate
: Boolean;
105 Transfer
: TNetFileTransfer
; // only one transfer may be active
106 NetOut
: array [0..1] of TMsg
;
112 pTNetClient
= ^TNetClient
;
114 AByte
= array of Byte;
117 NetInitDone
: Boolean = False;
118 NetMode
: Byte = NET_NONE
;
119 NetDump
: Boolean = False;
121 NetServerName
: string = 'Unnamed Server';
122 NetPassword
: string = '';
123 NetPort
: Word = 25666;
125 NetAllowRCON
: Boolean = False;
126 NetRCONPassword
: string = '';
128 NetTimeToUpdate
: Cardinal = 0;
129 NetTimeToReliable
: Cardinal = 0;
130 NetTimeToMaster
: Cardinal = 0;
132 NetHost
: pENetHost
= nil;
133 NetPeer
: pENetPeer
= nil;
135 NetAddr
: ENetAddress
;
137 NetPongAddr
: ENetAddress
;
138 NetPongSock
: ENetSocket
= ENET_SOCKET_NULL
;
140 NetUseMaster
: Boolean = True;
141 NetSlistAddr
: ENetAddress
;
142 NetSlistIP
: string = 'mpms.doom2d.org';
143 NetSlistPort
: Word = 25665;
145 NetClientIP
: string = '127.0.0.1';
146 NetClientPort
: Word = 25666;
149 NetBuf
: array [0..1] of TMsg
;
151 NetClients
: array of TNetClient
;
152 NetClientCount
: Byte = 0;
153 NetMaxClients
: Byte = 255;
154 NetBannedHosts
: array of TBanRecord
;
156 NetState
: Integer = NET_STATE_NONE
;
158 NetMyID
: Integer = -1;
159 NetPlrUID1
: Integer = -1;
160 NetPlrUID2
: Integer = -1;
162 NetInterpLevel
: Integer = 1;
163 NetUpdateRate
: Cardinal = 0; // as soon as possible
164 NetRelupdRate
: Cardinal = 18; // around two times a second
165 NetMasterRate
: Cardinal = 60000;
167 NetForcePlayerUpdate
: Boolean = False;
168 NetPredictSelf
: Boolean = True;
169 NetForwardPorts
: Boolean = False;
171 NetGotEverything
: Boolean = False;
172 NetGotKeys
: Boolean = False;
174 {$IFDEF USE_MINIUPNPC}
175 NetPortForwarded
: Word = 0;
176 NetPongForwarded
: Boolean = False;
177 NetIGDControl
: AnsiString;
178 NetIGDService
: TURLStr
;
181 NetPortThread
: TThreadID
= NilThreadId
;
183 NetDumpFile
: TStream
;
185 g_Res_received_map_start
: Integer = 0; // set if we received "map change" event
188 function g_Net_Init(): Boolean;
189 procedure g_Net_Cleanup();
190 procedure g_Net_Free();
191 procedure g_Net_Flush();
193 function g_Net_Host(IPAddr
: LongWord; Port
: enet_uint16
; MaxClients
: Cardinal = 16): Boolean;
194 procedure g_Net_Host_Die();
195 procedure g_Net_Host_Send(ID
: Integer; Reliable
: Boolean; Chan
: Byte = NET_CHAN_GAME
);
196 function g_Net_Host_Update(): enet_size_t
;
198 function g_Net_Connect(IP
: string; Port
: enet_uint16
): Boolean;
199 procedure g_Net_Disconnect(Forced
: Boolean = False);
200 procedure g_Net_Client_Send(Reliable
: Boolean; Chan
: Byte = NET_CHAN_GAME
);
201 function g_Net_Client_Update(): enet_size_t
;
202 function g_Net_Client_UpdateWhileLoading(): enet_size_t
;
204 function g_Net_Client_ByName(Name
: string): pTNetClient
;
205 function g_Net_Client_ByPlayer(PID
: Word): pTNetClient
;
206 function g_Net_ClientName_ByID(ID
: Integer): string;
208 procedure g_Net_SendData(Data
: AByte
; peer
: pENetPeer
; Reliable
: Boolean; Chan
: Byte = NET_CHAN_DOWNLOAD
);
209 //function g_Net_Wait_Event(msgId: Word): TMemoryStream;
210 //function g_Net_Wait_FileInfo (var tf: TNetFileTransfer; asMap: Boolean; out resList: TStringList): Integer;
212 function IpToStr(IP
: LongWord): string;
213 function StrToIp(IPstr
: string; var IP
: LongWord): Boolean;
215 function g_Net_IsHostBanned(IP
: LongWord; Perm
: Boolean = False): Boolean;
216 procedure g_Net_BanHost(IP
: LongWord; Perm
: Boolean = True); overload
;
217 procedure g_Net_BanHost(IP
: string; Perm
: Boolean = True); overload
;
218 function g_Net_UnbanHost(IP
: string): Boolean; overload
;
219 function g_Net_UnbanHost(IP
: LongWord): Boolean; overload
;
220 procedure g_Net_UnbanNonPermHosts();
221 procedure g_Net_SaveBanList();
223 procedure g_Net_DumpStart();
224 procedure g_Net_DumpSendBuffer();
225 procedure g_Net_DumpRecvBuffer(Buf
: penet_uint8
; Len
: LongWord);
226 procedure g_Net_DumpEnd();
228 function g_Net_ForwardPorts(ForwardPongPort
: Boolean = True): Boolean;
229 procedure g_Net_UnforwardPorts();
231 function g_Net_UserRequestExit
: 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;
255 //**************************************************************************
259 //**************************************************************************
261 procedure clearNetClientTransfers (var nc
: TNetClient
);
263 nc
.Transfer
.stream
.Free
;
264 nc
.Transfer
.diskName
:= ''; // just in case
265 if (nc
.Transfer
.diskBuffer
<> nil) then FreeMem(nc
.Transfer
.diskBuffer
);
266 nc
.Transfer
.stream
:= nil;
267 nc
.Transfer
.diskBuffer
:= nil;
271 procedure clearNetClient (var nc
: TNetClient
);
273 clearNetClientTransfers(nc
);
277 procedure clearNetClients (clearArray
: Boolean);
281 for f
:= Low(NetClients
) to High(NetClients
) do clearNetClient(NetClients
[f
]);
282 if (clearArray
) then SetLength(NetClients
, 0);
286 function g_Net_UserRequestExit (): Boolean;
288 Result
:= {e_KeyPressed(IK_SPACE) or}
289 e_KeyPressed(IK_ESCAPE
) or
290 e_KeyPressed(VK_ESCAPE
) or
291 e_KeyPressed(JOY0_JUMP
) or
292 e_KeyPressed(JOY1_JUMP
) or
293 e_KeyPressed(JOY2_JUMP
) or
294 e_KeyPressed(JOY3_JUMP
)
298 //**************************************************************************
300 // file transfer declaraions and host packet processor
302 //**************************************************************************
305 // server packet type
306 NTF_SERVER_DONE
= 10; // done with this file
307 NTF_SERVER_FILE_INFO
= 11; // sent after client request
308 NTF_SERVER_CHUNK
= 12; // next chunk; chunk number follows
309 NTF_SERVER_ABORT
= 13; // server abort
310 NTF_SERVER_MAP_INFO
= 14;
312 // client packet type
313 NTF_CLIENT_MAP_REQUEST
= 100; // map file request; also, returns list of additional wads to download
314 NTF_CLIENT_FILE_REQUEST
= 101; // resource file request (by index)
315 NTF_CLIENT_ABORT
= 102; // do not send requested file, or abort current transfer
316 NTF_CLIENT_START
= 103; // start transfer; client may resume download by sending non-zero starting chunk
317 NTF_CLIENT_ACK
= 104; // chunk ack; chunk number follows
320 // disconnect client due to some file transfer error
321 procedure killClientByFT (var nc
: TNetClient
);
323 e_LogWritefln('disconnected client #%d due to file transfer error', [nc
.ID
], TMsgType
.Warning
);
324 enet_peer_disconnect(nc
.Peer
, NET_DISC_FILE_TIMEOUT
);
325 clearNetClientTransfers(nc
);
329 // send file transfer message from server to client
330 function ftransSendServerMsg (var nc
: TNetClient
; var m
: TMsg
): Boolean;
335 if (m
.CurSize
< 1) then exit
;
336 pkt
:= enet_packet_create(m
.Data
, m
.CurSize
, ENET_PACKET_FLAG_RELIABLE
);
337 if not Assigned(pkt
) then begin killClientByFT(nc
); exit
; end;
338 if (enet_peer_send(nc
.Peer
, NET_CHAN_DOWNLOAD_EX
, pkt
) <> 0) then begin killClientByFT(nc
); exit
; end;
343 // send file transfer message from client to server
344 function ftransSendClientMsg (var m
: TMsg
): Boolean;
349 if (m
.CurSize
< 1) then exit
;
350 pkt
:= enet_packet_create(m
.Data
, m
.CurSize
, ENET_PACKET_FLAG_RELIABLE
);
351 if not Assigned(pkt
) then exit
;
352 if (enet_peer_send(NetPeer
, NET_CHAN_DOWNLOAD_EX
, pkt
) <> 0) then exit
;
358 procedure ProcessChunkSend (var nc
: TNetClient
);
360 tf
: ^TNetFileTransfer
;
366 if (tf
.stream
= nil) then exit
;
368 // arbitrary timeout number
369 if (ct
-tf
.lastAckTime
>= 5000) then
374 // check if we need to send something
375 if (not tf
.inProgress
) then exit
; // waiting for the initial ack
376 // ok, we're sending chunks
377 if (tf
.lastAckChunk
<> tf
.lastSentChunk
) then exit
;
378 Inc(tf
.lastSentChunk
);
379 // do it one chunk at a time; client ack will advance our chunk counter
380 chunks
:= (tf
.size
+tf
.chunkSize
-1) div tf
.chunkSize
;
382 if (tf
.lastSentChunk
> chunks
) then
389 if (tf
.lastSentChunk
= chunks
) then
391 // we're done with this file
392 e_LogWritefln('download: client #%d, DONE sending chunks #%d/#%d', [nc
.ID
, tf
.lastSentChunk
, chunks
]);
393 trans_omsg
.Write(Byte(NTF_SERVER_DONE
));
394 clearNetClientTransfers(nc
);
399 trans_omsg
.Write(Byte(NTF_SERVER_CHUNK
));
400 trans_omsg
.Write(LongInt(tf
.lastSentChunk
));
402 rd
:= tf
.size
-(tf
.lastSentChunk
*tf
.chunkSize
);
403 if (rd
> tf
.chunkSize
) then rd
:= tf
.chunkSize
;
404 trans_omsg
.Write(LongInt(rd
));
405 //e_LogWritefln('download: client #%d, sending chunk #%d/#%d (%d bytes)', [nc.ID, tf.lastSentChunk, chunks, rd]);
406 //FIXME: check for errors here
408 tf
.stream
.Seek(tf
.lastSentChunk
*tf
.chunkSize
, soFromBeginning
);
409 tf
.stream
.ReadBuffer(tf
.diskBuffer
^, rd
);
410 trans_omsg
.WriteData(tf
.diskBuffer
, rd
);
417 ftransSendServerMsg(nc
, trans_omsg
);
421 // server file transfer packet processor
422 // received packet is in `NetEvent`
423 procedure ProcessDownloadExPacket ();
430 tf
: ^TNetFileTransfer
;
439 // find client index by peer
440 for f
:= Low(NetClients
) to High(NetClients
) do
442 if (not NetClients
[f
].Used
) then continue
;
443 if (NetClients
[f
].Peer
= NetEvent
.peer
) then
449 //e_LogWritefln('RECEIVE: dlpacket; client=%d (datalen=%u)', [nid, NetEvent.packet^.dataLength]);
451 if (nid
< 0) then exit
; // wtf?!
452 nc
:= @NetClients
[nid
];
454 if (NetEvent
.packet
^.dataLength
= 0) then
460 tf
:= @NetClients
[nid
].Transfer
;
461 tf
.lastAckTime
:= GetTimerMS();
463 cmd
:= Byte(NetEvent
.packet
^.data
^);
464 //e_LogWritefln('RECEIVE: nid=%d; cmd=%u', [nid, cmd]);
466 NTF_CLIENT_FILE_REQUEST
: // file request
468 if (tf
.stream
<> nil) then
473 if (NetEvent
.packet
^.dataLength
< 2) then
478 // new transfer request; build packet
479 if not msg
.Init(NetEvent
.packet
^.data
+1, NetEvent
.packet
^.dataLength
-1, True) then
484 // get resource index
485 ridx
:= msg
.ReadLongInt();
486 if (ridx
< -1) or (ridx
>= gExternalResources
.Count
) then
488 e_LogWritefln('Invalid resource index %d', [ridx
], TMsgType
.Warning
);
492 if (ridx
< 0) then fname
:= MapsDir
+gGameSettings
.WAD
else fname
:= GameDir
+'/wads/'+gExternalResources
[ridx
];
493 if (length(fname
) = 0) then
495 e_WriteLog('Invalid filename: '+fname
, TMsgType
.Warning
);
499 tf
.diskName
:= findDiskWad(fname
);
500 //if (length(tf.diskName) = 0) then tf.diskName := findDiskWad(GameDir+'/wads/'+fname);
501 if (length(tf
.diskName
) = 0) then
503 e_LogWritefln('NETWORK: file "%s" not found!', [fname
], TMsgType
.Fatal
);
509 tf
.hash
:= MD5File(tf
.diskName
);
510 // create file stream
511 tf
.diskName
:= findDiskWad(fname
);
513 tf
.stream
:= openDiskFileRO(tf
.diskName
);
517 if (tf
.stream
= nil) then
519 e_WriteLog(Format('NETWORK: file "%s" not found!', [fname
]), TMsgType
.Fatal
);
523 e_LogWritefln('client #%d requested resource #%d (file is `%s` : `%s`)', [nc
.ID
, ridx
, fname
, tf
.diskName
]);
524 tf
.size
:= tf
.stream
.size
;
525 tf
.chunkSize
:= FILE_CHUNK_SIZE
; // arbitrary
526 tf
.lastSentChunk
:= -1;
527 tf
.lastAckChunk
:= -1;
528 tf
.lastAckTime
:= GetTimerMS();
529 tf
.inProgress
:= False; // waiting for the first ACK or for the cancel
530 GetMem(tf
.diskBuffer
, tf
.chunkSize
);
531 // sent file info message
533 trans_omsg
.Write(Byte(NTF_SERVER_FILE_INFO
));
534 trans_omsg
.Write(tf
.hash
);
535 trans_omsg
.Write(tf
.size
);
536 trans_omsg
.Write(tf
.chunkSize
);
537 trans_omsg
.Write(ExtractFileName(fname
));
538 if not ftransSendServerMsg(nc
^, trans_omsg
) then exit
;
540 NTF_CLIENT_ABORT
: // do not send requested file, or abort current transfer
542 e_LogWritefln('client #%d aborted file transfer', [nc
.ID
]);
543 clearNetClientTransfers(nc
^);
545 NTF_CLIENT_START
: // start transfer; client may resume download by sending non-zero starting chunk
547 if not Assigned(tf
.stream
) then
552 if (tf
.lastSentChunk
<> -1) or (tf
.lastAckChunk
<> -1) or (tf
.inProgress
) then
554 // double ack, get lost
558 if (NetEvent
.packet
^.dataLength
< 2) then
564 if not msg
.Init(NetEvent
.packet
^.data
+1, NetEvent
.packet
^.dataLength
-1, True) then
569 chunk
:= msg
.ReadLongInt();
570 if (chunk
< 0) or (chunk
> (tf
.size
+tf
.chunkSize
-1) div tf
.chunkSize
) then
575 e_LogWritefln('client #%d started file transfer from chunk %d', [nc
.ID
, chunk
]);
576 // start sending chunks
577 tf
.inProgress
:= True;
578 tf
.lastSentChunk
:= chunk
-1;
579 tf
.lastAckChunk
:= chunk
-1;
580 ProcessChunkSend(nc
^);
582 NTF_CLIENT_ACK
: // chunk ack; chunk number follows
584 if not Assigned(tf
.stream
) then
589 if (tf
.lastSentChunk
< 0) or (not tf
.inProgress
) then
591 // double ack, get lost
595 if (NetEvent
.packet
^.dataLength
< 2) then
601 if not msg
.Init(NetEvent
.packet
^.data
+1, NetEvent
.packet
^.dataLength
-1, True) then
606 chunk
:= msg
.ReadLongInt();
607 if (chunk
< 0) or (chunk
> (tf
.size
+tf
.chunkSize
-1) div tf
.chunkSize
) then
612 // do it this way, so client may seek, or request retransfers for some reason
613 tf
.lastAckChunk
:= chunk
;
614 tf
.lastSentChunk
:= chunk
;
615 //e_LogWritefln('client #%d acked file transfer chunk %d', [nc.ID, chunk]);
616 ProcessChunkSend(nc
^);
618 NTF_CLIENT_MAP_REQUEST
:
620 e_LogWritefln('client #%d requested map info', [nc
.ID
]);
622 dfn
:= findDiskWad(MapsDir
+gGameSettings
.WAD
);
623 if (dfn
= '') then dfn
:= '!wad_not_found!.wad'; //FIXME
625 st
:= openDiskFileRO(dfn
);
626 if not assigned(st
) then exit
; //wtf?!
630 trans_omsg
.Write(Byte(NTF_SERVER_MAP_INFO
));
632 trans_omsg
.Write(gGameSettings
.WAD
);
634 trans_omsg
.Write(md5
);
636 trans_omsg
.Write(size
);
637 // number of external resources for map
638 trans_omsg
.Write(LongInt(gExternalResources
.Count
));
639 // external resource names
640 for f
:= 0 to gExternalResources
.Count
-1 do
642 trans_omsg
.Write(ExtractFileName(gExternalResources
[f
])); // GameDir+'/wads/'+ResList.Strings[i]
645 if not ftransSendServerMsg(nc
^, trans_omsg
) then exit
;
656 //**************************************************************************
658 // file transfer crap (both client and server)
660 //**************************************************************************
662 function getNewTimeoutEnd (): Int64;
664 result
:= GetTimerMS();
665 if (g_Net_DownloadTimeout
<= 0) then
667 result
:= result
+1000*60*3; // 3 minutes
671 result
:= result
+trunc(g_Net_DownloadTimeout
*1000);
676 // send map request to server, and wait for "map info" server reply
678 // returns `false` on error or user abort
688 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
689 function g_Net_Wait_MapInfo (var tf
: TNetFileTransfer
; resList
: TStringList
): Integer;
695 freePacket
: Boolean = false;
703 trans_omsg
.Write(Byte(NTF_CLIENT_MAP_REQUEST
));
704 if not ftransSendClientMsg(trans_omsg
) then begin result
:= -1; exit
; end;
706 FillChar(ev
, SizeOf(ev
), 0);
709 ett
:= getNewTimeoutEnd();
711 status
:= enet_host_service(NetHost
, @ev
, 300);
714 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' network error', True);
724 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' timeout reached', True);
733 ENET_EVENT_TYPE_RECEIVE
:
736 if (ev
.channelID
<> NET_CHAN_DOWNLOAD_EX
) then
738 //e_LogWritefln('g_Net_Wait_MapInfo: skip message from non-transfer channel', []);
740 g_Net_Client_HandlePacket(ev
.packet
, g_Net_ClientLightMsgHandler
);
741 if (g_Res_received_map_start
< 0) then begin result
:= -666; exit
; end;
745 ett
:= getNewTimeoutEnd();
746 if (ev
.packet
.dataLength
< 1) then
748 e_LogWritefln('g_Net_Wait_MapInfo: invalid server packet (no data)', []);
752 Ptr
:= ev
.packet
^.data
;
753 rMsgId
:= Byte(Ptr
^);
754 e_LogWritefln('g_Net_Wait_MapInfo: got message %u from server (dataLength=%u)', [rMsgId
, ev
.packet
^.dataLength
]);
755 if (rMsgId
= NTF_SERVER_FILE_INFO
) then
757 e_LogWritefln('g_Net_Wait_MapInfo: waiting for map info reply, but got file info reply', []);
761 else if (rMsgId
= NTF_SERVER_ABORT
) then
763 e_LogWritefln('g_Net_Wait_MapInfo: server aborted transfer', []);
767 else if (rMsgId
= NTF_SERVER_MAP_INFO
) then
769 e_LogWritefln('g_Net_Wait_MapInfo: creating map info packet...', []);
770 if not msg
.Init(ev
.packet
^.data
+1, ev
.packet
^.dataLength
-1, True) then exit
;
771 e_LogWritefln('g_Net_Wait_MapInfo: parsing map info packet (rd=%d; max=%d)...', [msg
.ReadCount
, msg
.MaxSize
]);
774 tf
.diskName
:= msg
.ReadString();
775 e_LogWritefln('g_Net_Wait_MapInfo: map wad is `%s`', [tf
.diskName
]);
777 tf
.hash
:= msg
.ReadMD5();
779 tf
.size
:= msg
.ReadLongInt();
780 e_LogWritefln('g_Net_Wait_MapInfo: map wad size is %d', [tf
.size
]);
781 // number of external resources for map
782 rc
:= msg
.ReadLongInt();
783 if (rc
< 0) or (rc
> 1024) then
785 e_LogWritefln('g_Net_Wait_Event: invalid number of map external resources (%d)', [rc
]);
789 e_LogWritefln('g_Net_Wait_MapInfo: map external resource count is %d', [rc
]);
790 // external resource names
791 for f
:= 0 to rc
-1 do
793 s
:= ExtractFileName(msg
.ReadString());
794 if (length(s
) = 0) then
801 e_LogWritefln('g_Net_Wait_MapInfo: got map info', []);
802 Result
:= 0; // success
807 e_LogWritefln('g_Net_Wait_Event: invalid server packet type', []);
813 ENET_EVENT_TYPE_DISCONNECT
:
815 if (ev
.data
<= NET_DISC_MAX
) then
816 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' ' + _lc
[TStrings_Locale(Cardinal(I_NET_DISC_NONE
) + ev
.data
)], True);
822 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' unknown ENet event ' + IntToStr(Ord(ev
.kind
)), True);
827 if (freePacket
) then begin freePacket
:= false; enet_packet_destroy(ev
.packet
); end;
830 if g_Net_UserRequestExit() then
832 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' user abort', True);
838 if (freePacket
) then enet_packet_destroy(ev
.packet
);
843 // send file request to server, and wait for server reply
845 // returns `false` on error or user abort
847 // diskName (actually, base name)
856 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
857 function g_Net_RequestResFileInfo (resIndex
: LongInt; out tf
: TNetFileTransfer
): Integer;
863 freePacket
: Boolean = false;
869 trans_omsg
.Write(Byte(NTF_CLIENT_FILE_REQUEST
));
870 trans_omsg
.Write(resIndex
);
871 if not ftransSendClientMsg(trans_omsg
) then begin result
:= -1; exit
; end;
873 FillChar(ev
, SizeOf(ev
), 0);
876 ett
:= getNewTimeoutEnd();
878 status
:= enet_host_service(NetHost
, @ev
, 300);
881 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' network error', True);
891 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' timeout reached', True);
900 ENET_EVENT_TYPE_RECEIVE
:
903 if (ev
.channelID
<> NET_CHAN_DOWNLOAD_EX
) then
905 //e_LogWriteln('g_Net_Wait_Event: skip message from non-transfer channel');
907 g_Net_Client_HandlePacket(ev
.packet
, g_Net_ClientLightMsgHandler
);
908 if (g_Res_received_map_start
< 0) then begin result
:= -666; exit
; end;
912 ett
:= getNewTimeoutEnd();
913 if (ev
.packet
.dataLength
< 1) then
915 e_LogWriteln('g_Net_Wait_Event: invalid server packet (no data)');
919 Ptr
:= ev
.packet
^.data
;
920 rMsgId
:= Byte(Ptr
^);
921 e_LogWritefln('received transfer packet with id %d (%u bytes)', [rMsgId
, ev
.packet
^.dataLength
]);
922 if (rMsgId
= NTF_SERVER_FILE_INFO
) then
924 if not msg
.Init(ev
.packet
^.data
+1, ev
.packet
^.dataLength
-1, True) then exit
;
925 tf
.hash
:= msg
.ReadMD5();
926 tf
.size
:= msg
.ReadLongInt();
927 tf
.chunkSize
:= msg
.ReadLongInt();
928 tf
.diskName
:= ExtractFileName(msg
.readString());
929 if (tf
.size
< 0) or (tf
.chunkSize
<> FILE_CHUNK_SIZE
) or (length(tf
.diskName
) = 0) then
931 e_LogWritefln('g_Net_RequestResFileInfo: invalid file info packet', []);
935 e_LogWritefln('got file info for resource #%d: size=%d; name=%s', [resIndex
, tf
.size
, tf
.diskName
]);
936 Result
:= 0; // success
939 else if (rMsgId
= NTF_SERVER_ABORT
) then
941 e_LogWriteln('g_Net_RequestResFileInfo: server aborted transfer');
945 else if (rMsgId
= NTF_SERVER_MAP_INFO
) then
947 e_LogWriteln('g_Net_RequestResFileInfo: waiting for map info reply, but got file info reply');
953 e_LogWriteln('g_Net_RequestResFileInfo: invalid server packet type');
959 ENET_EVENT_TYPE_DISCONNECT
:
961 if (ev
.data
<= NET_DISC_MAX
) then
962 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' ' + _lc
[TStrings_Locale(Cardinal(I_NET_DISC_NONE
) + ev
.data
)], True);
968 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' unknown ENet event ' + IntToStr(Ord(ev
.kind
)), True);
973 if (freePacket
) then begin freePacket
:= false; enet_packet_destroy(ev
.packet
); end;
976 if g_Net_UserRequestExit() then
978 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' user abort', True);
984 if (freePacket
) then enet_packet_destroy(ev
.packet
);
989 // call this to cancel file transfer requested by `g_Net_RequestResFileInfo()`
990 function g_Net_AbortResTransfer (var tf
: TNetFileTransfer
): Boolean;
993 e_LogWritefln('aborting file transfer...', []);
996 trans_omsg
.Write(Byte(NTF_CLIENT_ABORT
));
997 result
:= ftransSendClientMsg(trans_omsg
);
998 if result
then enet_host_flush(NetHost
);
1002 // call this to start file transfer requested by `g_Net_RequestResFileInfo()`
1004 // returns `false` on error or user abort
1013 // 2 on server abort
1014 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
1015 function g_Net_ReceiveResourceFile (resIndex
: LongInt; var tf
: TNetFileTransfer
; strm
: TStream
): Integer;
1021 freePacket
: Boolean = false;
1024 nextChunk
: Integer = 0;
1025 chunkTotal
: Integer;
1032 tf
.resumed
:= false;
1033 e_LogWritefln('file `%s`, size=%d (%d)', [tf
.diskName
, Integer(strm
.size
), tf
.size
], TMsgType
.Notify
);
1034 // check if we should resume downloading
1035 resumed
:= (strm
.size
> tf
.chunkSize
) and (strm
.size
< tf
.size
);
1038 trans_omsg
.Write(Byte(NTF_CLIENT_START
));
1039 if resumed
then chunk
:= strm
.size
div tf
.chunkSize
else chunk
:= 0;
1040 trans_omsg
.Write(LongInt(chunk
));
1041 if not ftransSendClientMsg(trans_omsg
) then begin result
:= -1; exit
; end;
1043 strm
.Seek(chunk
*tf
.chunkSize
, soFromBeginning
);
1044 chunkTotal
:= (tf
.size
+tf
.chunkSize
-1) div tf
.chunkSize
;
1045 e_LogWritefln('receiving file `%s` (%d chunks)', [tf
.diskName
, chunkTotal
], TMsgType
.Notify
);
1046 g_Game_SetLoadingText('downloading "'+ExtractFileName(tf
.diskName
)+'"', chunkTotal
, False);
1047 tf
.resumed
:= resumed
;
1049 if (chunk
> 0) then g_Game_StepLoading(chunk
);
1052 // wait for reply data
1053 FillChar(ev
, SizeOf(ev
), 0);
1055 GetMem(buf
, tf
.chunkSize
);
1057 ett
:= getNewTimeoutEnd();
1059 //stx := -GetTimerMS();
1060 status
:= enet_host_service(NetHost
, @ev
, 300);
1061 if (status
< 0) then
1063 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' network error', True);
1067 if (status
= 0) then
1069 // check for timeout
1073 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' timeout reached', True);
1082 ENET_EVENT_TYPE_RECEIVE
:
1085 if (ev
.channelID
<> NET_CHAN_DOWNLOAD_EX
) then
1087 //e_LogWritefln('g_Net_Wait_Event: skip message from non-transfer channel', []);
1088 freePacket
:= false;
1089 g_Net_Client_HandlePacket(ev
.packet
, g_Net_ClientLightMsgHandler
);
1090 if (g_Res_received_map_start
< 0) then begin result
:= -666; exit
; end;
1094 //stx := stx+GetTimerMS();
1095 //e_LogWritefln('g_Net_ReceiveResourceFile: stx=%d', [Integer(stx)]);
1096 //stx := -GetTimerMS();
1097 ett
:= getNewTimeoutEnd();
1098 if (ev
.packet
.dataLength
< 1) then
1100 e_LogWritefln('g_Net_ReceiveResourceFile: invalid server packet (no data)', []);
1104 Ptr
:= ev
.packet
^.data
;
1105 rMsgId
:= Byte(Ptr
^);
1106 if (rMsgId
= NTF_SERVER_DONE
) then
1108 e_LogWritefln('file transfer complete.', []);
1112 else if (rMsgId
= NTF_SERVER_CHUNK
) then
1114 if not msg
.Init(ev
.packet
^.data
+1, ev
.packet
^.dataLength
-1, True) then exit
;
1115 chunk
:= msg
.ReadLongInt();
1116 csize
:= msg
.ReadLongInt();
1117 if (chunk
<> nextChunk
) then
1119 e_LogWritefln('received chunk %d, but expected chunk %d', [chunk
, nextChunk
]);
1123 if (csize
< 0) or (csize
> tf
.chunkSize
) then
1125 e_LogWritefln('received chunk with size %d, but expected chunk size is %d', [csize
, tf
.chunkSize
]);
1129 //e_LogWritefln('got chunk #%d of #%d (csize=%d)', [chunk, (tf.size+tf.chunkSize-1) div tf.chunkSize, csize]);
1130 msg
.ReadData(buf
, csize
);
1131 strm
.WriteBuffer(buf
^, csize
);
1132 nextChunk
:= chunk
+1;
1133 g_Game_StepLoading();
1136 trans_omsg
.Write(Byte(NTF_CLIENT_ACK
));
1137 trans_omsg
.Write(LongInt(chunk
));
1138 if not ftransSendClientMsg(trans_omsg
) then begin result
:= -1; exit
; end;
1140 else if (rMsgId
= NTF_SERVER_ABORT
) then
1142 e_LogWritefln('g_Net_ReceiveResourceFile: server aborted transfer', []);
1148 e_LogWritefln('g_Net_ReceiveResourceFile: invalid server packet type', []);
1152 //stx := stx+GetTimerMS();
1153 //e_LogWritefln('g_Net_ReceiveResourceFile: process stx=%d', [Integer(stx)]);
1156 ENET_EVENT_TYPE_DISCONNECT
:
1158 if (ev
.data
<= NET_DISC_MAX
) then
1159 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' ' + _lc
[TStrings_Locale(Cardinal(I_NET_DISC_NONE
) + ev
.data
)], True);
1165 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' unknown ENet event ' + IntToStr(Ord(ev
.kind
)), True);
1170 if (freePacket
) then begin freePacket
:= false; enet_packet_destroy(ev
.packet
); end;
1173 if g_Net_UserRequestExit() then
1175 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' user abort', True);
1182 if (freePacket
) then enet_packet_destroy(ev
.packet
);
1187 //**************************************************************************
1191 //**************************************************************************
1193 function g_Net_FindSlot(): Integer;
1202 for I
:= Low(NetClients
) to High(NetClients
) do
1204 if NetClients
[I
].Used
then
1213 if C
>= NetMaxClients
then
1221 if (Length(NetClients
) >= NetMaxClients
) then
1225 SetLength(NetClients
, Length(NetClients
) + 1);
1226 N
:= High(NetClients
);
1232 NetClients
[N
].Used
:= True;
1233 NetClients
[N
].ID
:= N
;
1234 NetClients
[N
].RequestedFullUpdate
:= False;
1235 NetClients
[N
].RCONAuth
:= False;
1236 NetClients
[N
].Voted
:= False;
1237 NetClients
[N
].Player
:= 0;
1238 clearNetClientTransfers(NetClients
[N
]); // just in case
1244 function g_Net_Init(): Boolean;
1252 NetBuf
[NET_UNRELIABLE
].Clear();
1253 NetBuf
[NET_RELIABLE
].Clear();
1254 //SetLength(NetClients, 0);
1255 clearNetClients(true); // clear array
1261 NetAddr
.port
:= 25666;
1262 SetLength(NetBannedHosts
, 0);
1263 if FileExists(DataDir
+ BANLIST_FILENAME
) then
1265 Assign(F
, DataDir
+ BANLIST_FILENAME
);
1270 if StrToIp(IPstr
, IP
) then
1274 g_Net_SaveBanList();
1277 Result
:= (enet_initialize() = 0);
1280 procedure g_Net_Flush();
1284 F
, Chan
: enet_uint32
;
1288 Chan
:= NET_CHAN_GAME
;
1290 if NetMode
= NET_SERVER
then
1291 for T
:= NET_UNRELIABLE
to NET_RELIABLE
do
1293 if NetBuf
[T
].CurSize
> 0 then
1295 P
:= enet_packet_create(NetBuf
[T
].Data
, NetBuf
[T
].CurSize
, F
);
1296 if not Assigned(P
) then continue
;
1297 enet_host_broadcast(NetHost
, Chan
, P
);
1301 for I
:= Low(NetClients
) to High(NetClients
) do
1303 if not NetClients
[I
].Used
then continue
;
1304 if NetClients
[I
].NetOut
[T
].CurSize
<= 0 then continue
;
1305 P
:= enet_packet_create(NetClients
[I
].NetOut
[T
].Data
, NetClients
[I
].NetOut
[T
].CurSize
, F
);
1306 if not Assigned(P
) then continue
;
1307 enet_peer_send(NetClients
[I
].Peer
, Chan
, P
);
1308 NetClients
[I
].NetOut
[T
].Clear();
1311 // next and last iteration is always RELIABLE
1312 F
:= LongWord(ENET_PACKET_FLAG_RELIABLE
);
1313 Chan
:= NET_CHAN_IMPORTANT
;
1315 else if NetMode
= NET_CLIENT
then
1316 for T
:= NET_UNRELIABLE
to NET_RELIABLE
do
1318 if NetBuf
[T
].CurSize
> 0 then
1320 P
:= enet_packet_create(NetBuf
[T
].Data
, NetBuf
[T
].CurSize
, F
);
1321 if not Assigned(P
) then continue
;
1322 enet_peer_send(NetPeer
, Chan
, P
);
1325 // next and last iteration is always RELIABLE
1326 F
:= LongWord(ENET_PACKET_FLAG_RELIABLE
);
1327 Chan
:= NET_CHAN_IMPORTANT
;
1331 procedure g_Net_Cleanup();
1335 NetBuf
[NET_UNRELIABLE
].Clear();
1336 NetBuf
[NET_RELIABLE
].Clear();
1338 //SetLength(NetClients, 0);
1339 clearNetClients(true); // clear array
1340 NetClientCount
:= 0;
1349 NetState
:= NET_STATE_NONE
;
1351 NetPongSock
:= ENET_SOCKET_NULL
;
1353 NetTimeToMaster
:= 0;
1354 NetTimeToUpdate
:= 0;
1355 NetTimeToReliable
:= 0;
1357 NetMode
:= NET_NONE
;
1359 if NetPortThread
<> NilThreadId
then
1360 WaitForThreadTerminate(NetPortThread
, 66666);
1362 NetPortThread
:= NilThreadId
;
1363 g_Net_UnforwardPorts();
1369 procedure g_Net_Free();
1373 enet_deinitialize();
1374 NetInitDone
:= False;
1378 //**************************************************************************
1382 //**************************************************************************
1384 function ForwardThread(Param
: Pointer): PtrInt
;
1387 if not g_Net_ForwardPorts() then Result
:= -1;
1390 function g_Net_Host(IPAddr
: LongWord; Port
: enet_uint16
; MaxClients
: Cardinal = 16): Boolean;
1392 if NetMode
<> NET_NONE
then
1394 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_INGAME
]);
1401 g_Console_Add(_lc
[I_NET_MSG
] + Format(_lc
[I_NET_MSG_HOST
], [Port
]));
1402 if not NetInitDone
then
1404 if (not g_Net_Init()) then
1406 g_Console_Add(_lc
[I_NET_MSG_FERROR
] + _lc
[I_NET_ERR_ENET
]);
1411 NetInitDone
:= True;
1414 NetAddr
.host
:= IPAddr
;
1415 NetAddr
.port
:= Port
;
1417 if NetForwardPorts
then NetPortThread
:= BeginThread(ForwardThread
);
1419 NetHost
:= enet_host_create(@NetAddr
, NET_MAXCLIENTS
, NET_CHANS
, 0, 0);
1421 if (NetHost
= nil) then
1423 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + Format(_lc
[I_NET_ERR_HOST
], [Port
]));
1429 NetPongSock
:= enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM
);
1430 if NetPongSock
<> ENET_SOCKET_NULL
then
1432 NetPongAddr
.host
:= IPAddr
;
1433 NetPongAddr
.port
:= NET_PING_PORT
;
1434 if enet_socket_bind(NetPongSock
, @NetPongAddr
) < 0 then
1436 enet_socket_destroy(NetPongSock
);
1437 NetPongSock
:= ENET_SOCKET_NULL
;
1440 enet_socket_set_option(NetPongSock
, ENET_SOCKOPT_NONBLOCK
, 1);
1443 NetMode
:= NET_SERVER
;
1445 NetBuf
[NET_UNRELIABLE
].Clear();
1446 NetBuf
[NET_RELIABLE
].Clear();
1452 procedure g_Net_Host_Die();
1456 if NetMode
<> NET_SERVER
then Exit
;
1458 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_HOST_DISCALL
]);
1459 for I
:= 0 to High(NetClients
) do
1460 if NetClients
[I
].Used
then
1461 enet_peer_disconnect(NetClients
[I
].Peer
, NET_DISC_DOWN
);
1463 while enet_host_service(NetHost
, @NetEvent
, 1000) > 0 do
1464 if NetEvent
.kind
= ENET_EVENT_TYPE_RECEIVE
then
1465 enet_packet_destroy(NetEvent
.packet
);
1467 for I
:= 0 to High(NetClients
) do
1468 if NetClients
[I
].Used
then
1470 FreeMemory(NetClients
[I
].Peer
^.data
);
1471 NetClients
[I
].Peer
^.data
:= nil;
1472 enet_peer_reset(NetClients
[I
].Peer
);
1473 NetClients
[I
].Peer
:= nil;
1474 NetClients
[I
].Used
:= False;
1475 NetClients
[I
].NetOut
[NET_UNRELIABLE
].Free();
1476 NetClients
[I
].NetOut
[NET_RELIABLE
].Free();
1479 clearNetClients(false); // don't clear array
1480 if (NetMPeer
<> nil) and (NetMHost
<> nil) then g_Net_Slist_Disconnect
;
1481 if NetPongSock
<> ENET_SOCKET_NULL
then
1482 enet_socket_destroy(NetPongSock
);
1484 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_HOST_DIE
]);
1485 enet_host_destroy(NetHost
);
1487 NetMode
:= NET_NONE
;
1490 e_WriteLog('NET: Server stopped', TMsgType
.Notify
);
1494 procedure g_Net_Host_Send(ID
: Integer; Reliable
: Boolean; Chan
: Byte = NET_CHAN_GAME
);
1501 T
:= NET_UNRELIABLE
;
1505 if ID
> High(NetClients
) then Exit
;
1506 if NetClients
[ID
].Peer
= nil then Exit
;
1508 NetClients
[ID
].NetOut
[T
].Write(Integer(NetOut
.CurSize
));
1509 NetClients
[ID
].NetOut
[T
].Write(NetOut
);
1514 NetBuf
[T
].Write(Integer(NetOut
.CurSize
));
1515 NetBuf
[T
].Write(NetOut
);
1518 if NetDump
then g_Net_DumpSendBuffer();
1522 procedure g_Net_Host_CheckPings();
1524 ClAddr
: ENetAddress
;
1528 Ping
: array [0..9] of Byte;
1531 if NetPongSock
= ENET_SOCKET_NULL
then Exit
;
1533 Buf
.data
:= Addr(Ping
[0]);
1534 Buf
.dataLength
:= 2+8;
1538 Len
:= enet_socket_receive(NetPongSock
, @ClAddr
, @Buf
, 1);
1539 if Len
< 0 then Exit
;
1541 if (Ping
[0] = Ord('D')) and (Ping
[1] = Ord('F')) then
1543 ClTime
:= Int64(Addr(Ping
[2])^);
1546 NetOut
.Write(Byte(Ord('D')));
1547 NetOut
.Write(Byte(Ord('F')));
1548 NetOut
.Write(NetPort
);
1549 NetOut
.Write(ClTime
);
1550 g_Net_Slist_WriteInfo();
1552 if gPlayer1
<> nil then Inc(NPl
);
1553 if gPlayer2
<> nil then Inc(NPl
);
1555 NetOut
.Write(gNumBots
);
1557 Buf
.data
:= NetOut
.Data
;
1558 Buf
.dataLength
:= NetOut
.CurSize
;
1559 enet_socket_send(NetPongSock
, @ClAddr
, @Buf
, 1);
1566 function g_Net_Host_Update(): enet_size_t
;
1577 if NetUseMaster
then g_Net_Slist_Check
;
1578 g_Net_Host_CheckPings
;
1580 while (enet_host_service(NetHost
, @NetEvent
, 0) > 0) do
1582 case (NetEvent
.kind
) of
1583 ENET_EVENT_TYPE_CONNECT
:
1585 IP
:= IpToStr(NetEvent
.Peer
^.address
.host
);
1586 Port
:= NetEvent
.Peer
^.address
.port
;
1587 g_Console_Add(_lc
[I_NET_MSG
] +
1588 Format(_lc
[I_NET_MSG_HOST_CONN
], [IP
, Port
]));
1590 if (NetEvent
.data
<> NET_PROTOCOL_VER
) then
1592 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_HOST_REJECT
] +
1593 _lc
[I_NET_DISC_PROTOCOL
]);
1594 NetEvent
.peer
^.data
:= GetMemory(SizeOf(Byte));
1595 Byte(NetEvent
.peer
^.data
^) := 255;
1596 enet_peer_disconnect(NetEvent
.peer
, NET_DISC_PROTOCOL
);
1597 enet_host_flush(NetHost
);
1601 ID
:= g_Net_FindSlot();
1605 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_HOST_REJECT
] +
1606 _lc
[I_NET_DISC_FULL
]);
1607 NetEvent
.Peer
^.data
:= GetMemory(SizeOf(Byte));
1608 Byte(NetEvent
.peer
^.data
^) := 255;
1609 enet_peer_disconnect(NetEvent
.peer
, NET_DISC_FULL
);
1610 enet_host_flush(NetHost
);
1614 NetClients
[ID
].Peer
:= NetEvent
.peer
;
1615 NetClients
[ID
].Peer
^.data
:= GetMemory(SizeOf(Byte));
1616 Byte(NetClients
[ID
].Peer
^.data
^) := ID
;
1617 NetClients
[ID
].State
:= NET_STATE_AUTH
;
1618 NetClients
[ID
].RCONAuth
:= False;
1619 NetClients
[ID
].NetOut
[NET_UNRELIABLE
].Alloc(NET_BUFSIZE
*2);
1620 NetClients
[ID
].NetOut
[NET_RELIABLE
].Alloc(NET_BUFSIZE
*2);
1621 clearNetClientTransfers(NetClients
[ID
]); // just in case
1623 enet_peer_timeout(NetEvent
.peer
, ENET_PEER_TIMEOUT_LIMIT
* 2, ENET_PEER_TIMEOUT_MINIMUM
* 2, ENET_PEER_TIMEOUT_MAXIMUM
* 2);
1625 Inc(NetClientCount
);
1626 g_Console_Add(_lc
[I_NET_MSG
] + Format(_lc
[I_NET_MSG_HOST_ADD
], [ID
]));
1629 ENET_EVENT_TYPE_RECEIVE
:
1631 //e_LogWritefln('RECEIVE: chan=%u', [NetEvent.channelID]);
1632 if (NetEvent
.channelID
= NET_CHAN_DOWNLOAD_EX
) then
1634 ProcessDownloadExPacket();
1638 ID
:= Byte(NetEvent
.peer
^.data
^);
1639 if ID
> High(NetClients
) then Exit
;
1640 TC
:= @NetClients
[ID
];
1642 if NetDump
then g_Net_DumpRecvBuffer(NetEvent
.packet
^.data
, NetEvent
.packet
^.dataLength
);
1643 g_Net_Host_HandlePacket(TC
, NetEvent
.packet
, g_Net_HostMsgHandler
);
1647 ENET_EVENT_TYPE_DISCONNECT
:
1649 ID
:= Byte(NetEvent
.peer
^.data
^);
1650 if ID
> High(NetClients
) then Exit
;
1651 clearNetClient(NetClients
[ID
]);
1652 TC
:= @NetClients
[ID
];
1653 if TC
= nil then Exit
;
1655 if not (TC
^.Used
) then Exit
;
1657 TP
:= g_Player_Get(TC
^.Player
);
1662 TP
.Kill(K_SIMPLEKILL
, 0, HIT_DISCON
);
1663 g_Console_Add(Format(_lc
[I_PLAYER_LEAVE
], [TP
.Name
]), True);
1664 e_WriteLog('NET: Client ' + TP
.Name
+ ' [' + IntToStr(ID
) + '] disconnected.', TMsgType
.Notify
);
1665 g_Player_Remove(TP
.UID
);
1669 TC
^.State
:= NET_STATE_NONE
;
1672 TC
^.RequestedFullUpdate
:= False;
1673 TC
^.NetOut
[NET_UNRELIABLE
].Free();
1674 TC
^.NetOut
[NET_RELIABLE
].Free();
1676 FreeMemory(NetEvent
.peer
^.data
);
1677 NetEvent
.peer
^.data
:= nil;
1678 g_Console_Add(_lc
[I_NET_MSG
] + Format(_lc
[I_NET_MSG_HOST_DISC
], [ID
]));
1679 Dec(NetClientCount
);
1681 if NetUseMaster
then g_Net_Slist_Update
;
1688 //**************************************************************************
1692 //**************************************************************************
1694 procedure g_Net_Disconnect(Forced
: Boolean = False);
1696 if NetMode
<> NET_CLIENT
then Exit
;
1697 if (NetHost
= nil) or (NetPeer
= nil) then Exit
;
1701 enet_peer_disconnect(NetPeer
, NET_DISC_NONE
);
1703 while (enet_host_service(NetHost
, @NetEvent
, 1500) > 0) do
1705 if (NetEvent
.kind
= ENET_EVENT_TYPE_DISCONNECT
) then
1711 if (NetEvent
.kind
= ENET_EVENT_TYPE_RECEIVE
) then
1712 enet_packet_destroy(NetEvent
.packet
);
1715 if NetPeer
<> nil then
1717 enet_peer_reset(NetPeer
);
1723 e_WriteLog('NET: Kicked from server: ' + IntToStr(NetEvent
.data
), TMsgType
.Notify
);
1724 if (NetEvent
.data
<= NET_DISC_MAX
) then
1725 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_KICK
] +
1726 _lc
[TStrings_Locale(Cardinal(I_NET_DISC_NONE
) + NetEvent
.data
)], True);
1729 if NetHost
<> nil then
1731 enet_host_destroy(NetHost
);
1734 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_CLIENT_DISC
]);
1737 e_WriteLog('NET: Disconnected', TMsgType
.Notify
);
1740 procedure g_Net_Client_Send(Reliable
: Boolean; Chan
: Byte = NET_CHAN_GAME
);
1747 T
:= NET_UNRELIABLE
;
1750 NetBuf
[T
].Write(Integer(NetOut
.CurSize
));
1751 NetBuf
[T
].Write(NetOut
);
1753 if NetDump
then g_Net_DumpSendBuffer();
1755 g_Net_Flush(); // FIXME: for now, send immediately
1758 function g_Net_Client_Update(): enet_size_t
;
1761 while (enet_host_service(NetHost
, @NetEvent
, 0) > 0) do
1763 case NetEvent
.kind
of
1764 ENET_EVENT_TYPE_RECEIVE
:
1766 if (NetEvent
.channelID
= NET_CHAN_DOWNLOAD_EX
) then continue
; // ignore all download packets, they're processed by separate code
1767 if NetDump
then g_Net_DumpRecvBuffer(NetEvent
.packet
^.data
, NetEvent
.packet
^.dataLength
);
1768 g_Net_Client_HandlePacket(NetEvent
.packet
, g_Net_ClientMsgHandler
);
1771 ENET_EVENT_TYPE_DISCONNECT
:
1773 g_Net_Disconnect(True);
1781 function g_Net_Client_UpdateWhileLoading(): enet_size_t
;
1784 while (enet_host_service(NetHost
, @NetEvent
, 0) > 0) do
1786 case NetEvent
.kind
of
1787 ENET_EVENT_TYPE_RECEIVE
:
1789 if (NetEvent
.channelID
= NET_CHAN_DOWNLOAD_EX
) then continue
; // ignore all download packets, they're processed by separate code
1790 if NetDump
then g_Net_DumpRecvBuffer(NetEvent
.packet
^.data
, NetEvent
.packet
^.dataLength
);
1791 g_Net_Client_HandlePacket(NetEvent
.packet
, g_Net_ClientLightMsgHandler
);
1794 ENET_EVENT_TYPE_DISCONNECT
:
1796 g_Net_Disconnect(True);
1805 function g_Net_Connect(IP
: string; Port
: enet_uint16
): Boolean;
1808 TimeoutTime
, T
: Int64;
1810 if NetMode
<> NET_NONE
then
1812 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_ERR_INGAME
], True);
1819 g_Console_Add(_lc
[I_NET_MSG
] + Format(_lc
[I_NET_MSG_CLIENT_CONN
],
1821 if not NetInitDone
then
1823 if (not g_Net_Init()) then
1825 g_Console_Add(_lc
[I_NET_MSG_FERROR
] + _lc
[I_NET_ERR_ENET
], True);
1830 NetInitDone
:= True;
1833 NetHost
:= enet_host_create(nil, 1, NET_CHANS
, 0, 0);
1835 if (NetHost
= nil) then
1837 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CLIENT
], True);
1843 enet_address_set_host(@NetAddr
, PChar(Addr(IP
[1])));
1844 NetAddr
.port
:= Port
;
1846 NetPeer
:= enet_host_connect(NetHost
, @NetAddr
, NET_CHANS
, NET_PROTOCOL_VER
);
1848 if (NetPeer
= nil) then
1850 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CLIENT
], True);
1851 enet_host_destroy(NetHost
);
1857 // предупредить что ждем слишком долго через N секунд
1858 TimeoutTime
:= GetTimer() + NET_CONNECT_TIMEOUT
;
1863 while (enet_host_service(NetHost
, @NetEvent
, 0) > 0) do
1865 if (NetEvent
.kind
= ENET_EVENT_TYPE_CONNECT
) then
1867 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_CLIENT_DONE
]);
1868 NetMode
:= NET_CLIENT
;
1870 enet_peer_timeout(NetPeer
, ENET_PEER_TIMEOUT_LIMIT
* 2, ENET_PEER_TIMEOUT_MINIMUM
* 2, ENET_PEER_TIMEOUT_MAXIMUM
* 2);
1872 NetClientPort
:= Port
;
1880 if T
> TimeoutTime
then
1882 TimeoutTime
:= T
+ NET_CONNECT_TIMEOUT
* 100; // одного предупреждения хватит
1883 g_Console_Add(_lc
[I_NET_MSG_TIMEOUT_WARN
], True);
1884 g_Console_Add(Format(_lc
[I_NET_MSG_PORTS
], [Integer(Port
), Integer(NET_PING_PORT
)]), True);
1887 ProcessLoading(true);
1889 if e_KeyPressed(IK_SPACE
) or e_KeyPressed(IK_ESCAPE
) or e_KeyPressed(VK_ESCAPE
) or
1890 e_KeyPressed(JOY0_JUMP
) or e_KeyPressed(JOY1_JUMP
) or e_KeyPressed(JOY2_JUMP
) or e_KeyPressed(JOY3_JUMP
) then
1894 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_TIMEOUT
], True);
1895 g_Console_Add(Format(_lc
[I_NET_MSG_PORTS
], [Integer(Port
), Integer(NET_PING_PORT
)]), True);
1896 if NetPeer
<> nil then enet_peer_reset(NetPeer
);
1897 if NetHost
<> nil then
1899 enet_host_destroy(NetHost
);
1906 function IpToStr(IP
: LongWord): string;
1911 Result
:= IntToStr(PByte(Ptr
+ 0)^) + '.';
1912 Result
:= Result
+ IntToStr(PByte(Ptr
+ 1)^) + '.';
1913 Result
:= Result
+ IntToStr(PByte(Ptr
+ 2)^) + '.';
1914 Result
:= Result
+ IntToStr(PByte(Ptr
+ 3)^);
1917 function StrToIp(IPstr
: string; var IP
: LongWord): Boolean;
1921 Result
:= enet_address_set_host(@EAddr
, PChar(@IPstr
[1])) = 0;
1925 function g_Net_Client_ByName(Name
: string): pTNetClient
;
1931 for a
:= Low(NetClients
) to High(NetClients
) do
1932 if (NetClients
[a
].Used
) and (NetClients
[a
].State
= NET_STATE_GAME
) then
1934 pl
:= g_Player_Get(NetClients
[a
].Player
);
1935 if pl
= nil then continue
;
1936 if Copy(LowerCase(pl
.Name
), 1, Length(Name
)) <> LowerCase(Name
) then continue
;
1937 if NetClients
[a
].Peer
<> nil then
1939 Result
:= @NetClients
[a
];
1945 function g_Net_Client_ByPlayer(PID
: Word): pTNetClient
;
1950 for a
:= Low(NetClients
) to High(NetClients
) do
1951 if (NetClients
[a
].Used
) and (NetClients
[a
].State
= NET_STATE_GAME
) then
1952 if NetClients
[a
].Player
= PID
then
1954 Result
:= @NetClients
[a
];
1959 function g_Net_ClientName_ByID(ID
: Integer): string;
1965 if ID
= NET_EVERYONE
then
1967 for a
:= Low(NetClients
) to High(NetClients
) do
1968 if (NetClients
[a
].ID
= ID
) and (NetClients
[a
].Used
) and (NetClients
[a
].State
= NET_STATE_GAME
) then
1970 pl
:= g_Player_Get(NetClients
[a
].Player
);
1971 if pl
= nil then Exit
;
1976 procedure g_Net_SendData(Data
: AByte
; peer
: pENetPeer
; Reliable
: Boolean; Chan
: Byte = NET_CHAN_DOWNLOAD
);
1980 dataLength
: Cardinal;
1982 dataLength
:= Length(Data
);
1985 F
:= LongWord(ENET_PACKET_FLAG_RELIABLE
)
1989 if (peer
<> nil) then
1991 P
:= enet_packet_create(@Data
[0], dataLength
, F
);
1992 if not Assigned(P
) then Exit
;
1993 enet_peer_send(peer
, Chan
, P
);
1997 P
:= enet_packet_create(@Data
[0], dataLength
, F
);
1998 if not Assigned(P
) then Exit
;
1999 enet_host_broadcast(NetHost
, Chan
, P
);
2002 enet_host_flush(NetHost
);
2005 function g_Net_IsHostBanned(IP
: LongWord; Perm
: Boolean = False): Boolean;
2010 if NetBannedHosts
= nil then
2012 for I
:= 0 to High(NetBannedHosts
) do
2013 if (NetBannedHosts
[I
].IP
= IP
) and ((not Perm
) or (NetBannedHosts
[I
].Perm
)) then
2020 procedure g_Net_BanHost(IP
: LongWord; Perm
: Boolean = True); overload
;
2026 if g_Net_IsHostBanned(IP
, Perm
) then
2030 for I
:= Low(NetBannedHosts
) to High(NetBannedHosts
) do
2031 if NetBannedHosts
[I
].IP
= 0 then
2039 SetLength(NetBannedHosts
, Length(NetBannedHosts
) + 1);
2040 P
:= High(NetBannedHosts
);
2043 NetBannedHosts
[P
].IP
:= IP
;
2044 NetBannedHosts
[P
].Perm
:= Perm
;
2047 procedure g_Net_BanHost(IP
: string; Perm
: Boolean = True); overload
;
2052 b
:= StrToIp(IP
, a
);
2054 g_Net_BanHost(a
, Perm
);
2057 procedure g_Net_UnbanNonPermHosts();
2061 if NetBannedHosts
= nil then
2063 for I
:= Low(NetBannedHosts
) to High(NetBannedHosts
) do
2064 if (NetBannedHosts
[I
].IP
> 0) and not NetBannedHosts
[I
].Perm
then
2066 NetBannedHosts
[I
].IP
:= 0;
2067 NetBannedHosts
[I
].Perm
:= True;
2071 function g_Net_UnbanHost(IP
: string): Boolean; overload
;
2075 Result
:= StrToIp(IP
, a
);
2077 Result
:= g_Net_UnbanHost(a
);
2080 function g_Net_UnbanHost(IP
: LongWord): Boolean; overload
;
2087 if NetBannedHosts
= nil then
2089 for I
:= 0 to High(NetBannedHosts
) do
2090 if NetBannedHosts
[I
].IP
= IP
then
2092 NetBannedHosts
[I
].IP
:= 0;
2093 NetBannedHosts
[I
].Perm
:= True;
2095 // no break here to clear all bans of this host, perm and non-perm
2099 procedure g_Net_SaveBanList();
2104 Assign(F
, DataDir
+ BANLIST_FILENAME
);
2106 if NetBannedHosts
<> nil then
2107 for I
:= 0 to High(NetBannedHosts
) do
2108 if NetBannedHosts
[I
].Perm
and (NetBannedHosts
[I
].IP
> 0) then
2109 Writeln(F
, IpToStr(NetBannedHosts
[I
].IP
));
2113 procedure g_Net_DumpStart();
2115 if NetMode
= NET_SERVER
then
2116 NetDumpFile
:= createDiskFile(NETDUMP_FILENAME
+ '_server')
2118 NetDumpFile
:= createDiskFile(NETDUMP_FILENAME
+ '_client');
2121 procedure g_Net_DumpSendBuffer();
2123 writeInt(NetDumpFile
, gTime
);
2124 writeInt(NetDumpFile
, LongWord(NetOut
.CurSize
));
2125 writeInt(NetDumpFile
, Byte(1));
2126 NetDumpFile
.WriteBuffer(NetOut
.Data
^, NetOut
.CurSize
);
2129 procedure g_Net_DumpRecvBuffer(Buf
: penet_uint8
; Len
: LongWord);
2131 if (Buf
= nil) or (Len
= 0) then Exit
;
2132 writeInt(NetDumpFile
, gTime
);
2133 writeInt(NetDumpFile
, Len
);
2134 writeInt(NetDumpFile
, Byte(0));
2135 NetDumpFile
.WriteBuffer(Buf
^, Len
);
2138 procedure g_Net_DumpEnd();
2144 function g_Net_ForwardPorts(ForwardPongPort
: Boolean = True): Boolean;
2145 {$IFDEF USE_MINIUPNPC}
2150 LanAddr
: array [0..255] of Char;
2151 StrPort
: AnsiString;
2156 if NetPortForwarded
= NetPort
then
2162 NetPongForwarded
:= False;
2163 NetPortForwarded
:= 0;
2165 DevList
:= upnpDiscover(1000, nil, nil, 0, 0, 2, Addr(Err
));
2166 if DevList
= nil then
2168 conwritefln('port forwarding failed: upnpDiscover() failed: %d', [Err
]);
2172 I
:= UPNP_GetValidIGD(DevList
, @Urls
, @Data
, Addr(LanAddr
[0]), 256);
2176 conwriteln('port forwarding failed: could not find an IGD device on this LAN');
2177 FreeUPNPDevList(DevList
);
2178 FreeUPNPUrls(@Urls
);
2182 StrPort
:= IntToStr(NetPort
);
2183 I
:= UPNP_AddPortMapping(
2184 Urls
.controlURL
, Addr(data
.first
.servicetype
[1]),
2185 PChar(StrPort
), PChar(StrPort
), Addr(LanAddr
[0]), PChar('D2DF'),
2186 PChar('UDP'), nil, PChar('0')
2191 conwritefln('forwarding port %d failed: error %d', [NetPort
, I
]);
2192 FreeUPNPDevList(DevList
);
2193 FreeUPNPUrls(@Urls
);
2197 if ForwardPongPort
then
2199 StrPort
:= IntToStr(NET_PING_PORT
);
2200 I
:= UPNP_AddPortMapping(
2201 Urls
.controlURL
, Addr(data
.first
.servicetype
[1]),
2202 PChar(StrPort
), PChar(StrPort
), Addr(LanAddr
[0]), PChar('D2DF'),
2203 PChar('UDP'), nil, PChar('0')
2208 conwritefln('forwarding port %d failed: error %d', [NetPort
+ 1, I
]);
2209 NetPongForwarded
:= False;
2213 conwritefln('forwarded port %d successfully', [NetPort
+ 1]);
2214 NetPongForwarded
:= True;
2218 conwritefln('forwarded port %d successfully', [NetPort
]);
2219 NetIGDControl
:= AnsiString(Urls
.controlURL
);
2220 NetIGDService
:= data
.first
.servicetype
;
2221 NetPortForwarded
:= NetPort
;
2223 FreeUPNPDevList(DevList
);
2224 FreeUPNPUrls(@Urls
);
2233 procedure g_Net_UnforwardPorts();
2234 {$IFDEF USE_MINIUPNPC}
2237 StrPort
: AnsiString;
2239 if NetPortForwarded
= 0 then Exit
;
2241 conwriteln('unforwarding ports...');
2243 StrPort
:= IntToStr(NetPortForwarded
);
2244 I
:= UPNP_DeletePortMapping(
2245 PChar(NetIGDControl
), Addr(NetIGDService
[1]),
2246 PChar(StrPort
), PChar('UDP'), nil
2248 conwritefln(' port %d: %d', [NetPortForwarded
, I
]);
2250 if NetPongForwarded
then
2252 NetPongForwarded
:= False;
2253 StrPort
:= IntToStr(NetPortForwarded
+ 1);
2254 I
:= UPNP_DeletePortMapping(
2255 PChar(NetIGDControl
), Addr(NetIGDService
[1]),
2256 PChar(StrPort
), PChar('UDP'), nil
2258 conwritefln(' port %d: %d', [NetPortForwarded
+ 1, I
]);
2261 NetPortForwarded
:= 0;
2270 conRegVar('cl_downloadtimeout', @g_Net_DownloadTimeout
, 0.0, 1000000.0, '', 'timeout in seconds, 0 to disable it');
2271 SetLength(NetClients
, 0);
2272 g_Net_DownloadTimeout
:= 60;
2273 NetIn
.Alloc(NET_BUFSIZE
);
2274 NetOut
.Alloc(NET_BUFSIZE
);
2275 NetBuf
[NET_UNRELIABLE
].Alloc(NET_BUFSIZE
*2);
2276 NetBuf
[NET_RELIABLE
].Alloc(NET_BUFSIZE
*2);
2277 trans_omsg
.Alloc(NET_BUFSIZE
);
2281 NetBuf
[NET_UNRELIABLE
].Free();
2282 NetBuf
[NET_RELIABLE
].Free();