diff --git a/src/game/g_net.pas b/src/game/g_net.pas
index 12194483511c729b73f6feaa1d6280cb12d8485d..0f3df0f81f384348afba14eeaa326f3ce4ff6f42 100644 (file)
--- a/src/game/g_net.pas
+++ b/src/game/g_net.pas
+(* Copyright (C) Doom 2D: Forever Developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *)
+{$INCLUDE ../shared/a_modes.inc}
unit g_net;
interface
uses
- e_log, e_fixedbuffer, ENet, ENetTypes, ENetPlatform, Classes;
+ e_log, e_msg, ENet, Classes, MAPDEF{$IFDEF USE_MINIUPNPC}, miniupnpc;{$ELSE};{$ENDIF}
const
- NET_PROTOCOL_VER = 164;
+ NET_PROTOCOL_VER = 180;
NET_MAXCLIENTS = 24;
NET_CHANS = 11;
NET_SERVER = 1;
NET_CLIENT = 2;
- NET_BUFSIZE = 65536;
+ NET_BUFSIZE = $FFFF;
+ NET_PING_PORT = $DF2D;
NET_EVERYONE = -1;
NET_STATE_GAME = 2;
BANLIST_FILENAME = 'banlist.txt';
+ NETDUMP_FILENAME = 'netdump';
+
+ {$IFDEF FREEBSD}
+ NilThreadId = nil;
+ {$ELSE}
+ NilThreadId = 0;
+ {$ENDIF}
type
TNetClient = record
var
NetInitDone: Boolean = False;
NetMode: Byte = NET_NONE;
+ NetDump: Boolean = False;
NetServerName: string = 'Unnamed Server';
NetPassword: string = '';
NetClientIP: string = '127.0.0.1';
NetClientPort: Word = 25666;
- NetIn, NetOut: TBuffer;
+ NetIn, NetOut: TMsg;
NetClients: array of TNetClient;
NetClientCount: Byte = 0;
NetForcePlayerUpdate: Boolean = False;
NetPredictSelf: Boolean = True;
- NetGotKeys: Boolean = False;
+ NetForwardPorts: Boolean = False;
NetGotEverything: Boolean = False;
+ NetGotKeys: Boolean = False;
+
+{$IFDEF USE_MINIUPNPC}
+ NetPortForwarded: Word = 0;
+ NetPongForwarded: Boolean = False;
+ NetIGDControl: AnsiString;
+ NetIGDService: TURLStr;
+{$ENDIF}
+
+ NetPortThread: TThreadID = NilThreadId;
+
+ NetDumpFile: TStream;
function g_Net_Init(): Boolean;
procedure g_Net_Cleanup();
function g_Net_Client_ByPlayer(PID: Word): pTNetClient;
function g_Net_ClientName_ByID(ID: Integer): string;
-procedure g_Net_SendData(Data:AByte; peer: pENetPeer; Reliable: Boolean; Chan: Byte = NET_CHAN_DOWNLOAD);
+procedure g_Net_SendData(Data: AByte; peer: pENetPeer; Reliable: Boolean; Chan: Byte = NET_CHAN_DOWNLOAD);
function g_Net_Wait_Event(msgId: Word): TMemoryStream;
function IpToStr(IP: LongWord): string;
procedure g_Net_UnbanNonPermHosts();
procedure g_Net_SaveBanList();
+procedure g_Net_DumpStart();
+procedure g_Net_DumpSendBuffer();
+procedure g_Net_DumpRecvBuffer(Buf: penet_uint8; Len: LongWord);
+procedure g_Net_DumpEnd();
+
+function g_Net_ForwardPorts(ForwardPongPort: Boolean = True): Boolean;
+procedure g_Net_UnforwardPorts();
+
implementation
uses
SysUtils,
e_input, g_nethandler, g_netmsg, g_netmaster, g_player, g_window, g_console,
- g_main, g_game, g_language, g_weapons;
+ g_main, g_game, g_language, g_weapons, utils, ctypes;
+
+var
+ g_Net_DownloadTimeout: Single;
{ /// SERVICE FUNCTIONS /// }
IPstr: string;
IP: LongWord;
begin
- e_Buffer_Clear(@NetIn);
- e_Buffer_Clear(@NetOut);
+ NetIn.Clear();
+ NetOut.Clear();
SetLength(NetClients, 0);
NetPeer := nil;
NetHost := nil;
procedure g_Net_Cleanup();
begin
- e_Buffer_Clear(@NetIn);
- e_Buffer_Clear(@NetOut);
+ NetIn.Clear();
+ NetOut.Clear();
SetLength(NetClients, 0);
NetClientCount := 0;
NetTimeToReliable := 0;
NetMode := NET_NONE;
+
+ if NetPortThread <> NilThreadId then
+ WaitForThreadTerminate(NetPortThread, 66666);
+
+ NetPortThread := NilThreadId;
+ g_Net_UnforwardPorts();
+
+ if NetDump then
+ g_Net_DumpEnd();
end;
procedure g_Net_Free();
{ /// SERVER FUNCTIONS /// }
+function ForwardThread(Param: Pointer): PtrInt;
+begin
+ Result := 0;
+ if not g_Net_ForwardPorts() then Result := -1;
+end;
+
function g_Net_Host(IPAddr: LongWord; Port: enet_uint16; MaxClients: Cardinal = 16): Boolean;
begin
if NetMode <> NET_NONE then
NetAddr.host := IPAddr;
NetAddr.port := Port;
+ if NetForwardPorts then NetPortThread := BeginThread(ForwardThread);
+
NetHost := enet_host_create(@NetAddr, NET_MAXCLIENTS, NET_CHANS, 0, 0);
if (NetHost = nil) then
if NetPongSock <> ENET_SOCKET_NULL then
begin
NetPongAddr.host := IPAddr;
- NetPongAddr.port := Port + 1;
+ NetPongAddr.port := NET_PING_PORT;
if enet_socket_bind(NetPongSock, @NetPongAddr) < 0 then
begin
enet_socket_destroy(NetPongSock);
end;
NetMode := NET_SERVER;
- e_Buffer_Clear(@NetOut);
+ NetOut.Clear();
+
+ if NetDump then
+ g_Net_DumpStart();
end;
procedure g_Net_Host_Die();
NetMode := NET_NONE;
g_Net_Cleanup;
- e_WriteLog('NET: Server stopped', MSG_NOTIFY);
+ e_WriteLog('NET: Server stopped', TMsgType.Notify);
end;
if ID > High(NetClients) then Exit;
if NetClients[ID].Peer = nil then Exit;
- P := enet_packet_create(Addr(NetOut.Data), NetOut.Len, F);
+ P := enet_packet_create(NetOut.Data, NetOut.CurSize, F);
if not Assigned(P) then Exit;
enet_peer_send(NetClients[ID].Peer, Chan, P);
end
else
begin
- P := enet_packet_create(Addr(NetOut.Data), NetOut.Len, F);
+ P := enet_packet_create(NetOut.Data, NetOut.CurSize, F);
if not Assigned(P) then Exit;
- enet_host_widecast(NetHost, Chan, P);
+ enet_host_broadcast(NetHost, Chan, P);
end;
+ if NetDump then g_Net_DumpSendBuffer();
g_Net_Flush();
- e_Buffer_Clear(@NetOut);
+ NetOut.Clear();
end;
procedure g_Net_Host_CheckPings();
var
ClAddr: ENetAddress;
Buf: ENetBuffer;
- Len, ClTime: Integer;
- Ping: array [0..5] of Byte;
+ Len: Integer;
+ ClTime: Int64;
+ Ping: array [0..9] of Byte;
NPl: Byte;
begin
if NetPongSock = ENET_SOCKET_NULL then Exit;
Buf.data := Addr(Ping[0]);
- Buf.dataLength := 6;
+ Buf.dataLength := 2+8;
Ping[0] := 0;
if (Ping[0] = Ord('D')) and (Ping[1] = Ord('F')) then
begin
- ClTime := Integer(Addr(Ping[2])^);
+ ClTime := Int64(Addr(Ping[2])^);
- e_Buffer_Clear(@NetOut);
- e_Buffer_Write(@NetOut, Byte(Ord('D')));
- e_Buffer_Write(@NetOut, Byte(Ord('F')));
- e_Buffer_Write(@NetOut, ClTime);
+ NetOut.Clear();
+ NetOut.Write(Byte(Ord('D')));
+ NetOut.Write(Byte(Ord('F')));
+ NetOut.Write(NetPort);
+ NetOut.Write(ClTime);
g_Net_Slist_WriteInfo();
NPl := 0;
if gPlayer1 <> nil then Inc(NPl);
if gPlayer2 <> nil then Inc(NPl);
- e_Buffer_Write(@NetOut, NPl);
- e_Buffer_Write(@NetOut, gNumBots);
+ NetOut.Write(NPl);
+ NetOut.Write(gNumBots);
- Buf.data := Addr(NetOut.Data[0]);
- Buf.dataLength := NetOut.WritePos;
+ Buf.data := NetOut.Data;
+ Buf.dataLength := NetOut.CurSize;
enet_socket_send(NetPongSock, @ClAddr, @Buf, 1);
- e_Buffer_Clear(@NetOut);
+ NetOut.Clear();
end;
end;
Result := 0;
if NetUseMaster then
- begin
g_Net_Slist_Check;
- g_Net_Host_CheckPings;
- end;
+ g_Net_Host_CheckPings;
while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
begin
if ID > High(NetClients) then Exit;
TC := @NetClients[ID];
+ if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
g_Net_HostMsgHandler(TC, NetEvent.packet);
end;
TP.Lives := 0;
TP.Kill(K_SIMPLEKILL, 0, HIT_DISCON);
g_Console_Add(Format(_lc[I_PLAYER_LEAVE], [TP.Name]), True);
- e_WriteLog('NET: Client ' + TP.Name + ' [' + IntToStr(ID) + '] disconnected.', MSG_NOTIFY);
+ e_WriteLog('NET: Client ' + TP.Name + ' [' + IntToStr(ID) + '] disconnected.', TMsgType.Notify);
g_Player_Remove(TP.UID);
end;
end
else
begin
- e_WriteLog('NET: Kicked from server: ' + IntToStr(NetEvent.data), MSG_NOTIFY);
+ e_WriteLog('NET: Kicked from server: ' + IntToStr(NetEvent.data), TMsgType.Notify);
if (NetEvent.data <= NET_DISC_MAX) then
g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_KICK] +
_lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + NetEvent.data)], True);
g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_CLIENT_DISC]);
g_Net_Cleanup;
- e_WriteLog('NET: Disconnected', MSG_NOTIFY);
+ e_WriteLog('NET: Disconnected', TMsgType.Notify);
end;
procedure g_Net_Client_Send(Reliable: Boolean; Chan: Byte = NET_CHAN_GAME);
else
F := 0;
- P := enet_packet_create(Addr(NetOut.Data), NetOut.Len, F);
+ P := enet_packet_create(NetOut.Data, NetOut.CurSize, F);
if not Assigned(P) then Exit;
enet_peer_send(NetPeer, Chan, P);
+ if NetDump then g_Net_DumpSendBuffer();
g_Net_Flush();
- e_Buffer_Clear(@NetOut);
+ NetOut.Clear();
end;
function g_Net_Client_Update(): enet_size_t;
begin
case NetEvent.kind of
ENET_EVENT_TYPE_RECEIVE:
+ begin
+ if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
g_Net_ClientMsgHandler(NetEvent.packet);
+ end;
ENET_EVENT_TYPE_DISCONNECT:
begin
begin
case NetEvent.kind of
ENET_EVENT_TYPE_RECEIVE:
+ begin
+ if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
g_Net_ClientLightMsgHandler(NetEvent.packet);
+ end;
ENET_EVENT_TYPE_DISCONNECT:
begin
begin
g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_CLIENT_DONE]);
NetMode := NET_CLIENT;
- e_Buffer_Clear(@NetOut);
+ NetOut.Clear();
enet_peer_timeout(NetPeer, ENET_PEER_TIMEOUT_LIMIT * 2, ENET_PEER_TIMEOUT_MINIMUM * 2, ENET_PEER_TIMEOUT_MAXIMUM * 2);
NetClientIP := IP;
NetClientPort := Port;
+ if NetDump then
+ g_Net_DumpStart();
Exit;
end;
end;
- ProcessLoading();
+ ProcessLoading(true);
- e_PollInput();
-
- if e_KeyPressed(IK_ESCAPE) or e_KeyPressed(IK_SPACE) then
+ if e_KeyPressed(IK_SPACE) or e_KeyPressed(IK_ESCAPE) or e_KeyPressed(VK_ESCAPE) or
+ e_KeyPressed(JOY0_JUMP) or e_KeyPressed(JOY1_JUMP) or e_KeyPressed(JOY2_JUMP) or e_KeyPressed(JOY3_JUMP) then
OuterLoop := False;
end;
var
Ptr: Pointer;
begin
- Result := '';
Ptr := Addr(IP);
-
- e_Raw_Seek(0);
- Result := Result + IntToStr(e_Raw_Read_Byte(Ptr)) + '.';
- Result := Result + IntToStr(e_Raw_Read_Byte(Ptr)) + '.';
- Result := Result + IntToStr(e_Raw_Read_Byte(Ptr)) + '.';
- Result := Result + IntToStr(e_Raw_Read_Byte(Ptr));
- e_Raw_Seek(0);
+ Result := IntToStr(PByte(Ptr + 0)^) + '.';
+ Result := Result + IntToStr(PByte(Ptr + 1)^) + '.';
+ Result := Result + IntToStr(PByte(Ptr + 2)^) + '.';
+ Result := Result + IntToStr(PByte(Ptr + 3)^);
end;
function StrToIp(IPstr: string; var IP: LongWord): Boolean;
end;
end;
-procedure g_Net_SendData(Data:AByte; peer: pENetPeer; Reliable: Boolean; Chan: Byte = NET_CHAN_DOWNLOAD);
+procedure g_Net_SendData(Data: AByte; peer: pENetPeer; Reliable: Boolean; Chan: Byte = NET_CHAN_DOWNLOAD);
var
P: pENetPacket;
F: enet_uint32;
begin
P := enet_packet_create(@Data[0], dataLength, F);
if not Assigned(P) then Exit;
- enet_host_widecast(NetHost, Chan, P);
+ enet_host_broadcast(NetHost, Chan, P);
end;
enet_host_flush(NetHost);
end;
+function UserRequestExit: Boolean;
+begin
+ Result := e_KeyPressed(IK_SPACE) or
+ e_KeyPressed(IK_ESCAPE) or
+ e_KeyPressed(VK_ESCAPE) or
+ e_KeyPressed(JOY0_JUMP) or
+ e_KeyPressed(JOY1_JUMP) or
+ e_KeyPressed(JOY2_JUMP) or
+ e_KeyPressed(JOY3_JUMP)
+end;
+
function g_Net_Wait_Event(msgId: Word): TMemoryStream;
-var
- downloadEvent: ENetEvent;
- OuterLoop: Boolean;
- MID: Byte;
- Ptr: Pointer;
- msgStream: TMemoryStream;
+ var
+ ev: ENetEvent;
+ rMsgId: Byte;
+ Ptr: Pointer;
+ stream: TMemoryStream;
+ status: cint;
begin
- FillChar(downloadEvent, SizeOf(downloadEvent), 0);
- msgStream := nil;
- OuterLoop := True;
- while OuterLoop do
- begin
- while (enet_host_service(NetHost, @downloadEvent, 0) > 0) do
+ FillChar(ev, SizeOf(ev), 0);
+ stream := nil;
+ repeat
+ status := enet_host_service(NetHost, @ev, Trunc(g_Net_DownloadTimeout * 1000));
+ if status > 0 then
begin
- if (downloadEvent.kind = ENET_EVENT_TYPE_RECEIVE) then
- begin
- Ptr := downloadEvent.packet^.data;
-
- MID := Byte(Ptr^);
-
- if (MID = msgId) then
+ case ev.kind of
+ ENET_EVENT_TYPE_RECEIVE:
+ begin
+ Ptr := ev.packet^.data;
+ rMsgId := Byte(Ptr^);
+ if rMsgId = msgId then
+ begin
+ stream := TMemoryStream.Create;
+ stream.SetSize(ev.packet^.dataLength);
+ stream.WriteBuffer(Ptr^, ev.packet^.dataLength);
+ stream.Seek(0, soFromBeginning);
+ status := 1 (* received *)
+ end
+ else
+ begin
+ (* looks that game state always received, so ignore it *)
+ e_LogWritefln('g_Net_Wait_Event(%s): skip message %s', [msgId, rMsgId]);
+ status := 2 (* continue *)
+ end
+ end;
+ ENET_EVENT_TYPE_DISCONNECT:
begin
- msgStream := TMemoryStream.Create;
- msgStream.SetSize(downloadEvent.packet^.dataLength);
- msgStream.WriteBuffer(Ptr^, downloadEvent.packet^.dataLength);
- msgStream.Seek(0, soFromBeginning);
-
- OuterLoop := False;
- enet_packet_destroy(downloadEvent.packet);
- break;
- end
- else begin
- enet_packet_destroy(downloadEvent.packet);
+ if (ev.data <= NET_DISC_MAX) then
+ g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' ' + _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + ev.data)], True);
+ status := -2 (* error: disconnected *)
end;
- end
else
- if (downloadEvent.kind = ENET_EVENT_TYPE_DISCONNECT) then
- begin
- if (downloadEvent.data <= NET_DISC_MAX) then
- g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' ' +
- _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + downloadEvent.data)], True);
- OuterLoop := False;
- Break;
- end;
+ g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' unknown ENet event ' + IntToStr(Ord(ev.kind)), True);
+ status := -3 (* error: unknown event *)
+ end;
+ enet_packet_destroy(ev.packet)
+ end
+ else
+ begin
+ g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' timeout reached', True);
+ status := 0 (* error: timeout *)
end;
-
- ProcessLoading();
-
- e_PollInput();
-
- if e_KeyPressed(IK_ESCAPE) or e_KeyPressed(IK_SPACE) then
- break;
- end;
- Result := msgStream;
+ ProcessLoading(true);
+ until (status <> 2) or UserRequestExit();
+ Result := stream
end;
function g_Net_IsHostBanned(IP: LongWord; Perm: Boolean = False): Boolean;
CloseFile(F);
end;
+procedure g_Net_DumpStart();
+begin
+ if NetMode = NET_SERVER then
+ NetDumpFile := createDiskFile(NETDUMP_FILENAME + '_server')
+ else
+ NetDumpFile := createDiskFile(NETDUMP_FILENAME + '_client');
+end;
+
+procedure g_Net_DumpSendBuffer();
+begin
+ writeInt(NetDumpFile, gTime);
+ writeInt(NetDumpFile, LongWord(NetOut.CurSize));
+ writeInt(NetDumpFile, Byte(1));
+ NetDumpFile.WriteBuffer(NetOut.Data^, NetOut.CurSize);
+end;
+
+procedure g_Net_DumpRecvBuffer(Buf: penet_uint8; Len: LongWord);
+begin
+ if (Buf = nil) or (Len = 0) then Exit;
+ writeInt(NetDumpFile, gTime);
+ writeInt(NetDumpFile, Len);
+ writeInt(NetDumpFile, Byte(0));
+ NetDumpFile.WriteBuffer(Buf^, Len);
+end;
+
+procedure g_Net_DumpEnd();
+begin
+ NetDumpFile.Free();
+ NetDumpFile := nil;
+end;
+
+function g_Net_ForwardPorts(ForwardPongPort: Boolean = True): Boolean;
+{$IFDEF USE_MINIUPNPC}
+var
+ DevList: PUPNPDev;
+ Urls: TUPNPUrls;
+ Data: TIGDDatas;
+ LanAddr: array [0..255] of Char;
+ StrPort: AnsiString;
+ Err, I: Integer;
+begin
+ Result := False;
+
+ if NetPortForwarded = NetPort then
+ begin
+ Result := True;
+ exit;
+ end;
+
+ NetPongForwarded := False;
+ NetPortForwarded := 0;
+
+ DevList := upnpDiscover(1000, nil, nil, 0, 0, 2, Addr(Err));
+ if DevList = nil then
+ begin
+ conwritefln('port forwarding failed: upnpDiscover() failed: %d', [Err]);
+ exit;
+ end;
+
+ I := UPNP_GetValidIGD(DevList, @Urls, @Data, Addr(LanAddr[0]), 256);
+
+ if I = 0 then
+ begin
+ conwriteln('port forwarding failed: could not find an IGD device on this LAN');
+ FreeUPNPDevList(DevList);
+ FreeUPNPUrls(@Urls);
+ exit;
+ end;
+
+ StrPort := IntToStr(NetPort);
+ I := UPNP_AddPortMapping(
+ Urls.controlURL, Addr(data.first.servicetype[1]),
+ PChar(StrPort), PChar(StrPort), Addr(LanAddr[0]), PChar('D2DF'),
+ PChar('UDP'), nil, PChar('0')
+ );
+
+ if I <> 0 then
+ begin
+ conwritefln('forwarding port %d failed: error %d', [NetPort, I]);
+ FreeUPNPDevList(DevList);
+ FreeUPNPUrls(@Urls);
+ exit;
+ end;
+
+ if ForwardPongPort then
+ begin
+ StrPort := IntToStr(NET_PING_PORT);
+ I := UPNP_AddPortMapping(
+ Urls.controlURL, Addr(data.first.servicetype[1]),
+ PChar(StrPort), PChar(StrPort), Addr(LanAddr[0]), PChar('D2DF'),
+ PChar('UDP'), nil, PChar('0')
+ );
+
+ if I <> 0 then
+ begin
+ conwritefln('forwarding port %d failed: error %d', [NetPort + 1, I]);
+ NetPongForwarded := False;
+ end
+ else
+ begin
+ conwritefln('forwarded port %d successfully', [NetPort + 1]);
+ NetPongForwarded := True;
+ end;
+ end;
+
+ conwritefln('forwarded port %d successfully', [NetPort]);
+ NetIGDControl := AnsiString(Urls.controlURL);
+ NetIGDService := data.first.servicetype;
+ NetPortForwarded := NetPort;
+
+ FreeUPNPDevList(DevList);
+ FreeUPNPUrls(@Urls);
+ Result := True;
+end;
+{$ELSE}
+begin
+ Result := False;
+end;
+{$ENDIF}
+
+procedure g_Net_UnforwardPorts();
+{$IFDEF USE_MINIUPNPC}
+var
+ I: Integer;
+ StrPort: AnsiString;
+begin
+ if NetPortForwarded = 0 then Exit;
+
+ conwriteln('unforwarding ports...');
+
+ StrPort := IntToStr(NetPortForwarded);
+ I := UPNP_DeletePortMapping(
+ PChar(NetIGDControl), Addr(NetIGDService[1]),
+ PChar(StrPort), PChar('UDP'), nil
+ );
+ conwritefln(' port %d: %d', [NetPortForwarded, I]);
+
+ if NetPongForwarded then
+ begin
+ NetPongForwarded := False;
+ StrPort := IntToStr(NetPortForwarded + 1);
+ I := UPNP_DeletePortMapping(
+ PChar(NetIGDControl), Addr(NetIGDService[1]),
+ PChar(StrPort), PChar('UDP'), nil
+ );
+ conwritefln(' port %d: %d', [NetPortForwarded + 1, I]);
+ end;
+
+ NetPortForwarded := 0;
+end;
+{$ELSE}
+begin
+end;
+{$ENDIF}
+
+initialization
+ conRegVar('cl_downloadtimeout', @g_Net_DownloadTimeout, 0.0, 1000000.0, '', 'timeout in seconds, 0 to disable it');
+ g_Net_DownloadTimeout := 60;
+ NetIn.Alloc(NET_BUFSIZE);
+ NetOut.Alloc(NET_BUFSIZE);
+finalization
+ NetIn.Free();
+ NetOut.Free();
end.