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 {$IFDEF USE_MINIUPNPC}MiniUPnPc
,{$ENDIF}
22 e_log
, e_msg
, utils
, ENet
, Classes
, md5
, MAPDEF
;
25 NET_PROTOCOL_VER
= 188;
28 // NOTE: We use different channels for unreliable and reliable packets because ENet seems to
29 // discard preceeding RELIABLE packets if a later UNRELIABLE (but not UNSEQUENCED) packet sent
30 // on the same channel has arrived earlier, which is useful for occasional full-state updates.
31 // However, we use a separate download channel to avoid being delayed by other reliable packets.
32 NET_CHAN_UNRELIABLE
= 2;
33 NET_CHAN_RELIABLE
= 1;
34 NET_CHAN_DOWNLOAD
= 11;
35 NET_CHANNELS
= 12; // TODO: Reduce to 3 and re-enumerate channels. Requires protocol increment.
42 NET_PING_PORT
= $DF2D;
49 NET_DISC_NONE
: enet_uint32
= 0;
50 NET_DISC_PROTOCOL
: enet_uint32
= 1;
51 NET_DISC_VERSION
: enet_uint32
= 2;
52 NET_DISC_FULL
: enet_uint32
= 3;
53 NET_DISC_KICK
: enet_uint32
= 4;
54 NET_DISC_DOWN
: enet_uint32
= 5;
55 NET_DISC_PASSWORD
: enet_uint32
= 6;
56 NET_DISC_TEMPBAN
: enet_uint32
= 7;
57 NET_DISC_BAN
: enet_uint32
= 8;
58 NET_DISC_MAX
: enet_uint32
= 8;
59 NET_DISC_FILE_TIMEOUT
: enet_uint32
= 13;
65 NET_CONNECT_TIMEOUT
= 1000 * 10;
67 BANLIST_FILENAME
= 'banlist.txt';
68 NETDUMP_FILENAME
= 'netdump';
71 TNetMapResourceInfo
= record
72 wadName
: AnsiString
; // wad file name, without a path
73 size
: Integer; // wad file size (-1: size and hash are not known)
74 hash
: TMD5Digest
; // wad hash
77 TNetMapResourceInfoArray
= array of TNetMapResourceInfo
;
79 TNetFileTransfer
= record
83 size
: Integer; // file size in bytes
85 lastSentChunk
: Integer;
86 lastAckChunk
: Integer;
87 lastAckTime
: Int64; // msecs; if not "in progress", we're waiting for the first ack
89 diskBuffer
: PChar
; // of `chunkSize` bytes
99 RequestedFullUpdate
: Boolean;
100 WaitForFirstSpawn
: Boolean; // set to True on server, used to spawn a player on first full state request
101 FullUpdateSent
: Boolean;
107 Transfer
: TNetFileTransfer
; // only one transfer may be active
108 NetOut
: array [0..1] of TMsg
;
114 pTNetClient
= ^TNetClient
;
116 AByte
= array of Byte;
119 NetInitDone
: Boolean;
120 NetMode
: Byte = NET_NONE
;
123 NetServerName
: String = 'Unnamed Server';
125 NetPort
: Word = 25666;
127 NetAllowRCON
: Boolean;
128 NetRCONPassword
: String;
130 NetTimeToUpdate
: Cardinal;
131 NetTimeToReliable
: Cardinal;
132 NetTimeToMaster
: Cardinal;
137 NetAddr
: ENetAddress
;
139 NetPongAddr
: ENetAddress
;
140 NetPongSock
: ENetSocket
= ENET_SOCKET_NULL
;
142 NetUseMaster
: Boolean = True;
143 NetMasterList
: string = 'mpms.doom2d.org:25665, deadsoftware.ru:25665, terminalcorner.ru: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;
153 NetMaxClients
: Byte = 255;
154 NetBannedHosts
: array of TBanRecord
;
156 NetAutoBanLimit
: Integer = 5;
157 NetAutoBanPerm
: Boolean = True;
158 NetAutoBanWarn
: Boolean;
159 NetAutoBanForTimeout
: Boolean;
161 NetAuthTimeout
: Integer = 30 * 1000;
162 NetPacketTimeout
: Integer = 60 * 1000;
164 NetState
: Integer = NET_STATE_NONE
;
166 NetMyID
: Integer = -1;
167 NetPlrUID1
: Integer = -1;
168 NetPlrUID2
: Integer = -1;
170 NetInterpLevel
: Integer = 1;
171 NetUpdateRate
: Cardinal; // 0 - as soon as possible
172 NetRelupdRate
: Cardinal = 18; // around two times a second
173 NetMasterRate
: Cardinal = 60000;
175 NetForcePlayerUpdate
: Boolean;
176 NetPredictSelf
: Boolean = True;
177 NetForwardPorts
: Boolean {$IFDEF USE_MINIUPNPC} = True {$ENDIF};
179 NetGotEverything
: Boolean;
182 NetDeafLevel
: Integer;
184 {$IFDEF USE_MINIUPNPC}
185 NetForwardedPort
: Word;
186 NetPingPortForwarded
: Boolean;
187 NetIGDControl
: AnsiString
;
188 NetIGDService
: array[0..MINIUPNPC_URL_MAXSIZE
-1] of AnsiChar
;
191 NetPortThread
: TThreadID
= NilThreadId
;
193 NetDumpFile
: TStream
;
195 g_Res_received_map_start
: Integer; // set if we received "map change" event
198 function g_Net_Init(): Boolean;
199 procedure g_Net_Cleanup();
200 procedure g_Net_Free();
201 procedure g_Net_Flush();
203 function g_Net_Host(IPAddr
: LongWord
; Port
: enet_uint16
; MaxClients
: Cardinal = 16): Boolean;
204 procedure g_Net_Host_Die();
205 procedure g_Net_Host_Send(ID
: Integer; Reliable
: Boolean);
206 procedure g_Net_Host_Update();
207 procedure g_Net_Host_Kick(ID
: Integer; Reason
: enet_uint32
);
208 procedure g_Net_Host_Ban(ID
: Integer; Perm
: Boolean);
209 procedure g_Net_Host_Ban(C
: pTNetClient
; Perm
: Boolean);
211 function g_Net_Connect(IP
: string; Port
: enet_uint16
): Boolean;
212 procedure g_Net_Disconnect(Forced
: Boolean = False);
213 procedure g_Net_Client_Send(Reliable
: Boolean);
214 procedure g_Net_Client_Update();
216 function g_Net_Client_ByName(Name
: string): pTNetClient
;
217 function g_Net_Client_ByPlayer(PID
: Word): pTNetClient
;
218 function g_Net_ClientName_ByID(ID
: Integer): string;
220 function IpToStr(IP
: LongWord
): string;
221 function StrToIp(IPstr
: string; var IP
: LongWord
): Boolean;
223 function g_Net_IsAddressBanned(IP
: LongWord
; Perm
: Boolean = False): Boolean;
224 procedure g_Net_BanAddress(IP
: LongWord
; Perm
: Boolean = True); overload
;
225 procedure g_Net_BanAddress(IP
: string; Perm
: Boolean = True); overload
;
226 function g_Net_UnbanAddress(IP
: string): Boolean; overload
;
227 function g_Net_UnbanAddress(IP
: LongWord
): Boolean; overload
;
228 procedure g_Net_UnbanNonPerm();
229 procedure g_Net_SaveBanList();
231 procedure g_Net_Penalize(C
: pTNetClient
; Reason
: string);
233 procedure g_Net_DumpStart();
234 procedure g_Net_DumpSendBuffer();
235 procedure g_Net_DumpRecvBuffer(Buf
: penet_uint8
; Len
: LongWord
);
236 procedure g_Net_DumpEnd();
238 function g_Net_ForwardPorts(ForwardPongPort
: Boolean = True): Boolean;
239 procedure g_Net_UnforwardPorts();
241 function g_Net_UserRequestExit
: Boolean;
243 function g_Net_Wait_MapInfo (var tf
: TNetFileTransfer
; var resList
: TNetMapResourceInfoArray
): Integer;
244 function g_Net_RequestResFileInfo (resIndex
: LongInt; out tf
: TNetFileTransfer
): Integer;
245 function g_Net_AbortResTransfer (var tf
: TNetFileTransfer
): Boolean;
246 function g_Net_ReceiveResourceFile (resIndex
: LongInt; var tf
: TNetFileTransfer
; strm
: TStream
): Integer;
248 function g_Net_IsNetworkAvailable (): Boolean;
249 procedure g_Net_InitLowLevel ();
250 procedure g_Net_DeinitLowLevel ();
252 procedure NetServerCVars(P
: SSArray
);
257 // *enet_host_service()*
258 // fuck! https://www.mail-archive.com/enet-discuss@cubik.org/msg00852.html
259 // tl;dr: on shitdows, we can get -1 sometimes, and it is *NOT* a failure.
260 // thank you, enet. let's ignore failures altogether then.
265 g_nethandler
, g_netmsg
, g_netmaster
, g_player
, g_window
, g_console
,
266 g_main
, g_game
, g_language
, g_weapons
, ctypes
, g_system
, g_map
;
269 FILE_CHUNK_SIZE
= 8192;
272 enet_init_success
: Boolean;
273 g_Net_DownloadTimeout
: Single;
277 function g_Net_IsNetworkAvailable (): Boolean;
279 result
:= enet_init_success
;
282 procedure g_Net_InitLowLevel ();
285 v
:= enet_linked_version();
286 e_LogWritefln('ENet Version: %s.%s.%s', [ENET_VERSION_GET_MAJOR(v
), ENET_VERSION_GET_MINOR(v
), ENET_VERSION_GET_PATCH(v
)]);
287 if enet_init_success
then raise Exception
.Create('wuta?!');
288 enet_init_success
:= (enet_initialize() = 0);
291 procedure g_Net_DeinitLowLevel ();
293 if enet_init_success
then
296 enet_init_success
:= False;
301 //**************************************************************************
305 //**************************************************************************
307 procedure clearNetClientTransfers (var nc
: TNetClient
);
309 nc
.Transfer
.stream
.Free
;
310 nc
.Transfer
.diskName
:= ''; // just in case
311 if (nc
.Transfer
.diskBuffer
<> nil) then FreeMem(nc
.Transfer
.diskBuffer
);
312 nc
.Transfer
.stream
:= nil;
313 nc
.Transfer
.diskBuffer
:= nil;
317 procedure clearNetClient (var nc
: TNetClient
);
319 clearNetClientTransfers(nc
);
323 procedure clearNetClients ();
327 for f
:= Low(NetClients
) to High(NetClients
) do
328 clearNetClient(NetClients
[f
]);
329 SetLength(NetClients
, 0);
333 function g_Net_UserRequestExit (): Boolean;
335 Result
:= {e_KeyPressed(IK_SPACE) or}
336 e_KeyPressed(IK_ESCAPE
) or
337 e_KeyPressed(VK_ESCAPE
) or
338 e_KeyPressed(JOY0_JUMP
) or
339 e_KeyPressed(JOY1_JUMP
) or
340 e_KeyPressed(JOY2_JUMP
) or
341 e_KeyPressed(JOY3_JUMP
)
344 //**************************************************************************
346 // file transfer declaraions and host packet processor
348 //**************************************************************************
351 // server packet type
352 NTF_SERVER_DONE
= 10; // done with this file
353 NTF_SERVER_FILE_INFO
= 11; // sent after client request
354 NTF_SERVER_CHUNK
= 12; // next chunk; chunk number follows
355 NTF_SERVER_ABORT
= 13; // server abort
356 NTF_SERVER_MAP_INFO
= 14;
358 // client packet type
359 NTF_CLIENT_MAP_REQUEST
= 100; // map file request; also, returns list of additional wads to download
360 NTF_CLIENT_FILE_REQUEST
= 101; // resource file request (by index)
361 NTF_CLIENT_ABORT
= 102; // do not send requested file, or abort current transfer
362 NTF_CLIENT_START
= 103; // start transfer; client may resume download by sending non-zero starting chunk
363 NTF_CLIENT_ACK
= 104; // chunk ack; chunk number follows
366 // disconnect client due to some file transfer error
367 procedure killClientByFT (var nc
: TNetClient
);
369 e_LogWritefln('disconnected client #%d due to file transfer error', [nc
.ID
], TMsgType
.Warning
);
370 g_Net_Host_Kick(nc
.ID
, NET_DISC_FILE_TIMEOUT
);
371 clearNetClientTransfers(nc
);
372 g_Net_Slist_ServerPlayerLeaves();
376 // send file transfer message from server to client
377 function ftransSendServerMsg (var nc
: TNetClient
; var m
: TMsg
): Boolean;
382 if (m
.CurSize
< 1) then exit
;
383 pkt
:= enet_packet_create(m
.Data
, m
.CurSize
, ENET_PACKET_FLAG_RELIABLE
);
384 if not Assigned(pkt
) then begin killClientByFT(nc
); exit
; end;
385 if (enet_peer_send(nc
.Peer
, NET_CHAN_DOWNLOAD
, pkt
) <> 0) then begin killClientByFT(nc
); exit
; end;
390 // send file transfer message from client to server
391 function ftransSendClientMsg (var m
: TMsg
): Boolean;
396 if (m
.CurSize
< 1) then exit
;
397 pkt
:= enet_packet_create(m
.Data
, m
.CurSize
, ENET_PACKET_FLAG_RELIABLE
);
398 if not Assigned(pkt
) then exit
;
399 if (enet_peer_send(NetPeer
, NET_CHAN_DOWNLOAD
, pkt
) <> 0) then exit
;
405 procedure ProcessChunkSend (var nc
: TNetClient
);
407 tf
: ^TNetFileTransfer
;
413 if (tf
.stream
= nil) then exit
;
415 // arbitrary timeout number
416 if (ct
-tf
.lastAckTime
>= 5000) then
421 // check if we need to send something
422 if (not tf
.inProgress
) then exit
; // waiting for the initial ack
423 // ok, we're sending chunks
424 if (tf
.lastAckChunk
<> tf
.lastSentChunk
) then exit
;
425 Inc(tf
.lastSentChunk
);
426 // do it one chunk at a time; client ack will advance our chunk counter
427 chunks
:= (tf
.size
+tf
.chunkSize
-1) div tf
.chunkSize
;
429 if (tf
.lastSentChunk
> chunks
) then
436 if (tf
.lastSentChunk
= chunks
) then
438 // we're done with this file
439 e_LogWritefln('download: client #%d, DONE sending chunks #%d/#%d', [nc
.ID
, tf
.lastSentChunk
, chunks
]);
440 trans_omsg
.Write(Byte(NTF_SERVER_DONE
));
441 clearNetClientTransfers(nc
);
446 trans_omsg
.Write(Byte(NTF_SERVER_CHUNK
));
447 trans_omsg
.Write(LongInt(tf
.lastSentChunk
));
449 rd
:= tf
.size
-(tf
.lastSentChunk
*tf
.chunkSize
);
450 if (rd
> tf
.chunkSize
) then rd
:= tf
.chunkSize
;
451 trans_omsg
.Write(LongInt(rd
));
452 //e_LogWritefln('download: client #%d, sending chunk #%d/#%d (%d bytes)', [nc.ID, tf.lastSentChunk, chunks, rd]);
453 //FIXME: check for errors here
455 tf
.stream
.Seek(tf
.lastSentChunk
*tf
.chunkSize
, soFromBeginning
);
456 tf
.stream
.ReadBuffer(tf
.diskBuffer
^, rd
);
457 trans_omsg
.WriteData(tf
.diskBuffer
, rd
);
464 ftransSendServerMsg(nc
, trans_omsg
);
468 // server file transfer packet processor
469 // received packet is in `NetEvent`
470 procedure ProcessDownloadExPacket ();
477 tf
: ^TNetFileTransfer
;
487 // find client index by peer
488 for f
:= Low(NetClients
) to High(NetClients
) do
490 if (not NetClients
[f
].Used
) then continue
;
491 if (NetClients
[f
].Peer
= NetEvent
.peer
) then
497 //e_LogWritefln('RECEIVE: dlpacket; client=%d (datalen=%u)', [nid, NetEvent.packet^.dataLength]);
499 if (nid
< 0) then exit
; // wtf?!
500 nc
:= @NetClients
[nid
];
502 if (NetEvent
.packet
^.dataLength
= 0) then
508 // don't time out clients during a file transfer
509 if (NetAuthTimeout
> 0) then
510 nc
^.AuthTime
:= gTime
+ NetAuthTimeout
;
511 if (NetPacketTimeout
> 0) then
512 nc
^.MsgTime
:= gTime
+ NetPacketTimeout
;
514 tf
:= @NetClients
[nid
].Transfer
;
515 tf
.lastAckTime
:= GetTimerMS();
517 cmd
:= Byte(NetEvent
.packet
^.data
^);
518 //e_LogWritefln('RECEIVE: nid=%d; cmd=%u', [nid, cmd]);
520 NTF_CLIENT_FILE_REQUEST
: // file request
522 if (tf
.stream
<> nil) then
527 if (NetEvent
.packet
^.dataLength
< 2) then
532 // new transfer request; build packet
533 if not msg
.Init(NetEvent
.packet
^.data
+1, NetEvent
.packet
^.dataLength
-1, True) then
538 // get resource index
539 ridx
:= msg
.ReadLongInt();
540 if (ridx
< -1) or (ridx
>= length(gExternalResources
)) then
542 e_LogWritefln('Invalid resource index %d', [ridx
], TMsgType
.Warning
);
546 if (ridx
< 0) then fname
:= gGameSettings
.WAD
else fname
:= gExternalResources
[ridx
].diskName
;
547 if (length(fname
) = 0) then
549 e_WriteLog('Invalid filename: '+fname
, TMsgType
.Warning
);
553 tf
.diskName
:= findDiskWad(fname
);
554 if (length(tf
.diskName
) = 0) then
556 e_LogWritefln('NETWORK: file "%s" not found!', [fname
], TMsgType
.Fatal
);
561 //tf.hash := MD5File(tf.diskName);
562 if (ridx
< 0) then tf
.hash
:= gWADHash
else tf
.hash
:= gExternalResources
[ridx
].hash
;
563 // create file stream
564 tf
.diskName
:= findDiskWad(fname
);
566 tf
.stream
:= openDiskFileRO(tf
.diskName
);
570 if (tf
.stream
= nil) then
572 e_WriteLog(Format('NETWORK: file "%s" not found!', [fname
]), TMsgType
.Fatal
);
576 e_LogWritefln('client #%d requested resource #%d (file is `%s` : `%s`)', [nc
.ID
, ridx
, fname
, tf
.diskName
]);
577 tf
.size
:= tf
.stream
.size
;
578 tf
.chunkSize
:= FILE_CHUNK_SIZE
; // arbitrary
579 tf
.lastSentChunk
:= -1;
580 tf
.lastAckChunk
:= -1;
581 tf
.lastAckTime
:= GetTimerMS();
582 tf
.inProgress
:= False; // waiting for the first ACK or for the cancel
583 GetMem(tf
.diskBuffer
, tf
.chunkSize
);
584 // sent file info message
586 trans_omsg
.Write(Byte(NTF_SERVER_FILE_INFO
));
587 trans_omsg
.Write(tf
.hash
);
588 trans_omsg
.Write(tf
.size
);
589 trans_omsg
.Write(tf
.chunkSize
);
590 trans_omsg
.Write(ExtractFileName(fname
));
591 if not ftransSendServerMsg(nc
^, trans_omsg
) then exit
;
593 NTF_CLIENT_ABORT
: // do not send requested file, or abort current transfer
595 e_LogWritefln('client #%d aborted file transfer', [nc
.ID
]);
596 clearNetClientTransfers(nc
^);
598 NTF_CLIENT_START
: // start transfer; client may resume download by sending non-zero starting chunk
600 if not Assigned(tf
.stream
) then
605 if (tf
.lastSentChunk
<> -1) or (tf
.lastAckChunk
<> -1) or (tf
.inProgress
) then
607 // double ack, get lost
611 if (NetEvent
.packet
^.dataLength
< 2) then
617 if not msg
.Init(NetEvent
.packet
^.data
+1, NetEvent
.packet
^.dataLength
-1, True) then
622 chunk
:= msg
.ReadLongInt();
623 if (chunk
< 0) or (chunk
> (tf
.size
+tf
.chunkSize
-1) div tf
.chunkSize
) then
628 e_LogWritefln('client #%d started file transfer from chunk %d', [nc
.ID
, chunk
]);
629 // start sending chunks
630 tf
.inProgress
:= True;
631 tf
.lastSentChunk
:= chunk
-1;
632 tf
.lastAckChunk
:= chunk
-1;
633 ProcessChunkSend(nc
^);
635 NTF_CLIENT_ACK
: // chunk ack; chunk number follows
637 if not Assigned(tf
.stream
) then
642 if (tf
.lastSentChunk
< 0) or (not tf
.inProgress
) then
644 // double ack, get lost
648 if (NetEvent
.packet
^.dataLength
< 2) then
654 if not msg
.Init(NetEvent
.packet
^.data
+1, NetEvent
.packet
^.dataLength
-1, True) then
659 chunk
:= msg
.ReadLongInt();
660 if (chunk
< 0) or (chunk
> (tf
.size
+tf
.chunkSize
-1) div tf
.chunkSize
) then
665 // do it this way, so client may seek, or request retransfers for some reason
666 tf
.lastAckChunk
:= chunk
;
667 tf
.lastSentChunk
:= chunk
;
668 //e_LogWritefln('client #%d acked file transfer chunk %d', [nc.ID, chunk]);
669 ProcessChunkSend(nc
^);
671 NTF_CLIENT_MAP_REQUEST
:
673 e_LogWritefln('client #%d requested map info', [nc
.ID
]);
675 dfn
:= findDiskWad(gGameSettings
.WAD
);
676 if (dfn
= '') then dfn
:= '!wad_not_found!.wad'; //FIXME
677 //md5 := MD5File(dfn);
679 if (not GetDiskFileInfo(dfn
, fi
)) then
681 e_LogWritefln('client #%d requested map info, but i cannot get file info', [nc
.ID
]);
687 st := openDiskFileRO(dfn);
688 if not assigned(st) then exit; //wtf?!
693 trans_omsg
.Write(Byte(NTF_SERVER_MAP_INFO
));
695 trans_omsg
.Write(ExtractFileName(gGameSettings
.WAD
));
697 trans_omsg
.Write(md5
);
699 trans_omsg
.Write(size
);
700 // number of external resources for map
701 trans_omsg
.Write(LongInt(length(gExternalResources
)));
702 // external resource names
703 for f
:= 0 to High(gExternalResources
) do
706 //trans_omsg.Write(ExtractFileName(gExternalResources[f])); // GameDir+'/wads/'+ResList.Strings[i]
708 trans_omsg
.Write('!');
709 trans_omsg
.Write(LongInt(gExternalResources
[f
].size
));
710 trans_omsg
.Write(gExternalResources
[f
].hash
);
711 trans_omsg
.Write(ExtractFileName(gExternalResources
[f
].diskName
));
714 if not ftransSendServerMsg(nc
^, trans_omsg
) then exit
;
725 //**************************************************************************
727 // file transfer crap (both client and server)
729 //**************************************************************************
731 function getNewTimeoutEnd (): Int64;
733 result
:= GetTimerMS();
734 if (g_Net_DownloadTimeout
<= 0) then
736 result
:= result
+1000*60*3; // 3 minutes
740 result
:= result
+trunc(g_Net_DownloadTimeout
*1000);
745 // send map request to server, and wait for "map info" server reply
747 // returns `false` on error or user abort
749 // diskName: map wad file name (without a path)
750 // hash: map wad hash
751 // size: map wad size
752 // chunkSize: set too
753 // resList: list of resource wads
759 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
760 function g_Net_Wait_MapInfo (var tf
: TNetFileTransfer
; var resList
: TNetMapResourceInfoArray
): Integer;
766 freePacket
: Boolean = false;
771 ri
: ^TNetMapResourceInfo
;
773 SetLength(resList
, 0);
777 trans_omsg
.Write(Byte(NTF_CLIENT_MAP_REQUEST
));
778 if not ftransSendClientMsg(trans_omsg
) then begin result
:= -1; exit
; end;
780 FillChar(ev
, SizeOf(ev
), 0);
783 ett
:= getNewTimeoutEnd();
785 status
:= enet_host_service(NetHost
, @ev
, 300);
789 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True);
794 if (status
<= 0) then
800 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' timeout reached', True);
809 ENET_EVENT_TYPE_RECEIVE
:
812 if (ev
.channelID
<> NET_CHAN_DOWNLOAD
) then
814 //e_LogWritefln('g_Net_Wait_MapInfo: skip message from non-transfer channel', []);
816 g_Net_Client_HandlePacket(ev
.packet
, g_Net_ClientLightMsgHandler
);
817 if (g_Res_received_map_start
< 0) then begin result
:= -666; exit
; end;
821 ett
:= getNewTimeoutEnd();
822 if (ev
.packet
.dataLength
< 1) then
824 e_LogWritefln('g_Net_Wait_MapInfo: invalid server packet (no data)', []);
828 Ptr
:= ev
.packet
^.data
;
829 rMsgId
:= Byte(Ptr
^);
830 e_LogWritefln('g_Net_Wait_MapInfo: got message %u from server (dataLength=%u)', [rMsgId
, ev
.packet
^.dataLength
]);
831 if (rMsgId
= NTF_SERVER_FILE_INFO
) then
833 e_LogWritefln('g_Net_Wait_MapInfo: waiting for map info reply, but got file info reply', []);
837 else if (rMsgId
= NTF_SERVER_ABORT
) then
839 e_LogWritefln('g_Net_Wait_MapInfo: server aborted transfer', []);
843 else if (rMsgId
= NTF_SERVER_MAP_INFO
) then
845 e_LogWritefln('g_Net_Wait_MapInfo: creating map info packet...', []);
846 if not msg
.Init(ev
.packet
^.data
+1, ev
.packet
^.dataLength
-1, True) then exit
;
847 e_LogWritefln('g_Net_Wait_MapInfo: parsing map info packet (rd=%d; max=%d)...', [msg
.ReadCount
, msg
.MaxSize
]);
848 SetLength(resList
, 0); // just in case
850 tf
.diskName
:= msg
.ReadString();
851 e_LogWritefln('g_Net_Wait_MapInfo: map wad is `%s`', [tf
.diskName
]);
853 tf
.hash
:= msg
.ReadMD5();
855 tf
.size
:= msg
.ReadLongInt();
856 e_LogWritefln('g_Net_Wait_MapInfo: map wad size is %d', [tf
.size
]);
857 // number of external resources for map
858 rc
:= msg
.ReadLongInt();
859 if (rc
< 0) or (rc
> 1024) then
861 e_LogWritefln('g_Net_Wait_MapInfo: invalid number of map external resources (%d)', [rc
]);
865 e_LogWritefln('g_Net_Wait_MapInfo: map external resource count is %d', [rc
]);
866 SetLength(resList
, rc
);
867 // external resource names
868 for f
:= 0 to rc
-1 do
871 s
:= msg
.ReadString();
872 if (length(s
) = 0) then begin result
:= -1; exit
; end;
876 ri
.size
:= msg
.ReadLongInt();
877 ri
.hash
:= msg
.ReadMD5();
878 ri
.wadName
:= ExtractFileName(msg
.ReadString());
879 if (length(ri
.wadName
) = 0) or (ri
.size
< 0) then begin result
:= -1; exit
; end;
883 // old-style packet, only name
884 ri
.wadName
:= ExtractFileName(s
);
885 if (length(ri
.wadName
) = 0) then begin result
:= -1; exit
; end;
886 ri
.size
:= -1; // unknown
889 e_LogWritefln('g_Net_Wait_MapInfo: got map info', []);
890 Result
:= 0; // success
895 e_LogWritefln('g_Net_Wait_MapInfo: invalid server packet type', []);
901 ENET_EVENT_TYPE_DISCONNECT
:
903 if (ev
.data
<= NET_DISC_MAX
) then
904 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' ' + _lc
[TStrings_Locale(Cardinal(I_NET_DISC_NONE
) + ev
.data
)], True);
910 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' unknown ENet event ' + IntToStr(Ord(ev
.kind
)), True);
915 if (freePacket
) then begin freePacket
:= false; enet_packet_destroy(ev
.packet
); end;
918 ProcessLoading(False);
919 if g_Net_UserRequestExit() then
921 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' user abort', True);
927 if (freePacket
) then enet_packet_destroy(ev
.packet
);
932 // send file request to server, and wait for server reply
934 // returns `false` on error or user abort
936 // diskName (actually, base name)
945 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
946 function g_Net_RequestResFileInfo (resIndex
: LongInt; out tf
: TNetFileTransfer
): Integer;
952 freePacket
: Boolean = false;
958 trans_omsg
.Write(Byte(NTF_CLIENT_FILE_REQUEST
));
959 trans_omsg
.Write(resIndex
);
960 if not ftransSendClientMsg(trans_omsg
) then begin result
:= -1; exit
; end;
962 FillChar(ev
, SizeOf(ev
), 0);
965 ett
:= getNewTimeoutEnd();
967 status
:= enet_host_service(NetHost
, @ev
, 300);
971 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True);
976 if (status
<= 0) then
982 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' timeout reached', True);
991 ENET_EVENT_TYPE_RECEIVE
:
994 if (ev
.channelID
<> NET_CHAN_DOWNLOAD
) then
996 //e_LogWriteln('g_Net_RequestResFileInfo: skip message from non-transfer channel');
998 g_Net_Client_HandlePacket(ev
.packet
, g_Net_ClientLightMsgHandler
);
999 if (g_Res_received_map_start
< 0) then begin result
:= -666; exit
; end;
1003 ett
:= getNewTimeoutEnd();
1004 if (ev
.packet
.dataLength
< 1) then
1006 e_LogWriteln('g_Net_RequestResFileInfo: invalid server packet (no data)');
1010 Ptr
:= ev
.packet
^.data
;
1011 rMsgId
:= Byte(Ptr
^);
1012 e_LogWritefln('received transfer packet with id %d (%u bytes)', [rMsgId
, ev
.packet
^.dataLength
]);
1013 if (rMsgId
= NTF_SERVER_FILE_INFO
) then
1015 if not msg
.Init(ev
.packet
^.data
+1, ev
.packet
^.dataLength
-1, True) then exit
;
1016 tf
.hash
:= msg
.ReadMD5();
1017 tf
.size
:= msg
.ReadLongInt();
1018 tf
.chunkSize
:= msg
.ReadLongInt();
1019 tf
.diskName
:= ExtractFileName(msg
.readString());
1020 if (tf
.size
< 0) or (tf
.chunkSize
<> FILE_CHUNK_SIZE
) or (length(tf
.diskName
) = 0) then
1022 e_LogWritefln('g_Net_RequestResFileInfo: invalid file info packet', []);
1026 e_LogWritefln('got file info for resource #%d: size=%d; name=%s', [resIndex
, tf
.size
, tf
.diskName
]);
1027 Result
:= 0; // success
1030 else if (rMsgId
= NTF_SERVER_ABORT
) then
1032 e_LogWriteln('g_Net_RequestResFileInfo: server aborted transfer');
1036 else if (rMsgId
= NTF_SERVER_MAP_INFO
) then
1038 e_LogWriteln('g_Net_RequestResFileInfo: waiting for map info reply, but got file info reply');
1044 e_LogWriteln('g_Net_RequestResFileInfo: invalid server packet type');
1050 ENET_EVENT_TYPE_DISCONNECT
:
1052 if (ev
.data
<= NET_DISC_MAX
) then
1053 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' ' + _lc
[TStrings_Locale(Cardinal(I_NET_DISC_NONE
) + ev
.data
)], True);
1059 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' unknown ENet event ' + IntToStr(Ord(ev
.kind
)), True);
1064 if (freePacket
) then begin freePacket
:= false; enet_packet_destroy(ev
.packet
); end;
1067 ProcessLoading(False);
1068 if g_Net_UserRequestExit() then
1070 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' user abort', True);
1076 if (freePacket
) then enet_packet_destroy(ev
.packet
);
1081 // call this to cancel file transfer requested by `g_Net_RequestResFileInfo()`
1082 function g_Net_AbortResTransfer (var tf
: TNetFileTransfer
): Boolean;
1085 e_LogWritefln('aborting file transfer...', []);
1088 trans_omsg
.Write(Byte(NTF_CLIENT_ABORT
));
1089 result
:= ftransSendClientMsg(trans_omsg
);
1090 if result
then enet_host_flush(NetHost
);
1094 // call this to start file transfer requested by `g_Net_RequestResFileInfo()`
1096 // returns `false` on error or user abort
1105 // 2 on server abort
1106 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
1107 function g_Net_ReceiveResourceFile (resIndex
: LongInt; var tf
: TNetFileTransfer
; strm
: TStream
): Integer;
1113 freePacket
: Boolean = false;
1116 nextChunk
: Integer = 0;
1117 chunkTotal
: Integer;
1124 tf
.resumed
:= false;
1125 e_LogWritefln('file `%s`, size=%d (%d)', [tf
.diskName
, Integer(strm
.size
), tf
.size
], TMsgType
.Notify
);
1126 // check if we should resume downloading
1127 resumed
:= (strm
.size
> tf
.chunkSize
) and (strm
.size
< tf
.size
);
1130 trans_omsg
.Write(Byte(NTF_CLIENT_START
));
1131 if resumed
then chunk
:= strm
.size
div tf
.chunkSize
else chunk
:= 0;
1132 trans_omsg
.Write(LongInt(chunk
));
1133 if not ftransSendClientMsg(trans_omsg
) then begin result
:= -1; exit
; end;
1135 strm
.Seek(chunk
*tf
.chunkSize
, soFromBeginning
);
1136 chunkTotal
:= (tf
.size
+tf
.chunkSize
-1) div tf
.chunkSize
;
1137 e_LogWritefln('receiving file `%s` (%d chunks)', [tf
.diskName
, chunkTotal
], TMsgType
.Notify
);
1138 g_Game_SetLoadingText('downloading "'+ExtractFileName(tf
.diskName
)+'"', chunkTotal
, False);
1139 tf
.resumed
:= resumed
;
1141 if (chunk
> 0) then g_Game_StepLoading(chunk
);
1144 // wait for reply data
1145 FillChar(ev
, SizeOf(ev
), 0);
1147 GetMem(buf
, tf
.chunkSize
);
1149 ett
:= getNewTimeoutEnd();
1151 //stx := -GetTimerMS();
1152 status
:= enet_host_service(NetHost
, @ev
, 300);
1154 if (status < 0) then
1156 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True);
1161 if (status
<= 0) then
1163 // check for timeout
1167 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' timeout reached', True);
1176 ENET_EVENT_TYPE_RECEIVE
:
1179 if (ev
.channelID
<> NET_CHAN_DOWNLOAD
) then
1181 //e_LogWritefln('g_Net_ReceiveResourceFile: skip message from non-transfer channel', []);
1182 freePacket
:= false;
1183 g_Net_Client_HandlePacket(ev
.packet
, g_Net_ClientLightMsgHandler
);
1184 if (g_Res_received_map_start
< 0) then begin result
:= -666; exit
; end;
1188 //stx := stx+GetTimerMS();
1189 //e_LogWritefln('g_Net_ReceiveResourceFile: stx=%d', [Integer(stx)]);
1190 //stx := -GetTimerMS();
1191 ett
:= getNewTimeoutEnd();
1192 if (ev
.packet
.dataLength
< 1) then
1194 e_LogWritefln('g_Net_ReceiveResourceFile: invalid server packet (no data)', []);
1198 Ptr
:= ev
.packet
^.data
;
1199 rMsgId
:= Byte(Ptr
^);
1200 if (rMsgId
= NTF_SERVER_DONE
) then
1202 e_LogWritefln('file transfer complete.', []);
1206 else if (rMsgId
= NTF_SERVER_CHUNK
) then
1208 if not msg
.Init(ev
.packet
^.data
+1, ev
.packet
^.dataLength
-1, True) then exit
;
1209 chunk
:= msg
.ReadLongInt();
1210 csize
:= msg
.ReadLongInt();
1211 if (chunk
<> nextChunk
) then
1213 e_LogWritefln('received chunk %d, but expected chunk %d', [chunk
, nextChunk
]);
1217 if (csize
< 0) or (csize
> tf
.chunkSize
) then
1219 e_LogWritefln('received chunk with size %d, but expected chunk size is %d', [csize
, tf
.chunkSize
]);
1223 //e_LogWritefln('got chunk #%d of #%d (csize=%d)', [chunk, (tf.size+tf.chunkSize-1) div tf.chunkSize, csize]);
1224 msg
.ReadData(buf
, csize
);
1225 strm
.WriteBuffer(buf
^, csize
);
1226 nextChunk
:= chunk
+1;
1227 g_Game_StepLoading();
1230 trans_omsg
.Write(Byte(NTF_CLIENT_ACK
));
1231 trans_omsg
.Write(LongInt(chunk
));
1232 if not ftransSendClientMsg(trans_omsg
) then begin result
:= -1; exit
; end;
1234 else if (rMsgId
= NTF_SERVER_ABORT
) then
1236 e_LogWritefln('g_Net_ReceiveResourceFile: server aborted transfer', []);
1242 e_LogWritefln('g_Net_ReceiveResourceFile: invalid server packet type', []);
1246 //stx := stx+GetTimerMS();
1247 //e_LogWritefln('g_Net_ReceiveResourceFile: process stx=%d', [Integer(stx)]);
1250 ENET_EVENT_TYPE_DISCONNECT
:
1252 if (ev
.data
<= NET_DISC_MAX
) then
1253 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' ' + _lc
[TStrings_Locale(Cardinal(I_NET_DISC_NONE
) + ev
.data
)], True);
1259 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' unknown ENet event ' + IntToStr(Ord(ev
.kind
)), True);
1264 if (freePacket
) then begin freePacket
:= false; enet_packet_destroy(ev
.packet
); end;
1267 ProcessLoading(False);
1268 if g_Net_UserRequestExit() then
1270 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' user abort', True);
1277 if (freePacket
) then enet_packet_destroy(ev
.packet
);
1282 //**************************************************************************
1286 //**************************************************************************
1288 function g_Net_FindSlot(): Integer;
1297 for I
:= Low(NetClients
) to High(NetClients
) do
1299 if NetClients
[I
].Used
then
1308 if C
>= NetMaxClients
then
1316 if (Length(NetClients
) >= NetMaxClients
) then
1320 SetLength(NetClients
, Length(NetClients
) + 1);
1321 N
:= High(NetClients
);
1327 NetClients
[N
].Used
:= True;
1328 NetClients
[N
].ID
:= N
;
1329 NetClients
[N
].RequestedFullUpdate
:= False;
1330 NetClients
[N
].WaitForFirstSpawn
:= False;
1331 NetClients
[N
].RCONAuth
:= False;
1332 NetClients
[N
].Voted
:= False;
1333 NetClients
[N
].Player
:= 0;
1334 clearNetClientTransfers(NetClients
[N
]); // just in case
1341 function g_Net_Init(): Boolean;
1350 NetBuf
[NET_UNRELIABLE
].Clear();
1351 NetBuf
[NET_RELIABLE
].Clear();
1358 NetAddr
.port
:= 25666;
1359 SetLength(NetBannedHosts
, 0);
1360 path
:= BANLIST_FILENAME
;
1361 if e_FindResource(DataDirs
, path
) = true then
1368 if StrToIp(IPstr
, IP
) then
1369 g_Net_BanAddress(IP
);
1372 g_Net_SaveBanList();
1375 //Result := (enet_initialize() = 0);
1376 Result
:= enet_init_success
;
1379 procedure g_Net_Flush();
1383 F
, Chan
: enet_uint32
;
1387 Chan
:= NET_CHAN_UNRELIABLE
;
1389 if NetMode
= NET_SERVER
then
1390 for T
:= NET_UNRELIABLE
to NET_RELIABLE
do
1392 for I
:= Low(NetClients
) to High(NetClients
) do
1394 if not NetClients
[I
].Used
then continue
;
1395 if NetClients
[I
].NetOut
[T
].CurSize
<= 0 then continue
;
1396 P
:= enet_packet_create(NetClients
[I
].NetOut
[T
].Data
, NetClients
[I
].NetOut
[T
].CurSize
, F
);
1397 if not Assigned(P
) then continue
;
1398 enet_peer_send(NetClients
[I
].Peer
, Chan
, P
);
1399 NetClients
[I
].NetOut
[T
].Clear();
1402 // next and last iteration is always RELIABLE
1403 F
:= LongWord(ENET_PACKET_FLAG_RELIABLE
);
1404 Chan
:= NET_CHAN_RELIABLE
;
1406 else if NetMode
= NET_CLIENT
then
1407 for T
:= NET_UNRELIABLE
to NET_RELIABLE
do
1409 if NetBuf
[T
].CurSize
> 0 then
1411 P
:= enet_packet_create(NetBuf
[T
].Data
, NetBuf
[T
].CurSize
, F
);
1412 if not Assigned(P
) then continue
;
1413 enet_peer_send(NetPeer
, Chan
, P
);
1416 // next and last iteration is always RELIABLE
1417 F
:= LongWord(ENET_PACKET_FLAG_RELIABLE
);
1418 Chan
:= NET_CHAN_RELIABLE
;
1422 procedure g_Net_Cleanup();
1426 NetBuf
[NET_UNRELIABLE
].Clear();
1427 NetBuf
[NET_RELIABLE
].Clear();
1430 NetClientCount
:= 0;
1434 g_Net_Slist_ServerClosed();
1438 NetState
:= NET_STATE_NONE
;
1440 NetPongSock
:= ENET_SOCKET_NULL
;
1442 NetTimeToMaster
:= 0;
1443 NetTimeToUpdate
:= 0;
1444 NetTimeToReliable
:= 0;
1446 NetMode
:= NET_NONE
;
1448 if NetPortThread
<> NilThreadId
then
1450 // TODO: Use TThread instead of procedural threading API to manage thread resources properly.
1451 WaitForThreadTerminate(NetPortThread
, 66666);
1452 CloseThread(NetPortThread
);
1453 NetPortThread
:= NilThreadId
;
1456 g_Net_UnforwardPorts();
1462 procedure g_Net_Free();
1466 //enet_deinitialize();
1467 NetInitDone
:= False;
1471 //**************************************************************************
1475 //**************************************************************************
1477 function ForwardThread(Param
: Pointer): PtrInt
;
1480 if not g_Net_ForwardPorts() then Result
:= -1;
1483 function g_Net_Host(IPAddr
: LongWord
; Port
: enet_uint16
; MaxClients
: Cardinal): Boolean;
1485 if NetMode
<> NET_NONE
then
1487 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_INGAME
]);
1494 g_Console_Add(_lc
[I_NET_MSG
] + Format(_lc
[I_NET_MSG_HOST
], [Port
]));
1495 if not NetInitDone
then
1497 if not g_Net_Init() then
1499 g_Console_Add(_lc
[I_NET_MSG_FERROR
] + _lc
[I_NET_ERR_ENET
]);
1504 NetInitDone
:= True;
1507 NetAddr
.host
:= IPAddr
;
1508 NetAddr
.port
:= Port
;
1510 NetHost
:= enet_host_create(@NetAddr
, NET_MAXCLIENTS
, NET_CHANNELS
, 0, 0);
1512 if NetHost
= nil then
1514 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + Format(_lc
[I_NET_ERR_HOST
], [Port
]));
1520 if NetForwardPorts
then
1521 NetPortThread
:= BeginThread(ForwardThread
);
1523 NetPongSock
:= enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM
);
1524 if NetPongSock
<> ENET_SOCKET_NULL
then
1526 NetPongAddr
.host
:= IPAddr
;
1527 NetPongAddr
.port
:= NET_PING_PORT
;
1528 if enet_socket_bind(NetPongSock
, @NetPongAddr
) < 0 then
1530 enet_socket_destroy(NetPongSock
);
1531 NetPongSock
:= ENET_SOCKET_NULL
;
1534 enet_socket_set_option(NetPongSock
, ENET_SOCKOPT_NONBLOCK
, 1);
1537 NetMode
:= NET_SERVER
;
1539 NetBuf
[NET_UNRELIABLE
].Clear();
1540 NetBuf
[NET_RELIABLE
].Clear();
1546 procedure g_Net_Host_Die();
1550 if NetMode
<> NET_SERVER
then
1553 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_HOST_DISCALL
]);
1554 for I
:= 0 to High(NetClients
) do
1555 if NetClients
[I
].Used
then
1556 enet_peer_disconnect(NetClients
[I
].Peer
, NET_DISC_DOWN
);
1558 while enet_host_service(NetHost
, @NetEvent
, 1000) > 0 do
1559 if NetEvent
.kind
= ENET_EVENT_TYPE_RECEIVE
then
1560 enet_packet_destroy(NetEvent
.packet
);
1562 for I
:= 0 to High(NetClients
) do
1563 if NetClients
[I
].Used
then
1565 FreeMemory(NetClients
[I
].Peer
^.data
);
1566 NetClients
[I
].Peer
^.data
:= nil;
1567 enet_peer_reset(NetClients
[I
].Peer
);
1568 NetClients
[I
].Peer
:= nil;
1569 NetClients
[I
].Used
:= False;
1570 NetClients
[I
].Player
:= 0;
1571 NetClients
[I
].Crimes
:= 0;
1572 NetClients
[I
].AuthTime
:= 0;
1573 NetClients
[I
].MsgTime
:= 0;
1574 NetClients
[I
].NetOut
[NET_UNRELIABLE
].Free();
1575 NetClients
[I
].NetOut
[NET_RELIABLE
].Free();
1578 g_Net_Slist_ServerClosed();
1579 if NetPongSock
<> ENET_SOCKET_NULL
then
1580 enet_socket_destroy(NetPongSock
);
1582 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_HOST_DIE
]);
1583 enet_host_destroy(NetHost
);
1585 NetMode
:= NET_NONE
;
1588 e_WriteLog('NET: Server stopped', TMsgType
.Notify
);
1592 procedure g_Net_Host_Send(ID
: Integer; Reliable
: Boolean);
1597 then T
:= NET_RELIABLE
1598 else T
:= NET_UNRELIABLE
;
1602 if ID
> High(NetClients
) then Exit
;
1603 if NetClients
[ID
].Peer
= nil then Exit
;
1605 NetClients
[ID
].NetOut
[T
].Write(Integer(NetOut
.CurSize
));
1606 NetClients
[ID
].NetOut
[T
].Write(NetOut
);
1610 for ID
:= Low(NetClients
) to High(NetClients
) do
1612 if NetClients
[ID
].Used
then
1615 NetClients
[ID
].NetOut
[T
].Write(Integer(NetOut
.CurSize
));
1616 NetClients
[ID
].NetOut
[T
].Write(NetOut
);
1621 if NetDump
then g_Net_DumpSendBuffer();
1625 procedure g_Net_Host_Disconnect_Client(ID
: Integer; Force
: Boolean = False);
1630 TC
:= @NetClients
[ID
];
1631 if (TC
= nil) then Exit
;
1632 clearNetClient(NetClients
[ID
]);
1633 if not (TC
^.Used
) then Exit
;
1635 TP
:= g_Player_Get(TC
^.Player
);
1640 TP
.Kill(K_SIMPLEKILL
, 0, HIT_DISCON
);
1641 g_Console_Add(Format(_lc
[I_PLAYER_LEAVE
], [TP
.Name
]), True);
1642 e_WriteLog('NET: Client ' + TP
.Name
+ ' [' + IntToStr(TC
^.ID
) + '] disconnected.', TMsgType
.Notify
);
1643 g_Player_Remove(TP
.UID
);
1646 if (TC
^.Peer
^.data
<> nil) then
1648 FreeMemory(TC
^.Peer
^.data
);
1649 TC
^.Peer
^.data
:= nil;
1653 enet_peer_reset(TC
^.Peer
);
1656 TC
^.State
:= NET_STATE_NONE
;
1662 TC
^.RequestedFullUpdate
:= False;
1663 TC
^.FullUpdateSent
:= False;
1664 TC
^.WaitForFirstSpawn
:= False;
1665 TC
^.NetOut
[NET_UNRELIABLE
].Free();
1666 TC
^.NetOut
[NET_RELIABLE
].Free();
1668 g_Console_Add(_lc
[I_NET_MSG
] + Format(_lc
[I_NET_MSG_HOST_DISC
], [ID
]));
1669 Dec(NetClientCount
);
1671 if NetUseMaster
then g_Net_Slist_ServerPlayerLeaves();
1674 procedure g_Net_Host_Kick(ID
: Integer; Reason
: enet_uint32
);
1679 TC
:= @NetClients
[ID
];
1680 if (TC
<> nil) and TC
^.Used
and (TC
^.Peer
<> nil) then
1683 g_Net_Host_Disconnect_Client(ID
);
1684 enet_peer_disconnect(Peer
, Reason
);
1688 procedure g_Net_Host_CheckPings();
1690 ClAddr
: ENetAddress
;
1694 Ping
: array [0..9] of Byte;
1697 if (NetPongSock
= ENET_SOCKET_NULL
) or (NetHost
= nil) then Exit
;
1699 Buf
.data
:= Addr(Ping
[0]);
1700 Buf
.dataLength
:= 2+8;
1704 Len
:= enet_socket_receive(NetPongSock
, @ClAddr
, @Buf
, 1);
1705 if Len
< 0 then Exit
;
1707 if (Ping
[0] = Ord('D')) and (Ping
[1] = Ord('F')) then
1709 ClTime
:= Int64(Addr(Ping
[2])^);
1712 NetOut
.Write(Byte(Ord('D')));
1713 NetOut
.Write(Byte(Ord('F')));
1714 NetOut
.Write(NetHost
.address
.port
);
1715 NetOut
.Write(ClTime
);
1716 TMasterHost
.writeInfo(NetOut
);
1718 if gPlayer1
<> nil then Inc(NPl
);
1719 if gPlayer2
<> nil then Inc(NPl
);
1721 NetOut
.Write(gNumBots
);
1723 Buf
.data
:= NetOut
.Data
;
1724 Buf
.dataLength
:= NetOut
.CurSize
;
1725 enet_socket_send(NetPongSock
, @ClAddr
, @Buf
, 1);
1731 procedure g_Net_Host_CheckTimeouts();
1735 for ID
:= Low(NetClients
) to High(NetClients
) do
1737 with NetClients
[ID
] do
1739 if (Peer
= nil) or (State
= NET_STATE_NONE
) then continue
;
1740 if (State
= NET_STATE_AUTH
) and (AuthTime
> 0) and (AuthTime
<= gTime
) then
1742 g_Net_Penalize(@NetClients
[ID
], 'auth taking too long');
1743 AuthTime
:= gTime
+ 1000; // do it every second to give them a chance
1745 else if (State
= NET_STATE_GAME
) and (MsgTime
> 0) and (MsgTime
<= gTime
) then
1747 // client hasn't sent packets in a while; either ban em or kick em
1748 if (NetAutoBanForTimeout
) then
1750 g_Net_Penalize(@NetClients
[ID
], 'message timeout');
1751 MsgTime
:= gTime
+ (NetPacketTimeout
div 2) + 500; // wait less for the next check
1755 e_LogWritefln('NET: client #%u (cid #%u) timed out', [ID
, Player
]);
1756 g_Net_Host_Disconnect_Client(ID
, True);
1764 procedure g_Net_Host_Update();
1773 if NetUseMaster
then g_Net_Slist_Pulse();
1774 g_Net_Host_CheckPings();
1775 g_Net_Host_CheckTimeouts();
1777 while (enet_host_service(NetHost
, @NetEvent
, 0) > 0) do
1779 case (NetEvent
.kind
) of
1780 ENET_EVENT_TYPE_CONNECT
:
1782 IP
:= IpToStr(NetEvent
.Peer
^.address
.host
);
1783 Port
:= NetEvent
.Peer
^.address
.port
;
1784 g_Console_Add(_lc
[I_NET_MSG
] +
1785 Format(_lc
[I_NET_MSG_HOST_CONN
], [IP
, Port
]));
1786 e_WriteLog('NET: Connection request from ' + IP
+ '.', TMsgType
.Notify
);
1788 if (NetEvent
.data
<> NET_PROTOCOL_VER
) then
1790 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_HOST_REJECT
] +
1791 _lc
[I_NET_DISC_PROTOCOL
]);
1792 e_WriteLog('NET: Connection request from ' + IP
+ ' rejected: version mismatch',
1794 enet_peer_disconnect(NetEvent
.peer
, NET_DISC_PROTOCOL
);
1798 if g_Net_IsAddressBanned(NetEvent
.Peer
^.address
.host
) then
1800 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_HOST_REJECT
] +
1801 _lc
[I_NET_DISC_BAN
]);
1802 e_WriteLog('NET: Connection request from ' + IP
+ ' rejected: banned',
1804 enet_peer_disconnect(NetEvent
.Peer
, NET_DISC_BAN
);
1808 ID
:= g_Net_FindSlot();
1812 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_HOST_REJECT
] +
1813 _lc
[I_NET_DISC_FULL
]);
1814 e_WriteLog('NET: Connection request from ' + IP
+ ' rejected: server full',
1816 enet_peer_disconnect(NetEvent
.peer
, NET_DISC_FULL
);
1820 NetClients
[ID
].Peer
:= NetEvent
.peer
;
1821 NetClients
[ID
].Peer
^.data
:= GetMemory(SizeOf(Byte));
1822 Byte(NetClients
[ID
].Peer
^.data
^) := ID
;
1823 NetClients
[ID
].State
:= NET_STATE_AUTH
;
1824 NetClients
[ID
].Player
:= 0;
1825 NetClients
[ID
].Crimes
:= 0;
1826 NetClients
[ID
].RCONAuth
:= False;
1827 NetClients
[ID
].NetOut
[NET_UNRELIABLE
].Alloc(NET_BUFSIZE
*2);
1828 NetClients
[ID
].NetOut
[NET_RELIABLE
].Alloc(NET_BUFSIZE
*2);
1829 if (NetAuthTimeout
> 0) then
1830 NetClients
[ID
].AuthTime
:= gTime
+ NetAuthTimeout
1832 NetClients
[ID
].AuthTime
:= 0;
1833 if (NetPacketTimeout
> 0) then
1834 NetClients
[ID
].MsgTime
:= gTime
+ NetPacketTimeout
1836 NetClients
[ID
].MsgTime
:= 0;
1837 clearNetClientTransfers(NetClients
[ID
]); // just in case
1839 enet_peer_timeout(NetEvent
.peer
, ENET_PEER_TIMEOUT_LIMIT
* 2, ENET_PEER_TIMEOUT_MINIMUM
* 2, ENET_PEER_TIMEOUT_MAXIMUM
* 2);
1841 Inc(NetClientCount
);
1842 g_Console_Add(_lc
[I_NET_MSG
] + Format(_lc
[I_NET_MSG_HOST_ADD
], [ID
]));
1845 ENET_EVENT_TYPE_RECEIVE
:
1847 //e_LogWritefln('RECEIVE: chan=%u', [NetEvent.channelID]);
1848 if (NetEvent
.channelID
= NET_CHAN_DOWNLOAD
) then
1850 ProcessDownloadExPacket();
1854 if NetEvent
.peer
^.data
= nil then Exit
;
1856 ID
:= Byte(NetEvent
.peer
^.data
^);
1857 if ID
> High(NetClients
) then Exit
;
1858 TC
:= @NetClients
[ID
];
1860 if (NetPacketTimeout
> 0) then
1861 TC
^.MsgTime
:= gTime
+ NetPacketTimeout
;
1863 if NetDump
then g_Net_DumpRecvBuffer(NetEvent
.packet
^.data
, NetEvent
.packet
^.dataLength
);
1864 g_Net_Host_HandlePacket(TC
, NetEvent
.packet
, g_Net_HostMsgHandler
);
1868 ENET_EVENT_TYPE_DISCONNECT
:
1870 if NetEvent
.peer
^.data
<> nil then
1872 ID
:= Byte(NetEvent
.peer
^.data
^);
1873 if ID
> High(NetClients
) then Exit
;
1874 g_Net_Host_Disconnect_Client(ID
);
1882 //**************************************************************************
1886 //**************************************************************************
1888 procedure g_Net_Disconnect(Forced
: Boolean = False);
1890 if NetMode
<> NET_CLIENT
then Exit
;
1891 if (NetHost
= nil) or (NetPeer
= nil) then Exit
;
1895 enet_peer_disconnect(NetPeer
, NET_DISC_NONE
);
1897 while (enet_host_service(NetHost
, @NetEvent
, 1500) > 0) do
1899 if (NetEvent
.kind
= ENET_EVENT_TYPE_DISCONNECT
) then
1905 if (NetEvent
.kind
= ENET_EVENT_TYPE_RECEIVE
) then
1906 enet_packet_destroy(NetEvent
.packet
);
1909 if NetPeer
<> nil then
1911 enet_peer_reset(NetPeer
);
1917 e_WriteLog('NET: Kicked from server: ' + IntToStr(NetEvent
.data
), TMsgType
.Notify
);
1918 if (NetEvent
.data
<= NET_DISC_MAX
) then
1919 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_KICK
] +
1920 _lc
[TStrings_Locale(Cardinal(I_NET_DISC_NONE
) + NetEvent
.data
)], True);
1923 if NetHost
<> nil then
1925 enet_host_destroy(NetHost
);
1928 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_CLIENT_DISC
]);
1931 e_WriteLog('NET: Disconnected', TMsgType
.Notify
);
1934 procedure g_Net_Client_Send(Reliable
: Boolean);
1939 then T
:= NET_RELIABLE
1940 else T
:= NET_UNRELIABLE
;
1943 NetBuf
[T
].Write(Integer(NetOut
.CurSize
));
1944 NetBuf
[T
].Write(NetOut
);
1946 if NetDump
then g_Net_DumpSendBuffer();
1948 g_Net_Flush(); // FIXME: for now, send immediately
1951 procedure g_Net_Client_Update();
1953 while (NetHost
<> nil) and (enet_host_service(NetHost
, @NetEvent
, 0) > 0) do
1955 case NetEvent
.kind
of
1956 ENET_EVENT_TYPE_RECEIVE
:
1958 if NetDump
then g_Net_DumpRecvBuffer(NetEvent
.packet
^.data
, NetEvent
.packet
^.dataLength
);
1959 g_Net_Client_HandlePacket(NetEvent
.packet
, g_Net_ClientMsgHandler
);
1962 ENET_EVENT_TYPE_DISCONNECT
:
1964 g_Net_Disconnect(True);
1971 function g_Net_Connect(IP
: string; Port
: enet_uint16
): Boolean;
1974 TimeoutTime
, T
: Int64;
1976 if NetMode
<> NET_NONE
then
1978 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_ERR_INGAME
], True);
1985 g_Console_Add(_lc
[I_NET_MSG
] + Format(_lc
[I_NET_MSG_CLIENT_CONN
],
1987 if not NetInitDone
then
1989 if (not g_Net_Init()) then
1991 g_Console_Add(_lc
[I_NET_MSG_FERROR
] + _lc
[I_NET_ERR_ENET
], True);
1996 NetInitDone
:= True;
1999 NetHost
:= enet_host_create(nil, 1, NET_CHANNELS
, 0, 0);
2001 if (NetHost
= nil) then
2003 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CLIENT
], True);
2009 enet_address_set_host(@NetAddr
, PChar(Addr(IP
[1])));
2010 NetAddr
.port
:= Port
;
2012 NetPeer
:= enet_host_connect(NetHost
, @NetAddr
, NET_CHANNELS
, NET_PROTOCOL_VER
);
2014 if (NetPeer
= nil) then
2016 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CLIENT
], True);
2017 enet_host_destroy(NetHost
);
2023 // предупредить что ждем слишком долго через N секунд
2024 TimeoutTime
:= sys_GetTicks() + NET_CONNECT_TIMEOUT
;
2029 while (enet_host_service(NetHost
, @NetEvent
, 0) > 0) do
2031 if (NetEvent
.kind
= ENET_EVENT_TYPE_CONNECT
) then
2033 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_CLIENT_DONE
]);
2034 NetMode
:= NET_CLIENT
;
2036 enet_peer_timeout(NetPeer
, ENET_PEER_TIMEOUT_LIMIT
* 2, ENET_PEER_TIMEOUT_MINIMUM
* 2, ENET_PEER_TIMEOUT_MAXIMUM
* 2);
2038 NetClientPort
:= Port
;
2045 T
:= sys_GetTicks();
2046 if T
> TimeoutTime
then
2048 TimeoutTime
:= T
+ NET_CONNECT_TIMEOUT
* 100; // одного предупреждения хватит
2049 g_Console_Add(_lc
[I_NET_MSG_TIMEOUT_WARN
], True);
2050 g_Console_Add(Format(_lc
[I_NET_MSG_PORTS
], [Integer(Port
), Integer(NET_PING_PORT
)]), True);
2053 ProcessLoading(True);
2054 if e_KeyPressed(IK_SPACE
) or g_Net_UserRequestExit() then
2058 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_TIMEOUT
], True);
2059 g_Console_Add(Format(_lc
[I_NET_MSG_PORTS
], [Integer(Port
), Integer(NET_PING_PORT
)]), True);
2060 if NetPeer
<> nil then enet_peer_reset(NetPeer
);
2061 if NetHost
<> nil then
2063 enet_host_destroy(NetHost
);
2070 function IpToStr(IP
: LongWord
): string;
2075 Result
:= IntToStr(PByte(Ptr
+ 0)^) + '.';
2076 Result
:= Result
+ IntToStr(PByte(Ptr
+ 1)^) + '.';
2077 Result
:= Result
+ IntToStr(PByte(Ptr
+ 2)^) + '.';
2078 Result
:= Result
+ IntToStr(PByte(Ptr
+ 3)^);
2081 function StrToIp(IPstr
: string; var IP
: LongWord
): Boolean;
2085 Result
:= enet_address_set_host(@EAddr
, PChar(@IPstr
[1])) = 0;
2089 function g_Net_Client_ByName(Name
: string): pTNetClient
;
2095 for a
:= Low(NetClients
) to High(NetClients
) do
2096 if (NetClients
[a
].Used
) and (NetClients
[a
].State
= NET_STATE_GAME
) then
2098 pl
:= g_Player_Get(NetClients
[a
].Player
);
2099 if pl
= nil then continue
;
2100 if Copy(LowerCase(pl
.Name
), 1, Length(Name
)) <> LowerCase(Name
) then continue
;
2101 if NetClients
[a
].Peer
<> nil then
2103 Result
:= @NetClients
[a
];
2109 function g_Net_Client_ByPlayer(PID
: Word): pTNetClient
;
2114 for a
:= Low(NetClients
) to High(NetClients
) do
2115 if (NetClients
[a
].Used
) and (NetClients
[a
].State
= NET_STATE_GAME
) then
2116 if NetClients
[a
].Player
= PID
then
2118 Result
:= @NetClients
[a
];
2123 function g_Net_ClientName_ByID(ID
: Integer): string;
2129 if ID
= NET_EVERYONE
then
2131 for a
:= Low(NetClients
) to High(NetClients
) do
2132 if (NetClients
[a
].ID
= ID
) and (NetClients
[a
].Used
) and (NetClients
[a
].State
= NET_STATE_GAME
) then
2134 pl
:= g_Player_Get(NetClients
[a
].Player
);
2135 if pl
= nil then Exit
;
2139 Result
:= 'Client #' + IntToStr(ID
);
2142 function g_Net_IsAddressBanned(IP
: LongWord
; Perm
: Boolean = False): Boolean;
2147 if NetBannedHosts
= nil then
2149 for I
:= 0 to High(NetBannedHosts
) do
2150 if (NetBannedHosts
[I
].IP
= IP
) and ((not Perm
) or (NetBannedHosts
[I
].Perm
)) then
2157 procedure g_Net_BanAddress(IP
: LongWord
; Perm
: Boolean = True); overload
;
2163 if g_Net_IsAddressBanned(IP
, Perm
) then
2167 for I
:= Low(NetBannedHosts
) to High(NetBannedHosts
) do
2168 if NetBannedHosts
[I
].IP
= 0 then
2176 SetLength(NetBannedHosts
, Length(NetBannedHosts
) + 1);
2177 P
:= High(NetBannedHosts
);
2180 NetBannedHosts
[P
].IP
:= IP
;
2181 NetBannedHosts
[P
].Perm
:= Perm
;
2184 procedure g_Net_BanAddress(IP
: string; Perm
: Boolean = True); overload
;
2189 b
:= StrToIp(IP
, a
);
2191 g_Net_BanAddress(a
, Perm
);
2194 procedure g_Net_UnbanNonPerm();
2198 if NetBannedHosts
= nil then
2200 for I
:= Low(NetBannedHosts
) to High(NetBannedHosts
) do
2201 if (NetBannedHosts
[I
].IP
> 0) and not NetBannedHosts
[I
].Perm
then
2203 NetBannedHosts
[I
].IP
:= 0;
2204 NetBannedHosts
[I
].Perm
:= True;
2208 function g_Net_UnbanAddress(IP
: string): Boolean; overload
;
2212 Result
:= StrToIp(IP
, a
);
2214 Result
:= g_Net_UnbanAddress(a
);
2217 function g_Net_UnbanAddress(IP
: LongWord
): Boolean; overload
;
2224 if NetBannedHosts
= nil then
2226 for I
:= 0 to High(NetBannedHosts
) do
2227 if NetBannedHosts
[I
].IP
= IP
then
2229 NetBannedHosts
[I
].IP
:= 0;
2230 NetBannedHosts
[I
].Perm
:= True;
2232 // no break here to clear all bans of this host, perm and non-perm
2236 procedure g_Net_SaveBanList();
2242 path
:= e_GetWriteableDir(DataDirs
);
2245 path
:= ConcatPaths([path
, BANLIST_FILENAME
]);
2248 if NetBannedHosts
<> nil then
2249 for I
:= 0 to High(NetBannedHosts
) do
2250 if NetBannedHosts
[I
].Perm
and (NetBannedHosts
[I
].IP
> 0) then
2251 Writeln(F
, IpToStr(NetBannedHosts
[I
].IP
));
2256 procedure g_Net_Host_Ban(C
: pTNetClient
; Perm
: Boolean);
2258 KickReason
: enet_uint32
;
2261 if (not C
^.Used
) then
2265 KickReason
:= NET_DISC_BAN
2267 KickReason
:= NET_DISC_TEMPBAN
;
2269 Name
:= g_Net_ClientName_ByID(C
^.ID
);
2271 g_Net_BanAddress(C
^.Peer
^.address
.host
, Perm
);
2272 g_Net_Host_Kick(C
^.ID
, KickReason
);
2273 g_Console_Add(Format(_lc
[I_PLAYER_BAN
], [Name
]));
2274 MH_SEND_GameEvent(NET_EV_PLAYER_BAN
, 0, Name
);
2275 g_Net_Slist_ServerPlayerLeaves();
2276 g_Net_SaveBanList();
2279 procedure g_Net_Host_Ban(ID
: Integer; Perm
: Boolean);
2281 if (ID
< 0) or (ID
> High(NetClients
)) then
2283 g_Net_Host_Ban(@NetClients
[ID
], Perm
);
2286 procedure g_Net_Penalize(C
: pTNetClient
; Reason
: string);
2290 e_LogWritefln('NET: client #%u (cid #%u) triggered a penalty (%d/%d): %s',
2291 [C
^.ID
, C
^.Player
, C
^.Crimes
+ 1, NetAutoBanLimit
, Reason
]);
2293 if (NetAutoBanLimit
<= 0) then Exit
;
2295 if (C
^.Crimes
>= NetAutoBanLimit
) then
2297 // we have tried asking nicely before, now it is time to die
2298 e_LogWritefln('NET: client #%u (cid #%u) force kicked',
2299 [C
^.ID
, C
^.Player
]);
2300 g_Net_Host_Disconnect_Client(C
^.ID
, True);
2306 if (NetAutoBanWarn
) then
2307 MH_SEND_Chat('You have been warned by the server: ' + Reason
, NET_CHAT_SYSTEM
, C
^.ID
);
2309 if (C
^.Crimes
>= NetAutoBanLimit
) then
2315 procedure g_Net_DumpStart();
2317 if NetMode
= NET_SERVER
2318 then NetDumpFile
:= e_CreateResource(LogDirs
, NETDUMP_FILENAME
+ '_server')
2319 else NetDumpFile
:= e_CreateResource(LogDirs
, NETDUMP_FILENAME
+ '_client');
2322 procedure g_Net_DumpSendBuffer();
2324 NetDumpFile
.WriteDWordLE(gTime
);
2325 NetDumpFile
.WriteDWordLE(NetOut
.CurSize
);
2326 NetDumpFile
.WriteByte(1);
2327 NetDumpFile
.WriteBuffer(NetOut
.Data
^, NetOut
.CurSize
);
2330 procedure g_Net_DumpRecvBuffer(Buf
: penet_uint8
; Len
: LongWord
);
2332 if (Buf
= nil) or (Len
= 0) then Exit
;
2333 NetDumpFile
.WriteDWordLE(gTime
);
2334 NetDumpFile
.WriteDWordLE(Len
);
2335 NetDumpFile
.WriteByte(0);
2336 NetDumpFile
.WriteBuffer(Buf
^, Len
);
2339 procedure g_Net_DumpEnd();
2341 FreeAndNil(NetDumpFile
);
2344 // FIXME: Are calls to conwritefln() thread-safe?
2345 function g_Net_ForwardPorts(ForwardPongPort
: Boolean): Boolean;
2346 {$IFDEF USE_MINIUPNPC}
2351 LanAddr
: array [0..255] of Char;
2352 StrPort
: AnsiString
;
2356 if NetHost
= nil then
2359 if NetForwardedPort
= NetHost
.address
.port
then
2362 NetPingPortForwarded
:= False;
2363 NetForwardedPort
:= 0;
2365 DevList
:= upnpDiscover(1000, nil, nil, 0, 0, 2, Addr(Err
));
2366 if DevList
= nil then
2368 conwritefln('port forwarding failed: upnpDiscover() failed: %d', [Err
]);
2372 I
:= UPNP_GetValidIGD(DevList
, @Urls
, @Data
, Addr(LanAddr
[0]), 256);
2375 conwriteln('port forwarding failed: could not find an IGD device on this LAN');
2376 FreeUPNPDevList(DevList
);
2377 FreeUPNPUrls(@Urls
);
2381 StrPort
:= IntToStr(NetHost
.address
.port
);
2382 I
:= UPNP_AddPortMapping(
2383 Urls
.controlURL
, Addr(data
.first
.servicetype
[1]),
2384 PChar(StrPort
), PChar(StrPort
), Addr(LanAddr
[0]), PChar('D2DF'),
2385 PChar('UDP'), nil, PChar('0')
2390 conwritefln('forwarding port %d failed: error %d', [NetHost
.address
.port
, I
]);
2391 FreeUPNPDevList(DevList
);
2392 FreeUPNPUrls(@Urls
);
2396 if ForwardPongPort
then
2398 StrPort
:= IntToStr(NET_PING_PORT
);
2399 I
:= UPNP_AddPortMapping(
2400 Urls
.controlURL
, Addr(data
.first
.servicetype
[1]),
2401 PChar(StrPort
), PChar(StrPort
), Addr(LanAddr
[0]), PChar('D2DF'),
2402 PChar('UDP'), nil, PChar('0')
2405 NetPingPortForwarded
:= I
= 0;
2406 if NetPingPortForwarded
2407 then conwritefln('forwarded ping port %d successfully', [NET_PING_PORT
])
2408 else conwritefln('forwarding ping port %d failed: error %d', [NET_PING_PORT
, I
]);
2411 conwritefln('forwarded port %d successfully', [NetHost
.address
.port
]);
2412 NetIGDControl
:= AnsiString(Urls
.controlURL
);
2413 NetIGDService
:= data
.first
.servicetype
;
2414 NetForwardedPort
:= NetHost
.address
.port
;
2416 FreeUPNPDevList(DevList
);
2417 FreeUPNPUrls(@Urls
);
2426 procedure g_Net_UnforwardPorts();
2427 {$IFDEF USE_MINIUPNPC}
2430 StrPort
: AnsiString
;
2432 if NetForwardedPort
= 0 then
2435 conwriteln('unforwarding ports...');
2437 StrPort
:= IntToStr(NetForwardedPort
);
2438 I
:= UPNP_DeletePortMapping(PChar(NetIGDControl
), PChar(NetIGDService
), PChar(StrPort
),
2440 conwritefln(' port %d: %d', [NetForwardedPort
, I
]);
2442 if NetPingPortForwarded
then
2444 NetPingPortForwarded
:= False;
2445 StrPort
:= IntToStr(NET_PING_PORT
);
2446 I
:= UPNP_DeletePortMapping(PChar(NetIGDControl
), PChar(NetIGDService
), PChar(StrPort
),
2448 conwritefln(' ping port %d: %d', [NET_PING_PORT
, I
]);
2451 NetForwardedPort
:= 0;
2458 procedure NetServerCVars(P
: SSArray
);
2463 cmd
:= LowerCase(P
[0]);
2467 if (Length(P
) > 1) and (Length(P
[1]) > 0) then
2469 NetServerName
:= P
[1];
2470 if Length(NetServerName
) > 64 then
2471 SetLength(NetServerName
, 64);
2472 g_Net_Slist_ServerRenamed();
2474 g_Console_Add(cmd
+ ' "' + NetServerName
+ '"');
2478 if (Length(P
) > 1) and (Length(P
[1]) > 0) then
2480 NetPassword
:= P
[1];
2481 if Length(NetPassword
) > 24 then
2482 SetLength(NetPassword
, 24);
2483 g_Net_Slist_ServerRenamed();
2485 g_Console_Add(cmd
+ ' "' + AnsiLowerCase(NetPassword
) + '"');
2489 if (Length(P
) > 1) then
2491 NetMaxClients
:= nclamp(StrToIntDef(P
[1], NetMaxClients
), 1, NET_MAXCLIENTS
);
2492 if g_Game_IsServer
and g_Game_IsNet
then
2495 for a
:= 0 to High(NetClients
) do
2497 if NetClients
[a
].Used
then
2500 if b
> NetMaxClients
then
2502 s
:= g_Player_Get(NetClients
[a
].Player
).Name
;
2503 g_Net_Host_Kick(NetClients
[a
].ID
, NET_DISC_FULL
);
2504 g_Console_Add(Format(_lc
[I_PLAYER_KICK
], [s
]));
2505 MH_SEND_GameEvent(NET_EV_PLAYER_KICK
, 0, s
);
2509 g_Net_Slist_ServerRenamed();
2512 g_Console_Add(cmd
+ ' ' + IntToStr(NetMaxClients
));
2516 if (Length(P
) > 1) then
2518 NetUseMaster
:= StrToIntDef(P
[1], Byte(NetUseMaster
)) <> 0;
2519 if NetUseMaster
then g_Net_Slist_Public() else g_Net_Slist_Private();
2521 g_Console_Add(cmd
+ ' ' + IntToStr(Byte(NetUseMaster
)));
2525 if (Length(P
) > 1) then
2527 if not g_Game_IsNet
then
2528 NetPort
:= nclamp(StrToIntDef(P
[1], NetPort
), 0, $FFFF)
2530 g_Console_Add(_lc
[I_MSG_NOT_NETGAME
]);
2532 g_Console_Add(cmd
+ ' ' + IntToStr(Ord(NetPort
)));
2538 conRegVar('cl_downloadtimeout', @g_Net_DownloadTimeout
, 0.0, 1000000.0, '', 'timeout in seconds, 0 to disable it');
2539 conRegVar('cl_predictself', @NetPredictSelf
, '', 'predict local player');
2540 conRegVar('cl_forceplayerupdate', @NetForcePlayerUpdate
, '', 'update net players on NET_MSG_PLRPOS');
2541 conRegVar('cl_interp', @NetInterpLevel
, '', 'net player interpolation steps');
2542 conRegVar('cl_last_ip', @NetClientIP
, '', 'address of the last server you have connected to');
2543 conRegVar('cl_last_port', @NetClientPort
, '', 'port of the last server you have connected to');
2544 conRegVar('cl_deafen', @NetDeafLevel
, '', 'filter server messages (0-3)');
2546 conRegVar('sv_forwardports', @NetForwardPorts
, '', 'forward server port using miniupnpc (requires server restart)');
2547 conRegVar('sv_rcon', @NetAllowRCON
, '', 'enable remote console');
2548 conRegVar('sv_rcon_password', @NetRCONPassword
, '', 'remote console password');
2549 conRegVar('sv_update_interval', @NetUpdateRate
, '', 'unreliable update interval');
2550 conRegVar('sv_reliable_interval', @NetRelupdRate
, '', 'reliable update interval');
2551 conRegVar('sv_master_interval', @NetMasterRate
, '', 'master server update interval');
2553 conRegVar('sv_autoban_threshold', @NetAutoBanLimit
, '', 'max crimes before autoban (0 = no autoban)');
2554 conRegVar('sv_autoban_permanent', @NetAutoBanPerm
, '', 'whether autobans are permanent');
2555 conRegVar('sv_autoban_warn', @NetAutoBanWarn
, '', 'send warnings to the client when he triggers penalties');
2556 conRegVar('sv_autoban_packet_timeout', @NetAutoBanForTimeout
, '', 'autoban for packet timeouts');
2558 conRegVar('sv_auth_timeout', @NetAuthTimeout
, '', 'number of msec in which connecting clients must complete auth (0 = unlimited)');
2559 conRegVar('sv_packet_timeout', @NetPacketTimeout
, '', 'number of msec the client must idle to be kicked (0 = unlimited)');
2561 conRegVar('net_master_list', @NetMasterList
, '', 'list of master servers');
2563 SetLength(NetClients
, 0);
2564 g_Net_DownloadTimeout
:= 60;
2565 NetIn
.Alloc(NET_BUFSIZE
);
2566 NetOut
.Alloc(NET_BUFSIZE
);
2567 NetBuf
[NET_UNRELIABLE
].Alloc(NET_BUFSIZE
*2);
2568 NetBuf
[NET_RELIABLE
].Alloc(NET_BUFSIZE
*2);
2569 trans_omsg
.Alloc(NET_BUFSIZE
);
2574 NetBuf
[NET_UNRELIABLE
].Free();
2575 NetBuf
[NET_RELIABLE
].Free();