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, either version 3 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 {$INCLUDE ../shared/a_modes.inc}
22 e_log
, e_msg
, ENet
, Classes
, MAPDEF
{$IFDEF USE_MINIUPNPC}, miniupnpc
;{$ELSE};{$ENDIF}
25 NET_PROTOCOL_VER
= 180;
31 NET_CHAN_IMPORTANT
= 1;
34 NET_CHAN_PLAYERPOS
= 4;
36 NET_CHAN_MONSTERPOS
= 6;
37 NET_CHAN_LARGEDATA
= 7;
39 NET_CHAN_DOWNLOAD
= 9;
47 NET_PING_PORT
= $DF2D;
51 NET_DISC_NONE
: enet_uint32
= 0;
52 NET_DISC_PROTOCOL
: enet_uint32
= 1;
53 NET_DISC_VERSION
: enet_uint32
= 2;
54 NET_DISC_FULL
: enet_uint32
= 3;
55 NET_DISC_KICK
: enet_uint32
= 4;
56 NET_DISC_DOWN
: enet_uint32
= 5;
57 NET_DISC_PASSWORD
: enet_uint32
= 6;
58 NET_DISC_TEMPBAN
: enet_uint32
= 7;
59 NET_DISC_BAN
: enet_uint32
= 8;
60 NET_DISC_MAX
: enet_uint32
= 8;
66 NET_CONNECT_TIMEOUT
= 1000 * 10;
68 BANLIST_FILENAME
= 'banlist.txt';
69 NETDUMP_FILENAME
= 'netdump';
84 RequestedFullUpdate
: Boolean;
92 pTNetClient
= ^TNetClient
;
94 AByte
= array of Byte;
97 NetInitDone
: Boolean = False;
98 NetMode
: Byte = NET_NONE
;
99 NetDump
: Boolean = False;
101 NetServerName
: string = 'Unnamed Server';
102 NetPassword
: string = '';
103 NetPort
: Word = 25666;
105 NetAllowRCON
: Boolean = False;
106 NetRCONPassword
: string = '';
108 NetTimeToUpdate
: Cardinal = 0;
109 NetTimeToReliable
: Cardinal = 0;
110 NetTimeToMaster
: Cardinal = 0;
112 NetHost
: pENetHost
= nil;
113 NetPeer
: pENetPeer
= nil;
115 NetAddr
: ENetAddress
;
117 NetPongAddr
: ENetAddress
;
118 NetPongSock
: ENetSocket
= ENET_SOCKET_NULL
;
120 NetUseMaster
: Boolean = True;
121 NetSlistAddr
: ENetAddress
;
122 NetSlistIP
: string = 'mpms.doom2d.org';
123 NetSlistPort
: Word = 25665;
125 NetClientIP
: string = '127.0.0.1';
126 NetClientPort
: Word = 25666;
130 NetClients
: array of TNetClient
;
131 NetClientCount
: Byte = 0;
132 NetMaxClients
: Byte = 255;
133 NetBannedHosts
: array of TBanRecord
;
135 NetState
: Integer = NET_STATE_NONE
;
137 NetMyID
: Integer = -1;
138 NetPlrUID1
: Integer = -1;
139 NetPlrUID2
: Integer = -1;
141 NetInterpLevel
: Integer = 1;
142 NetUpdateRate
: Cardinal = 0; // as soon as possible
143 NetRelupdRate
: Cardinal = 18; // around two times a second
144 NetMasterRate
: Cardinal = 60000;
146 NetForcePlayerUpdate
: Boolean = False;
147 NetPredictSelf
: Boolean = True;
148 NetForwardPorts
: Boolean = False;
150 NetGotEverything
: Boolean = False;
151 NetGotKeys
: Boolean = False;
153 {$IFDEF USE_MINIUPNPC}
154 NetPortForwarded
: Word = 0;
155 NetPongForwarded
: Boolean = False;
156 NetIGDControl
: AnsiString;
157 NetIGDService
: TURLStr
;
160 NetPortThread
: TThreadID
= NilThreadId
;
162 NetDumpFile
: TStream
;
164 function g_Net_Init(): Boolean;
165 procedure g_Net_Cleanup();
166 procedure g_Net_Free();
167 procedure g_Net_Flush();
169 function g_Net_Host(IPAddr
: LongWord; Port
: enet_uint16
; MaxClients
: Cardinal = 16): Boolean;
170 procedure g_Net_Host_Die();
171 procedure g_Net_Host_Send(ID
: Integer; Reliable
: Boolean; Chan
: Byte = NET_CHAN_GAME
);
172 function g_Net_Host_Update(): enet_size_t
;
174 function g_Net_Connect(IP
: string; Port
: enet_uint16
): Boolean;
175 procedure g_Net_Disconnect(Forced
: Boolean = False);
176 procedure g_Net_Client_Send(Reliable
: Boolean; Chan
: Byte = NET_CHAN_GAME
);
177 function g_Net_Client_Update(): enet_size_t
;
178 function g_Net_Client_UpdateWhileLoading(): enet_size_t
;
180 function g_Net_Client_ByName(Name
: string): pTNetClient
;
181 function g_Net_Client_ByPlayer(PID
: Word): pTNetClient
;
182 function g_Net_ClientName_ByID(ID
: Integer): string;
184 procedure g_Net_SendData(Data
: AByte
; peer
: pENetPeer
; Reliable
: Boolean; Chan
: Byte = NET_CHAN_DOWNLOAD
);
185 function g_Net_Wait_Event(msgId
: Word): TMemoryStream
;
187 function IpToStr(IP
: LongWord): string;
188 function StrToIp(IPstr
: string; var IP
: LongWord): Boolean;
190 function g_Net_IsHostBanned(IP
: LongWord; Perm
: Boolean = False): Boolean;
191 procedure g_Net_BanHost(IP
: LongWord; Perm
: Boolean = True); overload
;
192 procedure g_Net_BanHost(IP
: string; Perm
: Boolean = True); overload
;
193 function g_Net_UnbanHost(IP
: string): Boolean; overload
;
194 function g_Net_UnbanHost(IP
: LongWord): Boolean; overload
;
195 procedure g_Net_UnbanNonPermHosts();
196 procedure g_Net_SaveBanList();
198 procedure g_Net_DumpStart();
199 procedure g_Net_DumpSendBuffer();
200 procedure g_Net_DumpRecvBuffer(Buf
: penet_uint8
; Len
: LongWord);
201 procedure g_Net_DumpEnd();
203 function g_Net_ForwardPorts(ForwardPongPort
: Boolean = True): Boolean;
204 procedure g_Net_UnforwardPorts();
210 e_input
, g_nethandler
, g_netmsg
, g_netmaster
, g_player
, g_window
, g_console
,
211 g_main
, g_game
, g_language
, g_weapons
, utils
, ctypes
;
214 g_Net_DownloadTimeout
: Single;
217 { /// SERVICE FUNCTIONS /// }
220 function g_Net_FindSlot(): Integer;
229 for I
:= Low(NetClients
) to High(NetClients
) do
231 if NetClients
[I
].Used
then
240 if C
>= NetMaxClients
then
248 if (Length(NetClients
) >= NetMaxClients
) then
252 SetLength(NetClients
, Length(NetClients
) + 1);
253 N
:= High(NetClients
);
259 NetClients
[N
].Used
:= True;
260 NetClients
[N
].ID
:= N
;
261 NetClients
[N
].RequestedFullUpdate
:= False;
262 NetClients
[N
].RCONAuth
:= False;
263 NetClients
[N
].Voted
:= False;
264 NetClients
[N
].Player
:= 0;
270 function g_Net_Init(): Boolean;
278 SetLength(NetClients
, 0);
284 NetAddr
.port
:= 25666;
285 SetLength(NetBannedHosts
, 0);
286 if FileExists(DataDir
+ BANLIST_FILENAME
) then
288 Assign(F
, DataDir
+ BANLIST_FILENAME
);
293 if StrToIp(IPstr
, IP
) then
300 Result
:= (enet_initialize() = 0);
303 procedure g_Net_Flush();
305 enet_host_flush(NetHost
);
308 procedure g_Net_Cleanup();
313 SetLength(NetClients
, 0);
323 NetState
:= NET_STATE_NONE
;
325 NetPongSock
:= ENET_SOCKET_NULL
;
327 NetTimeToMaster
:= 0;
328 NetTimeToUpdate
:= 0;
329 NetTimeToReliable
:= 0;
333 if NetPortThread
<> NilThreadId
then
334 WaitForThreadTerminate(NetPortThread
, 66666);
336 NetPortThread
:= NilThreadId
;
337 g_Net_UnforwardPorts();
343 procedure g_Net_Free();
348 NetInitDone
:= False;
352 { /// SERVER FUNCTIONS /// }
355 function ForwardThread(Param
: Pointer): PtrInt
;
358 if not g_Net_ForwardPorts() then Result
:= -1;
361 function g_Net_Host(IPAddr
: LongWord; Port
: enet_uint16
; MaxClients
: Cardinal = 16): Boolean;
363 if NetMode
<> NET_NONE
then
365 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_INGAME
]);
372 g_Console_Add(_lc
[I_NET_MSG
] + Format(_lc
[I_NET_MSG_HOST
], [Port
]));
373 if not NetInitDone
then
375 if (not g_Net_Init()) then
377 g_Console_Add(_lc
[I_NET_MSG_FERROR
] + _lc
[I_NET_ERR_ENET
]);
385 NetAddr
.host
:= IPAddr
;
386 NetAddr
.port
:= Port
;
388 if NetForwardPorts
then NetPortThread
:= BeginThread(ForwardThread
);
390 NetHost
:= enet_host_create(@NetAddr
, NET_MAXCLIENTS
, NET_CHANS
, 0, 0);
392 if (NetHost
= nil) then
394 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + Format(_lc
[I_NET_ERR_HOST
], [Port
]));
400 NetPongSock
:= enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM
);
401 if NetPongSock
<> ENET_SOCKET_NULL
then
403 NetPongAddr
.host
:= IPAddr
;
404 NetPongAddr
.port
:= NET_PING_PORT
;
405 if enet_socket_bind(NetPongSock
, @NetPongAddr
) < 0 then
407 enet_socket_destroy(NetPongSock
);
408 NetPongSock
:= ENET_SOCKET_NULL
;
411 enet_socket_set_option(NetPongSock
, ENET_SOCKOPT_NONBLOCK
, 1);
414 NetMode
:= NET_SERVER
;
421 procedure g_Net_Host_Die();
425 if NetMode
<> NET_SERVER
then Exit
;
427 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_HOST_DISCALL
]);
428 for I
:= 0 to High(NetClients
) do
429 if NetClients
[I
].Used
then
430 enet_peer_disconnect(NetClients
[I
].Peer
, NET_DISC_DOWN
);
432 while enet_host_service(NetHost
, @NetEvent
, 1000) > 0 do
433 if NetEvent
.kind
= ENET_EVENT_TYPE_RECEIVE
then
434 enet_packet_destroy(NetEvent
.packet
);
436 for I
:= 0 to High(NetClients
) do
437 if NetClients
[I
].Used
then
439 FreeMemory(NetClients
[I
].Peer
^.data
);
440 NetClients
[I
].Peer
^.data
:= nil;
441 enet_peer_reset(NetClients
[I
].Peer
);
442 NetClients
[I
].Peer
:= nil;
443 NetClients
[I
].Used
:= False;
446 if (NetMPeer
<> nil) and (NetMHost
<> nil) then g_Net_Slist_Disconnect
;
447 if NetPongSock
<> ENET_SOCKET_NULL
then
448 enet_socket_destroy(NetPongSock
);
450 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_HOST_DIE
]);
451 enet_host_destroy(NetHost
);
456 e_WriteLog('NET: Server stopped', TMsgType
.Notify
);
460 procedure g_Net_Host_Send(ID
: Integer; Reliable
: Boolean; Chan
: Byte = NET_CHAN_GAME
);
466 F
:= LongWord(ENET_PACKET_FLAG_RELIABLE
)
472 if ID
> High(NetClients
) then Exit
;
473 if NetClients
[ID
].Peer
= nil then Exit
;
475 P
:= enet_packet_create(NetOut
.Data
, NetOut
.CurSize
, F
);
476 if not Assigned(P
) then Exit
;
478 enet_peer_send(NetClients
[ID
].Peer
, Chan
, P
);
482 P
:= enet_packet_create(NetOut
.Data
, NetOut
.CurSize
, F
);
483 if not Assigned(P
) then Exit
;
485 enet_host_broadcast(NetHost
, Chan
, P
);
488 if NetDump
then g_Net_DumpSendBuffer();
493 procedure g_Net_Host_CheckPings();
499 Ping
: array [0..9] of Byte;
502 if NetPongSock
= ENET_SOCKET_NULL
then Exit
;
504 Buf
.data
:= Addr(Ping
[0]);
505 Buf
.dataLength
:= 2+8;
509 Len
:= enet_socket_receive(NetPongSock
, @ClAddr
, @Buf
, 1);
510 if Len
< 0 then Exit
;
512 if (Ping
[0] = Ord('D')) and (Ping
[1] = Ord('F')) then
514 ClTime
:= Int64(Addr(Ping
[2])^);
517 NetOut
.Write(Byte(Ord('D')));
518 NetOut
.Write(Byte(Ord('F')));
519 NetOut
.Write(NetPort
);
520 NetOut
.Write(ClTime
);
521 g_Net_Slist_WriteInfo();
523 if gPlayer1
<> nil then Inc(NPl
);
524 if gPlayer2
<> nil then Inc(NPl
);
526 NetOut
.Write(gNumBots
);
528 Buf
.data
:= NetOut
.Data
;
529 Buf
.dataLength
:= NetOut
.CurSize
;
530 enet_socket_send(NetPongSock
, @ClAddr
, @Buf
, 1);
536 function g_Net_Host_Update(): enet_size_t
;
549 g_Net_Host_CheckPings
;
551 while (enet_host_service(NetHost
, @NetEvent
, 0) > 0) do
553 case (NetEvent
.kind
) of
554 ENET_EVENT_TYPE_CONNECT
:
556 IP
:= IpToStr(NetEvent
.Peer
^.address
.host
);
557 Port
:= NetEvent
.Peer
^.address
.port
;
558 g_Console_Add(_lc
[I_NET_MSG
] +
559 Format(_lc
[I_NET_MSG_HOST_CONN
], [IP
, Port
]));
561 if (NetEvent
.data
<> NET_PROTOCOL_VER
) then
563 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_HOST_REJECT
] +
564 _lc
[I_NET_DISC_PROTOCOL
]);
565 NetEvent
.peer
^.data
:= GetMemory(SizeOf(Byte));
566 Byte(NetEvent
.peer
^.data
^) := 255;
567 enet_peer_disconnect(NetEvent
.peer
, NET_DISC_PROTOCOL
);
568 enet_host_flush(NetHost
);
572 ID
:= g_Net_FindSlot();
576 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_HOST_REJECT
] +
577 _lc
[I_NET_DISC_FULL
]);
578 NetEvent
.Peer
^.data
:= GetMemory(SizeOf(Byte));
579 Byte(NetEvent
.peer
^.data
^) := 255;
580 enet_peer_disconnect(NetEvent
.peer
, NET_DISC_FULL
);
581 enet_host_flush(NetHost
);
585 NetClients
[ID
].Peer
:= NetEvent
.peer
;
586 NetClients
[ID
].Peer
^.data
:= GetMemory(SizeOf(Byte));
587 Byte(NetClients
[ID
].Peer
^.data
^) := ID
;
588 NetClients
[ID
].State
:= NET_STATE_AUTH
;
589 NetClients
[ID
].RCONAuth
:= False;
591 enet_peer_timeout(NetEvent
.peer
, ENET_PEER_TIMEOUT_LIMIT
* 2, ENET_PEER_TIMEOUT_MINIMUM
* 2, ENET_PEER_TIMEOUT_MAXIMUM
* 2);
594 g_Console_Add(_lc
[I_NET_MSG
] + Format(_lc
[I_NET_MSG_HOST_ADD
], [ID
]));
597 ENET_EVENT_TYPE_RECEIVE
:
599 ID
:= Byte(NetEvent
.peer
^.data
^);
600 if ID
> High(NetClients
) then Exit
;
601 TC
:= @NetClients
[ID
];
603 if NetDump
then g_Net_DumpRecvBuffer(NetEvent
.packet
^.data
, NetEvent
.packet
^.dataLength
);
604 g_Net_HostMsgHandler(TC
, NetEvent
.packet
);
607 ENET_EVENT_TYPE_DISCONNECT
:
609 ID
:= Byte(NetEvent
.peer
^.data
^);
610 if ID
> High(NetClients
) then Exit
;
611 TC
:= @NetClients
[ID
];
612 if TC
= nil then Exit
;
614 if not (TC
^.Used
) then Exit
;
616 TP
:= g_Player_Get(TC
^.Player
);
621 TP
.Kill(K_SIMPLEKILL
, 0, HIT_DISCON
);
622 g_Console_Add(Format(_lc
[I_PLAYER_LEAVE
], [TP
.Name
]), True);
623 e_WriteLog('NET: Client ' + TP
.Name
+ ' [' + IntToStr(ID
) + '] disconnected.', TMsgType
.Notify
);
624 g_Player_Remove(TP
.UID
);
628 TC
^.State
:= NET_STATE_NONE
;
631 TC
^.RequestedFullUpdate
:= False;
633 FreeMemory(NetEvent
.peer
^.data
);
634 NetEvent
.peer
^.data
:= nil;
635 g_Console_Add(_lc
[I_NET_MSG
] + Format(_lc
[I_NET_MSG_HOST_DISC
], [ID
]));
638 if NetUseMaster
then g_Net_Slist_Update
;
645 { /// CLIENT FUNCTIONS /// }
648 procedure g_Net_Disconnect(Forced
: Boolean = False);
650 if NetMode
<> NET_CLIENT
then Exit
;
651 if (NetHost
= nil) or (NetPeer
= nil) then Exit
;
655 enet_peer_disconnect(NetPeer
, NET_DISC_NONE
);
657 while (enet_host_service(NetHost
, @NetEvent
, 1500) > 0) do
659 if (NetEvent
.kind
= ENET_EVENT_TYPE_DISCONNECT
) then
665 if (NetEvent
.kind
= ENET_EVENT_TYPE_RECEIVE
) then
666 enet_packet_destroy(NetEvent
.packet
);
669 if NetPeer
<> nil then
671 enet_peer_reset(NetPeer
);
677 e_WriteLog('NET: Kicked from server: ' + IntToStr(NetEvent
.data
), TMsgType
.Notify
);
678 if (NetEvent
.data
<= NET_DISC_MAX
) then
679 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_KICK
] +
680 _lc
[TStrings_Locale(Cardinal(I_NET_DISC_NONE
) + NetEvent
.data
)], True);
683 if NetHost
<> nil then
685 enet_host_destroy(NetHost
);
688 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_CLIENT_DISC
]);
691 e_WriteLog('NET: Disconnected', TMsgType
.Notify
);
694 procedure g_Net_Client_Send(Reliable
: Boolean; Chan
: Byte = NET_CHAN_GAME
);
700 F
:= LongWord(ENET_PACKET_FLAG_RELIABLE
)
704 P
:= enet_packet_create(NetOut
.Data
, NetOut
.CurSize
, F
);
705 if not Assigned(P
) then Exit
;
707 enet_peer_send(NetPeer
, Chan
, P
);
708 if NetDump
then g_Net_DumpSendBuffer();
713 function g_Net_Client_Update(): enet_size_t
;
716 while (enet_host_service(NetHost
, @NetEvent
, 0) > 0) do
718 case NetEvent
.kind
of
719 ENET_EVENT_TYPE_RECEIVE
:
721 if NetDump
then g_Net_DumpRecvBuffer(NetEvent
.packet
^.data
, NetEvent
.packet
^.dataLength
);
722 g_Net_ClientMsgHandler(NetEvent
.packet
);
725 ENET_EVENT_TYPE_DISCONNECT
:
727 g_Net_Disconnect(True);
735 function g_Net_Client_UpdateWhileLoading(): enet_size_t
;
738 while (enet_host_service(NetHost
, @NetEvent
, 0) > 0) do
740 case NetEvent
.kind
of
741 ENET_EVENT_TYPE_RECEIVE
:
743 if NetDump
then g_Net_DumpRecvBuffer(NetEvent
.packet
^.data
, NetEvent
.packet
^.dataLength
);
744 g_Net_ClientLightMsgHandler(NetEvent
.packet
);
747 ENET_EVENT_TYPE_DISCONNECT
:
749 g_Net_Disconnect(True);
758 function g_Net_Connect(IP
: string; Port
: enet_uint16
): Boolean;
761 TimeoutTime
, T
: Int64;
763 if NetMode
<> NET_NONE
then
765 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_ERR_INGAME
], True);
772 g_Console_Add(_lc
[I_NET_MSG
] + Format(_lc
[I_NET_MSG_CLIENT_CONN
],
774 if not NetInitDone
then
776 if (not g_Net_Init()) then
778 g_Console_Add(_lc
[I_NET_MSG_FERROR
] + _lc
[I_NET_ERR_ENET
], True);
786 NetHost
:= enet_host_create(nil, 1, NET_CHANS
, 0, 0);
788 if (NetHost
= nil) then
790 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CLIENT
], True);
796 enet_address_set_host(@NetAddr
, PChar(Addr(IP
[1])));
797 NetAddr
.port
:= Port
;
799 NetPeer
:= enet_host_connect(NetHost
, @NetAddr
, NET_CHANS
, NET_PROTOCOL_VER
);
801 if (NetPeer
= nil) then
803 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CLIENT
], True);
804 enet_host_destroy(NetHost
);
810 // предупредить что ждем слишком долго через N секунд
811 TimeoutTime
:= GetTimer() + NET_CONNECT_TIMEOUT
;
816 while (enet_host_service(NetHost
, @NetEvent
, 0) > 0) do
818 if (NetEvent
.kind
= ENET_EVENT_TYPE_CONNECT
) then
820 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_MSG_CLIENT_DONE
]);
821 NetMode
:= NET_CLIENT
;
823 enet_peer_timeout(NetPeer
, ENET_PEER_TIMEOUT_LIMIT
* 2, ENET_PEER_TIMEOUT_MINIMUM
* 2, ENET_PEER_TIMEOUT_MAXIMUM
* 2);
825 NetClientPort
:= Port
;
833 if T
> TimeoutTime
then
835 TimeoutTime
:= T
+ NET_CONNECT_TIMEOUT
* 100; // одного предупреждения хватит
836 g_Console_Add(_lc
[I_NET_MSG_TIMEOUT_WARN
], True);
837 g_Console_Add(Format(_lc
[I_NET_MSG_PORTS
], [Integer(Port
), Integer(NET_PING_PORT
)]), True);
840 ProcessLoading(true);
842 if e_KeyPressed(IK_SPACE
) or e_KeyPressed(IK_ESCAPE
) or e_KeyPressed(VK_ESCAPE
) or
843 e_KeyPressed(JOY0_JUMP
) or e_KeyPressed(JOY1_JUMP
) or e_KeyPressed(JOY2_JUMP
) or e_KeyPressed(JOY3_JUMP
) then
847 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_TIMEOUT
], True);
848 g_Console_Add(Format(_lc
[I_NET_MSG_PORTS
], [Integer(Port
), Integer(NET_PING_PORT
)]), True);
849 if NetPeer
<> nil then enet_peer_reset(NetPeer
);
850 if NetHost
<> nil then
852 enet_host_destroy(NetHost
);
859 function IpToStr(IP
: LongWord): string;
864 Result
:= IntToStr(PByte(Ptr
+ 0)^) + '.';
865 Result
:= Result
+ IntToStr(PByte(Ptr
+ 1)^) + '.';
866 Result
:= Result
+ IntToStr(PByte(Ptr
+ 2)^) + '.';
867 Result
:= Result
+ IntToStr(PByte(Ptr
+ 3)^);
870 function StrToIp(IPstr
: string; var IP
: LongWord): Boolean;
874 Result
:= enet_address_set_host(@EAddr
, PChar(@IPstr
[1])) = 0;
878 function g_Net_Client_ByName(Name
: string): pTNetClient
;
884 for a
:= Low(NetClients
) to High(NetClients
) do
885 if (NetClients
[a
].Used
) and (NetClients
[a
].State
= NET_STATE_GAME
) then
887 pl
:= g_Player_Get(NetClients
[a
].Player
);
888 if pl
= nil then continue
;
889 if Copy(LowerCase(pl
.Name
), 1, Length(Name
)) <> LowerCase(Name
) then continue
;
890 if NetClients
[a
].Peer
<> nil then
892 Result
:= @NetClients
[a
];
898 function g_Net_Client_ByPlayer(PID
: Word): pTNetClient
;
903 for a
:= Low(NetClients
) to High(NetClients
) do
904 if (NetClients
[a
].Used
) and (NetClients
[a
].State
= NET_STATE_GAME
) then
905 if NetClients
[a
].Player
= PID
then
907 Result
:= @NetClients
[a
];
912 function g_Net_ClientName_ByID(ID
: Integer): string;
918 if ID
= NET_EVERYONE
then
920 for a
:= Low(NetClients
) to High(NetClients
) do
921 if (NetClients
[a
].ID
= ID
) and (NetClients
[a
].Used
) and (NetClients
[a
].State
= NET_STATE_GAME
) then
923 pl
:= g_Player_Get(NetClients
[a
].Player
);
924 if pl
= nil then Exit
;
929 procedure g_Net_SendData(Data
: AByte
; peer
: pENetPeer
; Reliable
: Boolean; Chan
: Byte = NET_CHAN_DOWNLOAD
);
933 dataLength
: Cardinal;
935 dataLength
:= Length(Data
);
938 F
:= LongWord(ENET_PACKET_FLAG_RELIABLE
)
942 if (peer
<> nil) then
944 P
:= enet_packet_create(@Data
[0], dataLength
, F
);
945 if not Assigned(P
) then Exit
;
946 enet_peer_send(peer
, Chan
, P
);
950 P
:= enet_packet_create(@Data
[0], dataLength
, F
);
951 if not Assigned(P
) then Exit
;
952 enet_host_broadcast(NetHost
, Chan
, P
);
955 enet_host_flush(NetHost
);
958 function UserRequestExit
: Boolean;
960 Result
:= e_KeyPressed(IK_SPACE
) or
961 e_KeyPressed(IK_ESCAPE
) or
962 e_KeyPressed(VK_ESCAPE
) or
963 e_KeyPressed(JOY0_JUMP
) or
964 e_KeyPressed(JOY1_JUMP
) or
965 e_KeyPressed(JOY2_JUMP
) or
966 e_KeyPressed(JOY3_JUMP
)
969 function g_Net_Wait_Event(msgId
: Word): TMemoryStream
;
974 stream
: TMemoryStream
;
977 FillChar(ev
, SizeOf(ev
), 0);
980 status
:= enet_host_service(NetHost
, @ev
, Trunc(g_Net_DownloadTimeout
* 1000));
984 ENET_EVENT_TYPE_RECEIVE
:
986 Ptr
:= ev
.packet
^.data
;
987 rMsgId
:= Byte(Ptr
^);
988 if rMsgId
= msgId
then
990 stream
:= TMemoryStream
.Create
;
991 stream
.SetSize(ev
.packet
^.dataLength
);
992 stream
.WriteBuffer(Ptr
^, ev
.packet
^.dataLength
);
993 stream
.Seek(0, soFromBeginning
);
994 status
:= 1 (* received *)
998 (* looks that game state always received, so ignore it *)
999 e_LogWritefln('g_Net_Wait_Event(%s): skip message %s', [msgId
, rMsgId
]);
1000 status
:= 2 (* continue *)
1003 ENET_EVENT_TYPE_DISCONNECT
:
1005 if (ev
.data
<= NET_DISC_MAX
) then
1006 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' ' + _lc
[TStrings_Locale(Cardinal(I_NET_DISC_NONE
) + ev
.data
)], True);
1007 status
:= -2 (* error: disconnected *)
1010 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' unknown ENet event ' + IntToStr(Ord(ev
.kind
)), True);
1011 status
:= -3 (* error: unknown event *)
1013 enet_packet_destroy(ev
.packet
)
1017 g_Console_Add(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CONN
] + ' timeout reached', True);
1018 status
:= 0 (* error: timeout *)
1020 ProcessLoading(true);
1021 until (status
<> 2) or UserRequestExit();
1025 function g_Net_IsHostBanned(IP
: LongWord; Perm
: Boolean = False): Boolean;
1030 if NetBannedHosts
= nil then
1032 for I
:= 0 to High(NetBannedHosts
) do
1033 if (NetBannedHosts
[I
].IP
= IP
) and ((not Perm
) or (NetBannedHosts
[I
].Perm
)) then
1040 procedure g_Net_BanHost(IP
: LongWord; Perm
: Boolean = True); overload
;
1046 if g_Net_IsHostBanned(IP
, Perm
) then
1050 for I
:= Low(NetBannedHosts
) to High(NetBannedHosts
) do
1051 if NetBannedHosts
[I
].IP
= 0 then
1059 SetLength(NetBannedHosts
, Length(NetBannedHosts
) + 1);
1060 P
:= High(NetBannedHosts
);
1063 NetBannedHosts
[P
].IP
:= IP
;
1064 NetBannedHosts
[P
].Perm
:= Perm
;
1067 procedure g_Net_BanHost(IP
: string; Perm
: Boolean = True); overload
;
1072 b
:= StrToIp(IP
, a
);
1074 g_Net_BanHost(a
, Perm
);
1077 procedure g_Net_UnbanNonPermHosts();
1081 if NetBannedHosts
= nil then
1083 for I
:= Low(NetBannedHosts
) to High(NetBannedHosts
) do
1084 if (NetBannedHosts
[I
].IP
> 0) and not NetBannedHosts
[I
].Perm
then
1086 NetBannedHosts
[I
].IP
:= 0;
1087 NetBannedHosts
[I
].Perm
:= True;
1091 function g_Net_UnbanHost(IP
: string): Boolean; overload
;
1095 Result
:= StrToIp(IP
, a
);
1097 Result
:= g_Net_UnbanHost(a
);
1100 function g_Net_UnbanHost(IP
: LongWord): Boolean; overload
;
1107 if NetBannedHosts
= nil then
1109 for I
:= 0 to High(NetBannedHosts
) do
1110 if NetBannedHosts
[I
].IP
= IP
then
1112 NetBannedHosts
[I
].IP
:= 0;
1113 NetBannedHosts
[I
].Perm
:= True;
1115 // no break here to clear all bans of this host, perm and non-perm
1119 procedure g_Net_SaveBanList();
1124 Assign(F
, DataDir
+ BANLIST_FILENAME
);
1126 if NetBannedHosts
<> nil then
1127 for I
:= 0 to High(NetBannedHosts
) do
1128 if NetBannedHosts
[I
].Perm
and (NetBannedHosts
[I
].IP
> 0) then
1129 Writeln(F
, IpToStr(NetBannedHosts
[I
].IP
));
1133 procedure g_Net_DumpStart();
1135 if NetMode
= NET_SERVER
then
1136 NetDumpFile
:= createDiskFile(NETDUMP_FILENAME
+ '_server')
1138 NetDumpFile
:= createDiskFile(NETDUMP_FILENAME
+ '_client');
1141 procedure g_Net_DumpSendBuffer();
1143 writeInt(NetDumpFile
, gTime
);
1144 writeInt(NetDumpFile
, LongWord(NetOut
.CurSize
));
1145 writeInt(NetDumpFile
, Byte(1));
1146 NetDumpFile
.WriteBuffer(NetOut
.Data
^, NetOut
.CurSize
);
1149 procedure g_Net_DumpRecvBuffer(Buf
: penet_uint8
; Len
: LongWord);
1151 if (Buf
= nil) or (Len
= 0) then Exit
;
1152 writeInt(NetDumpFile
, gTime
);
1153 writeInt(NetDumpFile
, Len
);
1154 writeInt(NetDumpFile
, Byte(0));
1155 NetDumpFile
.WriteBuffer(Buf
^, Len
);
1158 procedure g_Net_DumpEnd();
1164 function g_Net_ForwardPorts(ForwardPongPort
: Boolean = True): Boolean;
1165 {$IFDEF USE_MINIUPNPC}
1170 LanAddr
: array [0..255] of Char;
1171 StrPort
: AnsiString;
1176 if NetPortForwarded
= NetPort
then
1182 NetPongForwarded
:= False;
1183 NetPortForwarded
:= 0;
1185 DevList
:= upnpDiscover(1000, nil, nil, 0, 0, 2, Addr(Err
));
1186 if DevList
= nil then
1188 conwritefln('port forwarding failed: upnpDiscover() failed: %d', [Err
]);
1192 I
:= UPNP_GetValidIGD(DevList
, @Urls
, @Data
, Addr(LanAddr
[0]), 256);
1196 conwriteln('port forwarding failed: could not find an IGD device on this LAN');
1197 FreeUPNPDevList(DevList
);
1198 FreeUPNPUrls(@Urls
);
1202 StrPort
:= IntToStr(NetPort
);
1203 I
:= UPNP_AddPortMapping(
1204 Urls
.controlURL
, Addr(data
.first
.servicetype
[1]),
1205 PChar(StrPort
), PChar(StrPort
), Addr(LanAddr
[0]), PChar('D2DF'),
1206 PChar('UDP'), nil, PChar('0')
1211 conwritefln('forwarding port %d failed: error %d', [NetPort
, I
]);
1212 FreeUPNPDevList(DevList
);
1213 FreeUPNPUrls(@Urls
);
1217 if ForwardPongPort
then
1219 StrPort
:= IntToStr(NET_PING_PORT
);
1220 I
:= UPNP_AddPortMapping(
1221 Urls
.controlURL
, Addr(data
.first
.servicetype
[1]),
1222 PChar(StrPort
), PChar(StrPort
), Addr(LanAddr
[0]), PChar('D2DF'),
1223 PChar('UDP'), nil, PChar('0')
1228 conwritefln('forwarding port %d failed: error %d', [NetPort
+ 1, I
]);
1229 NetPongForwarded
:= False;
1233 conwritefln('forwarded port %d successfully', [NetPort
+ 1]);
1234 NetPongForwarded
:= True;
1238 conwritefln('forwarded port %d successfully', [NetPort
]);
1239 NetIGDControl
:= AnsiString(Urls
.controlURL
);
1240 NetIGDService
:= data
.first
.servicetype
;
1241 NetPortForwarded
:= NetPort
;
1243 FreeUPNPDevList(DevList
);
1244 FreeUPNPUrls(@Urls
);
1253 procedure g_Net_UnforwardPorts();
1254 {$IFDEF USE_MINIUPNPC}
1257 StrPort
: AnsiString;
1259 if NetPortForwarded
= 0 then Exit
;
1261 conwriteln('unforwarding ports...');
1263 StrPort
:= IntToStr(NetPortForwarded
);
1264 I
:= UPNP_DeletePortMapping(
1265 PChar(NetIGDControl
), Addr(NetIGDService
[1]),
1266 PChar(StrPort
), PChar('UDP'), nil
1268 conwritefln(' port %d: %d', [NetPortForwarded
, I
]);
1270 if NetPongForwarded
then
1272 NetPongForwarded
:= False;
1273 StrPort
:= IntToStr(NetPortForwarded
+ 1);
1274 I
:= UPNP_DeletePortMapping(
1275 PChar(NetIGDControl
), Addr(NetIGDService
[1]),
1276 PChar(StrPort
), PChar('UDP'), nil
1278 conwritefln(' port %d: %d', [NetPortForwarded
+ 1, I
]);
1281 NetPortForwarded
:= 0;
1289 conRegVar('cl_downloadtimeout', @g_Net_DownloadTimeout
, 0.0, 1000000.0, '', 'timeout in seconds, 0 to disable it');
1290 g_Net_DownloadTimeout
:= 60;
1291 NetIn
.Alloc(NET_BUFSIZE
);
1292 NetOut
.Alloc(NET_BUFSIZE
);