diff --git a/src/game/g_net.pas b/src/game/g_net.pas
index 74defc3d1083f613d89e67f59da2a879a4c9acc1..ac845852fa1c26f5d3e4e41339842b11f93bedba 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, version 3 of the License ONLY.
+ *
+ * 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
unit g_net;
interface
uses
- e_log, e_fixedbuffer, ENet, ENetTypes, ENetPlatform, Classes;
+ e_log, e_msg, ENet, Classes, md5, MAPDEF{$IFDEF USE_MINIUPNPC}, miniupnpc;{$ELSE};{$ENDIF}
const
const
- NET_PROTOCOL_VER = 164;
+ NET_PROTOCOL_VER = 181;
NET_MAXCLIENTS = 24;
NET_MAXCLIENTS = 24;
- NET_CHANS = 11;
+ NET_CHANS = 12;
NET_CHAN_SERVICE = 0;
NET_CHAN_IMPORTANT = 1;
NET_CHAN_SERVICE = 0;
NET_CHAN_IMPORTANT = 1;
NET_CHAN_CHAT = 8;
NET_CHAN_DOWNLOAD = 9;
NET_CHAN_SHOTS = 10;
NET_CHAN_CHAT = 8;
NET_CHAN_DOWNLOAD = 9;
NET_CHAN_SHOTS = 10;
+ NET_CHAN_DOWNLOAD_EX = 11;
NET_NONE = 0;
NET_SERVER = 1;
NET_CLIENT = 2;
NET_NONE = 0;
NET_SERVER = 1;
NET_CLIENT = 2;
- NET_BUFSIZE = 65536;
+ NET_BUFSIZE = $FFFF;
+ NET_PING_PORT = $DF2D;
NET_EVERYONE = -1;
NET_EVERYONE = -1;
+ NET_UNRELIABLE = 0;
+ NET_RELIABLE = 1;
+
NET_DISC_NONE: enet_uint32 = 0;
NET_DISC_PROTOCOL: enet_uint32 = 1;
NET_DISC_VERSION: enet_uint32 = 2;
NET_DISC_NONE: enet_uint32 = 0;
NET_DISC_PROTOCOL: enet_uint32 = 1;
NET_DISC_VERSION: enet_uint32 = 2;
NET_DISC_TEMPBAN: enet_uint32 = 7;
NET_DISC_BAN: enet_uint32 = 8;
NET_DISC_MAX: enet_uint32 = 8;
NET_DISC_TEMPBAN: enet_uint32 = 7;
NET_DISC_BAN: enet_uint32 = 8;
NET_DISC_MAX: enet_uint32 = 8;
+ NET_DISC_FILE_TIMEOUT: enet_uint32 = 13;
NET_STATE_NONE = 0;
NET_STATE_AUTH = 1;
NET_STATE_GAME = 2;
NET_STATE_NONE = 0;
NET_STATE_AUTH = 1;
NET_STATE_GAME = 2;
+ NET_CONNECT_TIMEOUT = 1000 * 10;
+
BANLIST_FILENAME = 'banlist.txt';
BANLIST_FILENAME = 'banlist.txt';
+ NETDUMP_FILENAME = 'netdump';
+
+ {$IFDEF FREEBSD}
+ NilThreadId = nil;
+ {$ELSE}
+ NilThreadId = 0;
+ {$ENDIF}
type
type
+ TNetFileTransfer = record
+ diskName: string;
+ hash: TMD5Digest;
+ stream: TStream;
+ size: Integer; // file size in bytes
+ chunkSize: Integer;
+ lastSentChunk: Integer;
+ lastAckChunk: Integer;
+ lastAckTime: Int64; // msecs; if not "in progress", we're waiting for the first ack
+ inProgress: Boolean;
+ diskBuffer: PChar; // of `chunkSize` bytes
+ end;
+
TNetClient = record
TNetClient = record
- ID: Byte;
- Used: Boolean;
- State: Byte;
- Peer: pENetPeer;
- Player: Word;
+ ID: Byte;
+ Used: Boolean;
+ State: Byte;
+ Peer: pENetPeer;
+ Player: Word;
RequestedFullUpdate: Boolean;
RCONAuth: Boolean;
Voted: Boolean;
RequestedFullUpdate: Boolean;
RCONAuth: Boolean;
Voted: Boolean;
+ Transfer: TNetFileTransfer; // only one transfer may be active
+ NetOut: array [0..1] of TMsg;
end;
TBanRecord = record
IP: LongWord;
end;
TBanRecord = record
IP: LongWord;
var
NetInitDone: Boolean = False;
NetMode: Byte = NET_NONE;
var
NetInitDone: Boolean = False;
NetMode: Byte = NET_NONE;
+ NetDump: Boolean = False;
NetServerName: string = 'Unnamed Server';
NetPassword: string = '';
NetServerName: string = 'Unnamed Server';
NetPassword: string = '';
NetClientIP: string = '127.0.0.1';
NetClientPort: Word = 25666;
NetClientIP: string = '127.0.0.1';
NetClientPort: Word = 25666;
- NetIn, NetOut: TBuffer;
+ NetIn, NetOut: TMsg;
+ NetBuf: array [0..1] of TMsg;
NetClients: array of TNetClient;
NetClientCount: Byte = 0;
NetClients: array of TNetClient;
NetClientCount: Byte = 0;
NetForcePlayerUpdate: Boolean = False;
NetPredictSelf: Boolean = True;
NetForcePlayerUpdate: Boolean = False;
NetPredictSelf: Boolean = True;
- NetGotKeys: Boolean = False;
+ NetForwardPorts: Boolean = False;
NetGotEverything: 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;
+
+ g_Res_received_map_start: Boolean = false; // set if we received "map change" event
+
function g_Net_Init(): Boolean;
procedure g_Net_Cleanup();
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;
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);
-function g_Net_Wait_Event(msgId: Word): TMemoryStream;
+procedure g_Net_SendData(Data: AByte; peer: pENetPeer; Reliable: Boolean; Chan: Byte = NET_CHAN_DOWNLOAD);
+//function g_Net_Wait_Event(msgId: Word): TMemoryStream;
+//function g_Net_Wait_FileInfo (var tf: TNetFileTransfer; asMap: Boolean; out resList: TStringList): Integer;
function IpToStr(IP: LongWord): string;
function StrToIp(IPstr: string; var IP: LongWord): Boolean;
function IpToStr(IP: LongWord): string;
function StrToIp(IPstr: string; var IP: LongWord): Boolean;
procedure g_Net_UnbanNonPermHosts();
procedure g_Net_SaveBanList();
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();
+
+function g_Net_UserRequestExit: Boolean;
+
+function g_Net_SendMapRequest (): Boolean;
+function g_Net_Wait_MapInfo (var tf: TNetFileTransfer; resList: TStringList): Integer;
+function g_Net_RequestResFileInfo (resIndex: LongInt; out tf: TNetFileTransfer): Integer;
+function g_Net_AbortResTransfer (var tf: TNetFileTransfer): Boolean;
+function g_Net_ReceiveResourceFile (resIndex: LongInt; var tf: TNetFileTransfer; strm: TStream): Integer;
+
+
implementation
uses
SysUtils,
e_input, g_nethandler, g_netmsg, g_netmaster, g_player, g_window, g_console,
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,
+ g_map;
+
+const
+ FILE_CHUNK_SIZE = 8192;
+
+var
+ g_Net_DownloadTimeout: Single;
{ /// SERVICE FUNCTIONS /// }
{ /// SERVICE FUNCTIONS /// }
+procedure clearNetClientTransfers (var nc: TNetClient);
+begin
+ nc.Transfer.stream.Free;
+ nc.Transfer.diskName := ''; // just in case
+ if (nc.Transfer.diskBuffer <> nil) then FreeMem(nc.Transfer.diskBuffer);
+ nc.Transfer.stream := nil;
+ nc.Transfer.diskBuffer := nil;
+end;
+
+
+procedure clearNetClient (var nc: TNetClient);
+begin
+ clearNetClientTransfers(nc);
+end;
+
+procedure clearNetClients (clearArray: Boolean);
+var
+ f: Integer;
+begin
+ for f := Low(NetClients) to High(NetClients) do clearNetClient(NetClients[f]);
+ if (clearArray) then SetLength(NetClients, 0);
+end;
function g_Net_FindSlot(): Integer;
function g_Net_FindSlot(): Integer;
NetClients[N].RCONAuth := False;
NetClients[N].Voted := False;
NetClients[N].Player := 0;
NetClients[N].RCONAuth := False;
NetClients[N].Voted := False;
NetClients[N].Player := 0;
+ clearNetClientTransfers(NetClients[N]); // just in case
end;
Result := N;
end;
Result := N;
IPstr: string;
IP: LongWord;
begin
IPstr: string;
IP: LongWord;
begin
- e_Buffer_Clear(@NetIn);
- e_Buffer_Clear(@NetOut);
- SetLength(NetClients, 0);
+ NetIn.Clear();
+ NetOut.Clear();
+ NetBuf[NET_UNRELIABLE].Clear();
+ NetBuf[NET_RELIABLE].Clear();
+ //SetLength(NetClients, 0);
+ clearNetClients(true); // clear array
NetPeer := nil;
NetHost := nil;
NetMyID := -1;
NetPeer := nil;
NetHost := nil;
NetMyID := -1;
end;
procedure g_Net_Flush();
end;
procedure g_Net_Flush();
+var
+ T: Integer;
+ P: pENetPacket;
+ F, Chan: enet_uint32;
+ I: Integer;
begin
begin
- enet_host_flush(NetHost);
+ F := 0;
+ Chan := NET_CHAN_GAME;
+
+ if NetMode = NET_SERVER then
+ for T := NET_UNRELIABLE to NET_RELIABLE do
+ begin
+ if NetBuf[T].CurSize > 0 then
+ begin
+ P := enet_packet_create(NetBuf[T].Data, NetBuf[T].CurSize, F);
+ if not Assigned(P) then continue;
+ enet_host_broadcast(NetHost, Chan, P);
+ NetBuf[T].Clear();
+ end;
+
+ for I := Low(NetClients) to High(NetClients) do
+ begin
+ if not NetClients[I].Used then continue;
+ if NetClients[I].NetOut[T].CurSize <= 0 then continue;
+ P := enet_packet_create(NetClients[I].NetOut[T].Data, NetClients[I].NetOut[T].CurSize, F);
+ if not Assigned(P) then continue;
+ enet_peer_send(NetClients[I].Peer, Chan, P);
+ NetClients[I].NetOut[T].Clear();
+ end;
+
+ // next and last iteration is always RELIABLE
+ F := LongWord(ENET_PACKET_FLAG_RELIABLE);
+ Chan := NET_CHAN_IMPORTANT;
+ end
+ else if NetMode = NET_CLIENT then
+ for T := NET_UNRELIABLE to NET_RELIABLE do
+ begin
+ if NetBuf[T].CurSize > 0 then
+ begin
+ P := enet_packet_create(NetBuf[T].Data, NetBuf[T].CurSize, F);
+ if not Assigned(P) then continue;
+ enet_peer_send(NetPeer, Chan, P);
+ NetBuf[T].Clear();
+ end;
+ // next and last iteration is always RELIABLE
+ F := LongWord(ENET_PACKET_FLAG_RELIABLE);
+ Chan := NET_CHAN_IMPORTANT;
+ end;
end;
procedure g_Net_Cleanup();
begin
end;
procedure g_Net_Cleanup();
begin
- e_Buffer_Clear(@NetIn);
- e_Buffer_Clear(@NetOut);
+ NetIn.Clear();
+ NetOut.Clear();
+ NetBuf[NET_UNRELIABLE].Clear();
+ NetBuf[NET_RELIABLE].Clear();
- SetLength(NetClients, 0);
+ //SetLength(NetClients, 0);
+ clearNetClients(true); // clear array
NetClientCount := 0;
NetPeer := nil;
NetClientCount := 0;
NetPeer := nil;
NetTimeToReliable := 0;
NetMode := NET_NONE;
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();
end;
procedure g_Net_Free();
{ /// SERVER FUNCTIONS /// }
{ /// 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
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;
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
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;
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);
if enet_socket_bind(NetPongSock, @NetPongAddr) < 0 then
begin
enet_socket_destroy(NetPongSock);
end;
NetMode := NET_SERVER;
end;
NetMode := NET_SERVER;
- e_Buffer_Clear(@NetOut);
+ NetOut.Clear();
+ NetBuf[NET_UNRELIABLE].Clear();
+ NetBuf[NET_RELIABLE].Clear();
+
+ if NetDump then
+ g_Net_DumpStart();
end;
procedure g_Net_Host_Die();
end;
procedure g_Net_Host_Die();
enet_peer_reset(NetClients[I].Peer);
NetClients[I].Peer := nil;
NetClients[I].Used := False;
enet_peer_reset(NetClients[I].Peer);
NetClients[I].Peer := nil;
NetClients[I].Used := False;
+ NetClients[I].NetOut[NET_UNRELIABLE].Free();
+ NetClients[I].NetOut[NET_RELIABLE].Free();
end;
end;
+ clearNetClients(false); // don't clear array
if (NetMPeer <> nil) and (NetMHost <> nil) then g_Net_Slist_Disconnect;
if NetPongSock <> ENET_SOCKET_NULL then
enet_socket_destroy(NetPongSock);
if (NetMPeer <> nil) and (NetMHost <> nil) then g_Net_Slist_Disconnect;
if NetPongSock <> ENET_SOCKET_NULL then
enet_socket_destroy(NetPongSock);
NetMode := NET_NONE;
g_Net_Cleanup;
NetMode := NET_NONE;
g_Net_Cleanup;
- e_WriteLog('NET: Server stopped', MSG_NOTIFY);
+ e_WriteLog('NET: Server stopped', TMsgType.Notify);
end;
procedure g_Net_Host_Send(ID: Integer; Reliable: Boolean; Chan: Byte = NET_CHAN_GAME);
var
end;
procedure g_Net_Host_Send(ID: Integer; Reliable: Boolean; Chan: Byte = NET_CHAN_GAME);
var
- P: pENetPacket;
- F: enet_uint32;
+ T: Integer;
begin
if (Reliable) then
begin
if (Reliable) then
- F := LongWord(ENET_PACKET_FLAG_RELIABLE)
+ T := NET_RELIABLE
else
else
- F := 0;
+ T := NET_UNRELIABLE;
if (ID >= 0) then
begin
if ID > High(NetClients) then Exit;
if NetClients[ID].Peer = nil then Exit;
if (ID >= 0) then
begin
if ID > High(NetClients) then Exit;
if NetClients[ID].Peer = nil then Exit;
-
- P := enet_packet_create(Addr(NetOut.Data), NetOut.Len, F);
- if not Assigned(P) then Exit;
-
- enet_peer_send(NetClients[ID].Peer, Chan, P);
+ // write size first
+ NetClients[ID].NetOut[T].Write(Integer(NetOut.CurSize));
+ NetClients[ID].NetOut[T].Write(NetOut);
end
else
begin
end
else
begin
- P := enet_packet_create(Addr(NetOut.Data), NetOut.Len, F);
- if not Assigned(P) then Exit;
-
- enet_host_widecast(NetHost, Chan, P);
+ // write size first
+ NetBuf[T].Write(Integer(NetOut.CurSize));
+ NetBuf[T].Write(NetOut);
end;
end;
- g_Net_Flush();
- e_Buffer_Clear(@NetOut);
+ if NetDump then g_Net_DumpSendBuffer();
+ NetOut.Clear();
end;
procedure g_Net_Host_CheckPings();
var
ClAddr: ENetAddress;
Buf: ENetBuffer;
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]);
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;
Ping[0] := 0;
if (Ping[0] = Ord('D')) and (Ping[1] = Ord('F')) then
begin
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);
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);
enet_socket_send(NetPongSock, @ClAddr, @Buf, 1);
- e_Buffer_Clear(@NetOut);
+ NetOut.Clear();
+ end;
+end;
+
+
+const
+ // server packet type
+ NTF_SERVER_DONE = 10; // done with this file
+ NTF_SERVER_FILE_INFO = 11; // sent after client request
+ NTF_SERVER_CHUNK = 12; // next chunk; chunk number follows
+ NTF_SERVER_ABORT = 13; // server abort
+ NTF_SERVER_MAP_INFO = 14;
+
+ // client packet type
+ NTF_CLIENT_MAP_REQUEST = 100; // map file request; also, returns list of additional wads to download
+ NTF_CLIENT_FILE_REQUEST = 101; // resource file request (by index)
+ NTF_CLIENT_ABORT = 102; // do not send requested file, or abort current transfer
+ NTF_CLIENT_START = 103; // start transfer; client may resume download by sending non-zero starting chunk
+ NTF_CLIENT_ACK = 104; // chunk ack; chunk number follows
+
+
+procedure KillClientByFT (var nc: TNetClient);
+begin
+ e_LogWritefln('disconnected client #%d due to file transfer error', [nc.ID], TMsgType.Warning);
+ enet_peer_disconnect(nc.Peer, NET_DISC_FILE_TIMEOUT);
+ clearNetClientTransfers(nc);
+end;
+
+
+procedure ProcessHostFileTransfers (var nc: TNetClient);
+var
+ tf: ^TNetFileTransfer;
+ ct: Int64;
+ chunks: Integer;
+ rd: Integer;
+ pkt: PENetPacket;
+ omsg: TMsg;
+begin
+ tf := @nc.Transfer;
+ if (tf.stream = nil) then exit;;
+ ct := GetTimerMS();
+ // arbitrary timeout number
+ if (ct-tf.lastAckTime >= 5000) then
+ begin
+ KillClientByFT(nc);
+ exit;
+ end;
+ // check if we need to send something
+ if (not tf.inProgress) then exit; // waiting for the initial ack
+ // ok, we're sending chunks
+ if (tf.lastAckChunk <> tf.lastSentChunk) then exit;
+ Inc(tf.lastSentChunk);
+ // do it one chunk at a time; client ack will advance our chunk counter
+ chunks := (tf.size+tf.chunkSize-1) div tf.chunkSize;
+
+ if (tf.lastSentChunk > chunks) then
+ begin
+ KillClientByFT(nc);
+ exit;
+ end;
+
+ omsg.Alloc(NET_BUFSIZE);
+ try
+ omsg.Clear();
+ if (tf.lastSentChunk = chunks) then
+ begin
+ // we're done with this file
+ e_LogWritefln('download: client #%d, DONE sending chunks #%d/#%d', [nc.ID, tf.lastSentChunk, chunks]);
+ omsg.Write(Byte(NTF_SERVER_DONE));
+ clearNetClientTransfers(nc);
+ end
+ else
+ begin
+ // packet type
+ omsg.Write(Byte(NTF_SERVER_CHUNK));
+ omsg.Write(LongInt(tf.lastSentChunk));
+ // read chunk
+ rd := tf.size-(tf.lastSentChunk*tf.chunkSize);
+ if (rd > tf.chunkSize) then rd := tf.chunkSize;
+ omsg.Write(LongInt(rd));
+ //e_LogWritefln('download: client #%d, sending chunk #%d/#%d (%d bytes)', [nc.ID, tf.lastSentChunk, chunks, rd]);
+ //FIXME: check for errors here
+ try
+ tf.stream.Seek(tf.lastSentChunk*tf.chunkSize, soFromBeginning);
+ tf.stream.ReadBuffer(tf.diskBuffer^, rd);
+ omsg.WriteData(tf.diskBuffer, rd);
+ except // sorry
+ KillClientByFT(nc);
+ exit;
+ end;
+ end;
+ // send packet
+ pkt := enet_packet_create(omsg.Data, omsg.CurSize, ENET_PACKET_FLAG_RELIABLE);
+ if not Assigned(pkt) then
+ begin
+ KillClientByFT(nc);
+ exit;
+ end;
+ if (enet_peer_send(nc.Peer, NET_CHAN_DOWNLOAD_EX, pkt) <> 0) then
+ begin
+ KillClientByFT(nc);
+ exit;
+ end;
+ finally
+ omsg.Free();
+ end;
+end;
+
+
+// received packet is in `NetEvent`
+procedure ProcessDownloadExPacket ();
+var
+ f: Integer;
+ nc: ^TNetClient;
+ nid: Integer = -1;
+ msg: TMsg;
+ omsg: TMsg;
+ cmd: Byte;
+ tf: ^TNetFileTransfer;
+ fname: string;
+ pkt: PENetPacket;
+ chunk: Integer;
+ ridx: Integer;
+ dfn: AnsiString;
+ md5: TMD5Digest;
+ st: TStream;
+ size: LongInt;
+begin
+ // find client index by peer
+ for f := Low(NetClients) to High(NetClients) do
+ begin
+ if (not NetClients[f].Used) then continue;
+ if (NetClients[f].Peer = NetEvent.peer) then
+ begin
+ nid := f;
+ break;
+ end;
+ end;
+ //e_LogWritefln('RECEIVE: dlpacket; client=%d (datalen=%u)', [nid, NetEvent.packet^.dataLength]);
+
+ if (nid < 0) then exit; // wtf?!
+ nc := @NetClients[nid];
+
+ if (NetEvent.packet^.dataLength = 0) then
+ begin
+ KillClientByFT(nc^);
+ exit;
+ end;
+
+ tf := @NetClients[nid].Transfer;
+ tf.lastAckTime := GetTimerMS();
+
+ cmd := Byte(NetEvent.packet^.data^);
+ //e_LogWritefln('RECEIVE: nid=%d; cmd=%u', [nid, cmd]);
+ case cmd of
+ NTF_CLIENT_FILE_REQUEST: // file request
+ begin
+ if (tf.stream <> nil) then
+ begin
+ KillClientByFT(nc^);
+ exit;
+ end;
+ if (NetEvent.packet^.dataLength < 2) then
+ begin
+ KillClientByFT(nc^);
+ exit;
+ end;
+ // new transfer request; build packet
+ if not msg.Init(NetEvent.packet^.data+1, NetEvent.packet^.dataLength-1, True) then
+ begin
+ KillClientByFT(nc^);
+ exit;
+ end;
+ // get resource index
+ ridx := msg.ReadLongInt();
+ if (ridx < -1) or (ridx >= gExternalResources.Count) then
+ begin
+ e_LogWritefln('Invalid resource index %d', [ridx], TMsgType.Warning);
+ KillClientByFT(nc^);
+ exit;
+ end;
+ if (ridx < 0) then fname := MapsDir+gGameSettings.WAD else fname := GameDir+'/wads/'+gExternalResources[ridx];
+ if (length(fname) = 0) then
+ begin
+ e_WriteLog('Invalid filename: '+fname, TMsgType.Warning);
+ KillClientByFT(nc^);
+ exit;
+ end;
+ tf.diskName := findDiskWad(fname);
+ //if (length(tf.diskName) = 0) then tf.diskName := findDiskWad(GameDir+'/wads/'+fname);
+ if (length(tf.diskName) = 0) then
+ begin
+ e_LogWritefln('NETWORK: file "%s" not found!', [fname], TMsgType.Fatal);
+ KillClientByFT(nc^);
+ exit;
+ end;
+ // calculate hash
+ //TODO: cache hashes
+ tf.hash := MD5File(tf.diskName);
+ // create file stream
+ tf.diskName := findDiskWad(fname);
+ try
+ tf.stream := openDiskFileRO(tf.diskName);
+ except
+ tf.stream := nil;
+ end;
+ if (tf.stream = nil) then
+ begin
+ e_WriteLog(Format('NETWORK: file "%s" not found!', [fname]), TMsgType.Fatal);
+ KillClientByFT(nc^);
+ exit;
+ end;
+ e_LogWritefln('client #%d requested resource #%d (file is `%s` : `%s`)', [nc.ID, ridx, fname, tf.diskName]);
+ tf.size := tf.stream.size;
+ tf.chunkSize := FILE_CHUNK_SIZE; // arbitrary
+ tf.lastSentChunk := -1;
+ tf.lastAckChunk := -1;
+ tf.lastAckTime := GetTimerMS();
+ tf.inProgress := False; // waiting for the first ACK or for the cancel
+ GetMem(tf.diskBuffer, tf.chunkSize);
+ // sent file info message
+ omsg.Alloc(NET_BUFSIZE);
+ try
+ omsg.Clear();
+ omsg.Write(Byte(NTF_SERVER_FILE_INFO));
+ omsg.Write(tf.hash);
+ omsg.Write(tf.size);
+ omsg.Write(tf.chunkSize);
+ omsg.Write(ExtractFileName(fname));
+ pkt := enet_packet_create(omsg.Data, omsg.CurSize, ENET_PACKET_FLAG_RELIABLE);
+ if not Assigned(pkt) then
+ begin
+ KillClientByFT(nc^);
+ exit;
+ end;
+ if (enet_peer_send(nc.Peer, NET_CHAN_DOWNLOAD_EX, pkt) <> 0) then
+ begin
+ KillClientByFT(nc^);
+ exit;
+ end;
+ finally
+ omsg.Free();
+ end;
+ end;
+ NTF_CLIENT_ABORT: // do not send requested file, or abort current transfer
+ begin
+ e_LogWritefln('client #%d aborted file transfer', [nc.ID]);
+ clearNetClientTransfers(nc^);
+ end;
+ NTF_CLIENT_START: // start transfer; client may resume download by sending non-zero starting chunk
+ begin
+ if not Assigned(tf.stream) then
+ begin
+ KillClientByFT(nc^);
+ exit;
+ end;
+ if (tf.lastSentChunk <> -1) or (tf.lastAckChunk <> -1) or (tf.inProgress) then
+ begin
+ // double ack, get lost
+ KillClientByFT(nc^);
+ exit;
+ end;
+ if (NetEvent.packet^.dataLength < 2) then
+ begin
+ KillClientByFT(nc^);
+ exit;
+ end;
+ // build packet
+ if not msg.Init(NetEvent.packet^.data+1, NetEvent.packet^.dataLength-1, True) then
+ begin
+ KillClientByFT(nc^);
+ exit;
+ end;
+ chunk := msg.ReadLongInt();
+ if (chunk < 0) or (chunk > (tf.size+tf.chunkSize-1) div tf.chunkSize) then
+ begin
+ KillClientByFT(nc^);
+ exit;
+ end;
+ e_LogWritefln('client #%d started file transfer from chunk %d', [nc.ID, chunk]);
+ // start sending chunks
+ tf.inProgress := True;
+ tf.lastSentChunk := chunk-1;
+ tf.lastAckChunk := chunk-1;
+ end;
+ NTF_CLIENT_ACK: // chunk ack; chunk number follows
+ begin
+ if not Assigned(tf.stream) then
+ begin
+ KillClientByFT(nc^);
+ exit;
+ end;
+ if (tf.lastSentChunk < 0) or (not tf.inProgress) then
+ begin
+ // double ack, get lost
+ KillClientByFT(nc^);
+ exit;
+ end;
+ if (NetEvent.packet^.dataLength < 2) then
+ begin
+ KillClientByFT(nc^);
+ exit;
+ end;
+ // build packet
+ if not msg.Init(NetEvent.packet^.data+1, NetEvent.packet^.dataLength-1, True) then
+ begin
+ KillClientByFT(nc^);
+ exit;
+ end;
+ chunk := msg.ReadLongInt();
+ if (chunk < 0) or (chunk > (tf.size+tf.chunkSize-1) div tf.chunkSize) then
+ begin
+ KillClientByFT(nc^);
+ exit;
+ end;
+ // do it this way, so client may seek, or request retransfers for some reason
+ tf.lastAckChunk := chunk;
+ tf.lastSentChunk := chunk;
+ //e_LogWritefln('client #%d acked file transfer chunk %d', [nc.ID, chunk]);
+ end;
+ NTF_CLIENT_MAP_REQUEST:
+ begin
+ e_LogWritefln('client #%d requested map info', [nc.ID]);
+ omsg.Alloc(NET_BUFSIZE);
+ try
+ omsg.Clear();
+ dfn := findDiskWad(MapsDir+gGameSettings.WAD);
+ if (dfn = '') then dfn := '!wad_not_found!.wad'; //FIXME
+ md5 := MD5File(dfn);
+ st := openDiskFileRO(dfn);
+ if not assigned(st) then exit; //wtf?!
+ size := st.size;
+ st.Free;
+ // packet type
+ omsg.Write(Byte(NTF_SERVER_MAP_INFO));
+ // map wad name
+ omsg.Write(gGameSettings.WAD);
+ // map wad md5
+ omsg.Write(md5);
+ // map wad size
+ omsg.Write(size);
+ // number of external resources for map
+ omsg.Write(LongInt(gExternalResources.Count));
+ // external resource names
+ for f := 0 to gExternalResources.Count-1 do
+ begin
+ omsg.Write(ExtractFileName(gExternalResources[f])); // GameDir+'/wads/'+ResList.Strings[i]
+ end;
+ // send packet
+ pkt := enet_packet_create(omsg.Data, omsg.CurSize, ENET_PACKET_FLAG_RELIABLE);
+ if not Assigned(pkt) then exit;
+ if (enet_peer_send(nc.Peer, NET_CHAN_DOWNLOAD_EX, pkt) <> 0) then exit;
+ finally
+ omsg.Free();
+ end;
+ end;
+ else
+ begin
+ KillClientByFT(NetClients[nid]);
+ exit;
+ end;
end;
end;
end;
end;
+
function g_Net_Host_Update(): enet_size_t;
var
IP: string;
function g_Net_Host_Update(): enet_size_t;
var
IP: string;
ID: Integer;
TC: pTNetClient;
TP: TPlayer;
ID: Integer;
TC: pTNetClient;
TP: TPlayer;
+ f: Integer;
+ //ctt: Int64;
begin
IP := '';
Result := 0;
begin
IP := '';
Result := 0;
- if NetUseMaster then
+ if NetUseMaster then g_Net_Slist_Check;
+ g_Net_Host_CheckPings;
+
+ //ctt := -GetTimerMS();
+ // process file transfers
+ for f := Low(NetClients) to High(NetClients) do
begin
begin
- g_Net_Slist_Check;
- g_Net_Host_CheckPings;
+ if (not NetClients[f].Used) then continue;
+ if (NetClients[f].Transfer.stream = nil) then continue;
+ ProcessHostFileTransfers(NetClients[f]);
end;
end;
+ {
+ ctt := ctt+GetTimerMS();
+ if (ctt > 1) then e_LogWritefln('all transfers: [%d]', [Integer(ctt)]);
+ }
while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
begin
while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
begin
Byte(NetClients[ID].Peer^.data^) := ID;
NetClients[ID].State := NET_STATE_AUTH;
NetClients[ID].RCONAuth := False;
Byte(NetClients[ID].Peer^.data^) := ID;
NetClients[ID].State := NET_STATE_AUTH;
NetClients[ID].RCONAuth := False;
+ NetClients[ID].NetOut[NET_UNRELIABLE].Alloc(NET_BUFSIZE*2);
+ NetClients[ID].NetOut[NET_RELIABLE].Alloc(NET_BUFSIZE*2);
+ clearNetClientTransfers(NetClients[ID]); // just in case
enet_peer_timeout(NetEvent.peer, ENET_PEER_TIMEOUT_LIMIT * 2, ENET_PEER_TIMEOUT_MINIMUM * 2, ENET_PEER_TIMEOUT_MAXIMUM * 2);
enet_peer_timeout(NetEvent.peer, ENET_PEER_TIMEOUT_LIMIT * 2, ENET_PEER_TIMEOUT_MINIMUM * 2, ENET_PEER_TIMEOUT_MAXIMUM * 2);
ENET_EVENT_TYPE_RECEIVE:
begin
ENET_EVENT_TYPE_RECEIVE:
begin
- ID := Byte(NetEvent.peer^.data^);
- if ID > High(NetClients) then Exit;
- TC := @NetClients[ID];
+ //e_LogWritefln('RECEIVE: chan=%u', [NetEvent.channelID]);
+ if (NetEvent.channelID = NET_CHAN_DOWNLOAD_EX) then
+ begin
+ ProcessDownloadExPacket();
+ end
+ else
+ begin
+ ID := Byte(NetEvent.peer^.data^);
+ if ID > High(NetClients) then Exit;
+ TC := @NetClients[ID];
- g_Net_HostMsgHandler(TC, NetEvent.packet);
+ if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
+ g_Net_Host_HandlePacket(TC, NetEvent.packet, g_Net_HostMsgHandler);
+ end;
end;
ENET_EVENT_TYPE_DISCONNECT:
begin
ID := Byte(NetEvent.peer^.data^);
end;
ENET_EVENT_TYPE_DISCONNECT:
begin
ID := Byte(NetEvent.peer^.data^);
- if ID > High(NetClients) then Exit;
+ if ID > High(NetClients) then Exit;
+ clearNetClient(NetClients[ID]);
TC := @NetClients[ID];
if TC = nil then Exit;
TC := @NetClients[ID];
if TC = nil then Exit;
TP.Lives := 0;
TP.Kill(K_SIMPLEKILL, 0, HIT_DISCON);
g_Console_Add(Format(_lc[I_PLAYER_LEAVE], [TP.Name]), True);
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;
g_Player_Remove(TP.UID);
end;
TC^.Peer := nil;
TC^.Player := 0;
TC^.RequestedFullUpdate := False;
TC^.Peer := nil;
TC^.Player := 0;
TC^.RequestedFullUpdate := False;
+ TC^.NetOut[NET_UNRELIABLE].Free();
+ TC^.NetOut[NET_RELIABLE].Free();
FreeMemory(NetEvent.peer^.data);
NetEvent.peer^.data := nil;
FreeMemory(NetEvent.peer^.data);
NetEvent.peer^.data := nil;
end
else
begin
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);
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;
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);
var
end;
procedure g_Net_Client_Send(Reliable: Boolean; Chan: Byte = NET_CHAN_GAME);
var
- P: pENetPacket;
- F: enet_uint32;
+ T: Integer;
begin
if (Reliable) then
begin
if (Reliable) then
- F := LongWord(ENET_PACKET_FLAG_RELIABLE)
+ T := NET_RELIABLE
else
else
- F := 0;
+ T := NET_UNRELIABLE;
- P := enet_packet_create(Addr(NetOut.Data), NetOut.Len, F);
- if not Assigned(P) then Exit;
+ // write size first
+ NetBuf[T].Write(Integer(NetOut.CurSize));
+ NetBuf[T].Write(NetOut);
- enet_peer_send(NetPeer, Chan, P);
- g_Net_Flush();
- e_Buffer_Clear(@NetOut);
+ if NetDump then g_Net_DumpSendBuffer();
+ NetOut.Clear();
+ g_Net_Flush(); // FIXME: for now, send immediately
end;
function g_Net_Client_Update(): enet_size_t;
end;
function g_Net_Client_Update(): enet_size_t;
begin
case NetEvent.kind of
ENET_EVENT_TYPE_RECEIVE:
begin
case NetEvent.kind of
ENET_EVENT_TYPE_RECEIVE:
- g_Net_ClientMsgHandler(NetEvent.packet);
+ begin
+ if (NetEvent.channelID = NET_CHAN_DOWNLOAD_EX) then continue; // ignore all download packets, they're processed by separate code
+ if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
+ g_Net_Client_HandlePacket(NetEvent.packet, g_Net_ClientMsgHandler);
+ end;
ENET_EVENT_TYPE_DISCONNECT:
begin
ENET_EVENT_TYPE_DISCONNECT:
begin
begin
case NetEvent.kind of
ENET_EVENT_TYPE_RECEIVE:
begin
case NetEvent.kind of
ENET_EVENT_TYPE_RECEIVE:
- g_Net_ClientLightMsgHandler(NetEvent.packet);
+ begin
+ if (NetEvent.channelID = NET_CHAN_DOWNLOAD_EX) then continue; // ignore all download packets, they're processed by separate code
+ if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
+ g_Net_Client_HandlePacket(NetEvent.packet, g_Net_ClientLightMsgHandler);
+ end;
ENET_EVENT_TYPE_DISCONNECT:
begin
ENET_EVENT_TYPE_DISCONNECT:
begin
function g_Net_Connect(IP: string; Port: enet_uint16): Boolean;
var
OuterLoop: Boolean;
function g_Net_Connect(IP: string; Port: enet_uint16): Boolean;
var
OuterLoop: Boolean;
+ TimeoutTime, T: Int64;
begin
if NetMode <> NET_NONE then
begin
begin
if NetMode <> NET_NONE then
begin
Exit;
end;
Exit;
end;
+ // предупредить что ждем слишком долго через N секунд
+ TimeoutTime := GetTimer() + NET_CONNECT_TIMEOUT;
+
OuterLoop := True;
while OuterLoop do
begin
OuterLoop := True;
while OuterLoop do
begin
begin
g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_CLIENT_DONE]);
NetMode := NET_CLIENT;
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;
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;
Exit;
end;
end;
- ProcessLoading();
-
- e_PollInput();
+ T := GetTimer();
+ if T > TimeoutTime then
+ begin
+ TimeoutTime := T + NET_CONNECT_TIMEOUT * 100; // одного предупреждения хватит
+ g_Console_Add(_lc[I_NET_MSG_TIMEOUT_WARN], True);
+ g_Console_Add(Format(_lc[I_NET_MSG_PORTS], [Integer(Port), Integer(NET_PING_PORT)]), True);
+ end;
+
+ ProcessLoading(true);
- 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;
g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_TIMEOUT], True);
OuterLoop := False;
end;
g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_TIMEOUT], True);
+ g_Console_Add(Format(_lc[I_NET_MSG_PORTS], [Integer(Port), Integer(NET_PING_PORT)]), True);
if NetPeer <> nil then enet_peer_reset(NetPeer);
if NetHost <> nil then
begin
if NetPeer <> nil then enet_peer_reset(NetPeer);
if NetHost <> nil then
begin
var
Ptr: Pointer;
begin
var
Ptr: Pointer;
begin
- Result := '';
Ptr := Addr(IP);
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;
function StrToIp(IPstr: string; var IP: LongWord): Boolean;
end;
end;
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;
var
P: pENetPacket;
F: enet_uint32;
begin
P := enet_packet_create(@Data[0], dataLength, F);
if not Assigned(P) then Exit;
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;
end;
enet_host_flush(NetHost);
end;
+function g_Net_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;
function g_Net_Wait_Event(msgId: Word): TMemoryStream;
+ var
+ ev: ENetEvent;
+ rMsgId: Byte;
+ Ptr: Pointer;
+ stream: TMemoryStream;
+ status: cint;
+begin
+ FillChar(ev, SizeOf(ev), 0);
+ stream := nil;
+ repeat
+ status := enet_host_service(NetHost, @ev, Trunc(g_Net_DownloadTimeout * 1000));
+ if status > 0 then
+ begin
+ 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
+ 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;
+ else
+ 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(true);
+ until (status <> 2) or g_Net_UserRequestExit();
+ Result := stream
+end;
+}
+
+
+function getNewTimeoutEnd (): Int64;
+begin
+ result := GetTimerMS();
+ if (g_Net_DownloadTimeout <= 0) then
+ begin
+ result := result+1000*60*3; // 3 minutes
+ end
+ else
+ begin
+ result := result+trunc(g_Net_DownloadTimeout*1000);
+ end;
+end;
+
+
+function g_Net_SendMapRequest (): Boolean;
var
var
- downloadEvent: ENetEvent;
- OuterLoop: Boolean;
- MID: Byte;
+ msg: TMsg;
+ pkt: PENetPacket;
+begin
+ result := false;
+ e_LogWritefln('sending map request...', []);
+ // send request
+ msg.Alloc(NET_BUFSIZE);
+ try
+ msg.Clear();
+ msg.Write(Byte(NTF_CLIENT_MAP_REQUEST));
+ e_LogWritefln(' request size is %d', [msg.CurSize]);
+ pkt := enet_packet_create(msg.Data, msg.CurSize, ENET_PACKET_FLAG_RELIABLE);
+ if not Assigned(pkt) then exit;
+ if (enet_peer_send(NetPeer, NET_CHAN_DOWNLOAD_EX, pkt) <> 0) then exit;
+ enet_host_flush(NetHost);
+ finally
+ msg.Free();
+ end;
+ result := true;
+end;
+
+
+// returns `false` on error or user abort
+// fills:
+// hash
+// size
+// chunkSize
+// returns:
+// <0 on error
+// 0 on success
+// 1 on user abort
+// 2 on server abort
+// for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
+function g_Net_Wait_MapInfo (var tf: TNetFileTransfer; resList: TStringList): Integer;
+var
+ ev: ENetEvent;
+ rMsgId: Byte;
Ptr: Pointer;
Ptr: Pointer;
- msgStream: TMemoryStream;
+ msg: TMsg;
+ freePacket: Boolean = false;
+ ct, ett: Int64;
+ status: cint;
+ s: AnsiString;
+ rc, f: LongInt;
begin
begin
- msgStream := nil;
- OuterLoop := True;
- while OuterLoop do
- begin
- while (enet_host_service(NetHost, @downloadEvent, 0) > 0) do
- begin
- if (downloadEvent.kind = ENET_EVENT_TYPE_RECEIVE) then
+ FillChar(ev, SizeOf(ev), 0);
+ Result := -1;
+ try
+ ett := getNewTimeoutEnd();
+ repeat
+ status := enet_host_service(NetHost, @ev, 300);
+ if (status < 0) then
+ begin
+ g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True);
+ Result := -1;
+ exit;
+ end;
+ if (status = 0) then
+ begin
+ // check for timeout
+ ct := GetTimerMS();
+ if (ct >= ett) then
+ begin
+ g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' timeout reached', True);
+ Result := -1;
+ exit;
+ end;
+ end
+ else
+ begin
+ // some event
+ case ev.kind of
+ ENET_EVENT_TYPE_RECEIVE:
+ begin
+ freePacket := true;
+ if (ev.channelID <> NET_CHAN_DOWNLOAD_EX) then
+ begin
+ //e_LogWritefln('g_Net_Wait_MapInfo: skip message from non-transfer channel', []);
+ freePacket := false;
+ g_Net_Client_HandlePacket(ev.packet, g_Net_ClientLightMsgHandler);
+ if (g_Res_received_map_start) then begin result := -666; exit; end;
+ end
+ else
+ begin
+ ett := getNewTimeoutEnd();
+ if (ev.packet.dataLength < 1) then
+ begin
+ e_LogWritefln('g_Net_Wait_MapInfo: invalid server packet (no data)', []);
+ Result := -1;
+ exit;
+ end;
+ Ptr := ev.packet^.data;
+ rMsgId := Byte(Ptr^);
+ e_LogWritefln('g_Net_Wait_MapInfo: got message %u from server (dataLength=%u)', [rMsgId, ev.packet^.dataLength]);
+ if (rMsgId = NTF_SERVER_FILE_INFO) then
+ begin
+ e_LogWritefln('g_Net_Wait_MapInfo: waiting for map info reply, but got file info reply', []);
+ Result := -1;
+ exit;
+ end
+ else if (rMsgId = NTF_SERVER_ABORT) then
+ begin
+ e_LogWritefln('g_Net_Wait_MapInfo: server aborted transfer', []);
+ Result := 2;
+ exit;
+ end
+ else if (rMsgId = NTF_SERVER_MAP_INFO) then
+ begin
+ e_LogWritefln('g_Net_Wait_MapInfo: creating map info packet...', []);
+ if not msg.Init(ev.packet^.data+1, ev.packet^.dataLength-1, True) then exit;
+ e_LogWritefln('g_Net_Wait_MapInfo: parsing map info packet (rd=%d; max=%d)...', [msg.ReadCount, msg.MaxSize]);
+ resList.Clear();
+ // map wad name
+ tf.diskName := msg.ReadString();
+ e_LogWritefln('g_Net_Wait_MapInfo: map wad is `%s`', [tf.diskName]);
+ // map wad md5
+ tf.hash := msg.ReadMD5();
+ // map wad size
+ tf.size := msg.ReadLongInt();
+ e_LogWritefln('g_Net_Wait_MapInfo: map wad size is %d', [tf.size]);
+ // number of external resources for map
+ rc := msg.ReadLongInt();
+ if (rc < 0) or (rc > 1024) then
+ begin
+ e_LogWritefln('g_Net_Wait_Event: invalid number of map external resources (%d)', [rc]);
+ Result := -1;
+ exit;
+ end;
+ e_LogWritefln('g_Net_Wait_MapInfo: map external resource count is %d', [rc]);
+ // external resource names
+ for f := 0 to rc-1 do
+ begin
+ s := ExtractFileName(msg.ReadString());
+ if (length(s) = 0) then
+ begin
+ Result := -1;
+ exit;
+ end;
+ resList.append(s);
+ end;
+ e_LogWritefln('g_Net_Wait_MapInfo: got map info', []);
+ Result := 0; // success
+ exit;
+ end
+ else
+ begin
+ e_LogWritefln('g_Net_Wait_Event: invalid server packet type', []);
+ Result := -1;
+ exit;
+ end;
+ end;
+ end;
+ ENET_EVENT_TYPE_DISCONNECT:
+ begin
+ 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);
+ Result := -1;
+ exit;
+ end;
+ else
+ begin
+ g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' unknown ENet event ' + IntToStr(Ord(ev.kind)), True);
+ result := -1;
+ exit;
+ end;
+ end;
+ if (freePacket) then begin freePacket := false; enet_packet_destroy(ev.packet); end;
+ end;
+ ProcessLoading(true);
+ if g_Net_UserRequestExit() then
begin
begin
- Ptr := downloadEvent.packet^.data;
+ g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' user abort', True);
+ Result := 1;
+ exit;
+ end;
+ until false;
+ finally
+ if (freePacket) then enet_packet_destroy(ev.packet);
+ end;
+end;
- MID := Byte(Ptr^);
- if (MID = msgId) then
+// returns `false` on error or user abort
+// fills:
+// diskName (actually, base name)
+// hash
+// size
+// chunkSize
+// returns:
+// <0 on error
+// 0 on success
+// 1 on user abort
+// 2 on server abort
+// for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
+function g_Net_RequestResFileInfo (resIndex: LongInt; out tf: TNetFileTransfer): Integer;
+var
+ ev: ENetEvent;
+ rMsgId: Byte;
+ Ptr: Pointer;
+ msg: TMsg;
+ freePacket: Boolean = false;
+ ct, ett: Int64;
+ status: cint;
+ pkt: PENetPacket;
+begin
+ // send request
+ msg.Alloc(NET_BUFSIZE);
+ try
+ msg.Clear();
+ msg.Write(Byte(NTF_CLIENT_FILE_REQUEST));
+ msg.Write(resIndex);
+ pkt := enet_packet_create(msg.Data, msg.CurSize, ENET_PACKET_FLAG_RELIABLE);
+ if not Assigned(pkt) then
+ begin
+ result := -1;
+ exit;
+ end;
+ if (enet_peer_send(NetPeer, NET_CHAN_DOWNLOAD_EX, pkt) <> 0) then
+ begin
+ result := -1;
+ exit;
+ end;
+ finally
+ msg.Free();
+ end;
+
+ FillChar(ev, SizeOf(ev), 0);
+ Result := -1;
+ try
+ ett := getNewTimeoutEnd();
+ repeat
+ status := enet_host_service(NetHost, @ev, 300);
+ if (status < 0) then
+ begin
+ g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True);
+ Result := -1;
+ exit;
+ end;
+ if (status = 0) then
+ begin
+ // check for timeout
+ ct := GetTimerMS();
+ if (ct >= ett) then
begin
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);
+ g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' timeout reached', True);
+ Result := -1;
+ exit;
end;
end
else
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;
+ begin
+ // some event
+ case ev.kind of
+ ENET_EVENT_TYPE_RECEIVE:
+ begin
+ freePacket := true;
+ if (ev.channelID <> NET_CHAN_DOWNLOAD_EX) then
+ begin
+ //e_LogWriteln('g_Net_Wait_Event: skip message from non-transfer channel');
+ freePacket := false;
+ g_Net_Client_HandlePacket(ev.packet, g_Net_ClientLightMsgHandler);
+ if (g_Res_received_map_start) then begin result := -666; exit; end;
+ end
+ else
+ begin
+ ett := getNewTimeoutEnd();
+ if (ev.packet.dataLength < 1) then
+ begin
+ e_LogWriteln('g_Net_Wait_Event: invalid server packet (no data)');
+ Result := -1;
+ exit;
+ end;
+ Ptr := ev.packet^.data;
+ rMsgId := Byte(Ptr^);
+ e_LogWritefln('received transfer packet with id %d (%u bytes)', [rMsgId, ev.packet^.dataLength]);
+ if (rMsgId = NTF_SERVER_FILE_INFO) then
+ begin
+ if not msg.Init(ev.packet^.data+1, ev.packet^.dataLength-1, True) then exit;
+ tf.hash := msg.ReadMD5();
+ tf.size := msg.ReadLongInt();
+ tf.chunkSize := msg.ReadLongInt();
+ tf.diskName := ExtractFileName(msg.readString());
+ if (tf.size < 0) or (tf.chunkSize <> FILE_CHUNK_SIZE) or (length(tf.diskName) = 0) then
+ begin
+ e_LogWritefln('g_Net_RequestResFileInfo: invalid file info packet', []);
+ Result := -1;
+ exit;
+ end;
+ e_LogWritefln('got file info for resource #%d: size=%d; name=%s', [resIndex, tf.size, tf.diskName]);
+ Result := 0; // success
+ exit;
+ end
+ else if (rMsgId = NTF_SERVER_ABORT) then
+ begin
+ e_LogWriteln('g_Net_RequestResFileInfo: server aborted transfer');
+ Result := 2;
+ exit;
+ end
+ else if (rMsgId = NTF_SERVER_MAP_INFO) then
+ begin
+ e_LogWriteln('g_Net_RequestResFileInfo: waiting for map info reply, but got file info reply');
+ Result := -1;
+ exit;
+ end
+ else
+ begin
+ e_LogWriteln('g_Net_RequestResFileInfo: invalid server packet type');
+ Result := -1;
+ exit;
+ end;
+ end;
+ end;
+ ENET_EVENT_TYPE_DISCONNECT:
+ begin
+ 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);
+ Result := -1;
+ exit;
+ end;
+ else
+ begin
+ g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' unknown ENet event ' + IntToStr(Ord(ev.kind)), True);
+ result := -1;
+ exit;
+ end;
end;
end;
- end;
+ if (freePacket) then begin freePacket := false; enet_packet_destroy(ev.packet); end;
+ end;
+ ProcessLoading(true);
+ if g_Net_UserRequestExit() then
+ begin
+ g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' user abort', True);
+ Result := 1;
+ exit;
+ end;
+ until false;
+ finally
+ if (freePacket) then enet_packet_destroy(ev.packet);
+ end;
+end;
- ProcessLoading();
- e_PollInput();
+function g_Net_AbortResTransfer (var tf: TNetFileTransfer): Boolean;
+var
+ msg: TMsg;
+ pkt: PENetPacket;
+begin
+ result := false;
+ e_LogWritefln('aborting file transfer...', []);
+ // send request
+ msg.Alloc(NET_BUFSIZE);
+ try
+ msg.Clear();
+ msg.Write(Byte(NTF_CLIENT_ABORT));
+ pkt := enet_packet_create(msg.Data, msg.CurSize, ENET_PACKET_FLAG_RELIABLE);
+ if not Assigned(pkt) then exit;
+ if (enet_peer_send(NetPeer, NET_CHAN_DOWNLOAD_EX, pkt) <> 0) then exit;
+ enet_host_flush(NetHost);
+ finally
+ msg.Free();
+ end;
+ result := true;
+end;
- if e_KeyPressed(IK_ESCAPE) or e_KeyPressed(IK_SPACE) then
- break;
+
+// returns `false` on error or user abort
+// fills:
+// hash
+// size
+// chunkSize
+// returns:
+// <0 on error
+// 0 on success
+// 1 on user abort
+// 2 on server abort
+// for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
+function g_Net_ReceiveResourceFile (resIndex: LongInt; var tf: TNetFileTransfer; strm: TStream): Integer;
+var
+ ev: ENetEvent;
+ rMsgId: Byte;
+ Ptr: Pointer;
+ msg: TMsg;
+ omsg: TMsg;
+ freePacket: Boolean = false;
+ ct, ett: Int64;
+ status: cint;
+ nextChunk: Integer = 0;
+ chunkTotal: Integer;
+ chunk: Integer;
+ csize: Integer;
+ buf: PChar = nil;
+ pkt: PENetPacket;
+ //stx: Int64;
+begin
+ // send request
+ msg.Alloc(NET_BUFSIZE);
+ try
+ msg.Clear();
+ msg.Write(Byte(NTF_CLIENT_START));
+ msg.Write(LongInt(0));
+ pkt := enet_packet_create(msg.Data, msg.CurSize, ENET_PACKET_FLAG_RELIABLE);
+ if not Assigned(pkt) then exit;
+ if (enet_peer_send(NetPeer, NET_CHAN_DOWNLOAD_EX, pkt) <> 0) then exit;
+ finally
+ msg.Free();
+ end;
+
+ chunkTotal := (tf.size+tf.chunkSize-1) div tf.chunkSize;
+ e_LogWritefln('receiving file `%s` (%d chunks)', [tf.diskName, chunkTotal], TMsgType.Notify);
+ g_Game_SetLoadingText('downloading "'+ExtractFileName(tf.diskName)+'"', chunkTotal, False);
+
+ // wait for reply data
+ FillChar(ev, SizeOf(ev), 0);
+ Result := -1;
+ GetMem(buf, tf.chunkSize);
+ try
+ ett := getNewTimeoutEnd();
+ repeat
+ //stx := -GetTimerMS();
+ status := enet_host_service(NetHost, @ev, 300);
+ if (status < 0) then
+ begin
+ g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True);
+ Result := -1;
+ exit;
+ end;
+ if (status = 0) then
+ begin
+ // check for timeout
+ ct := GetTimerMS();
+ if (ct >= ett) then
+ begin
+ g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' timeout reached', True);
+ Result := -1;
+ exit;
+ end;
+ end
+ else
+ begin
+ // some event
+ case ev.kind of
+ ENET_EVENT_TYPE_RECEIVE:
+ begin
+ freePacket := true;
+ if (ev.channelID <> NET_CHAN_DOWNLOAD_EX) then
+ begin
+ //e_LogWritefln('g_Net_Wait_Event: skip message from non-transfer channel', []);
+ freePacket := false;
+ g_Net_Client_HandlePacket(ev.packet, g_Net_ClientLightMsgHandler);
+ if (g_Res_received_map_start) then begin result := -666; exit; end;
+ end
+ else
+ begin
+ //stx := stx+GetTimerMS();
+ //e_LogWritefln('g_Net_ReceiveResourceFile: stx=%d', [Integer(stx)]);
+ //stx := -GetTimerMS();
+ ett := getNewTimeoutEnd();
+ if (ev.packet.dataLength < 1) then
+ begin
+ e_LogWritefln('g_Net_ReceiveResourceFile: invalid server packet (no data)', []);
+ Result := -1;
+ exit;
+ end;
+ Ptr := ev.packet^.data;
+ rMsgId := Byte(Ptr^);
+ if (rMsgId = NTF_SERVER_DONE) then
+ begin
+ e_LogWritefln('file transfer complete.', []);
+ result := 0;
+ exit;
+ end
+ else if (rMsgId = NTF_SERVER_CHUNK) then
+ begin
+ if not msg.Init(ev.packet^.data+1, ev.packet^.dataLength-1, True) then exit;
+ chunk := msg.ReadLongInt();
+ csize := msg.ReadLongInt();
+ if (chunk <> nextChunk) then
+ begin
+ e_LogWritefln('received chunk %d, but expected chunk %d', [chunk, nextChunk]);
+ Result := -1;
+ exit;
+ end;
+ if (csize < 0) or (csize > tf.chunkSize) then
+ begin
+ e_LogWritefln('received chunk with size %d, but expected chunk size is %d', [csize, tf.chunkSize]);
+ Result := -1;
+ exit;
+ end;
+ //e_LogWritefln('got chunk #%d of #%d (csize=%d)', [chunk, (tf.size+tf.chunkSize-1) div tf.chunkSize, csize]);
+ msg.ReadData(buf, csize);
+ strm.WriteBuffer(buf^, csize);
+ nextChunk := chunk+1;
+ g_Game_StepLoading();
+ // send ack
+ omsg.Alloc(NET_BUFSIZE);
+ try
+ omsg.Clear();
+ omsg.Write(Byte(NTF_CLIENT_ACK));
+ omsg.Write(LongInt(chunk));
+ pkt := enet_packet_create(omsg.Data, omsg.CurSize, ENET_PACKET_FLAG_RELIABLE);
+ if not Assigned(pkt) then exit;
+ if (enet_peer_send(NetPeer, NET_CHAN_DOWNLOAD_EX, pkt) <> 0) then exit;
+ finally
+ omsg.Free();
+ end;
+ end
+ else if (rMsgId = NTF_SERVER_ABORT) then
+ begin
+ e_LogWritefln('g_Net_ReceiveResourceFile: server aborted transfer', []);
+ Result := 2;
+ exit;
+ end
+ else
+ begin
+ e_LogWritefln('g_Net_ReceiveResourceFile: invalid server packet type', []);
+ Result := -1;
+ exit;
+ end;
+ //stx := stx+GetTimerMS();
+ //e_LogWritefln('g_Net_ReceiveResourceFile: process stx=%d', [Integer(stx)]);
+ end;
+ end;
+ ENET_EVENT_TYPE_DISCONNECT:
+ begin
+ 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);
+ Result := -1;
+ exit;
+ end;
+ else
+ begin
+ g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' unknown ENet event ' + IntToStr(Ord(ev.kind)), True);
+ result := -1;
+ exit;
+ end;
+ end;
+ if (freePacket) then begin freePacket := false; enet_packet_destroy(ev.packet); end;
+ end;
+ ProcessLoading(true);
+ if g_Net_UserRequestExit() then
+ begin
+ g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' user abort', True);
+ Result := 1;
+ exit;
+ end;
+ until false;
+ finally
+ FreeMem(buf);
+ if (freePacket) then enet_packet_destroy(ev.packet);
end;
end;
- Result := msgStream;
end;
end;
+
function g_Net_IsHostBanned(IP: LongWord; Perm: Boolean = False): Boolean;
var
I: Integer;
function g_Net_IsHostBanned(IP: LongWord; Perm: Boolean = False): Boolean;
var
I: Integer;
CloseFile(F);
end;
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');
+ SetLength(NetClients, 0);
+ g_Net_DownloadTimeout := 60;
+ NetIn.Alloc(NET_BUFSIZE);
+ NetOut.Alloc(NET_BUFSIZE);
+ NetBuf[NET_UNRELIABLE].Alloc(NET_BUFSIZE*2);
+ NetBuf[NET_RELIABLE].Alloc(NET_BUFSIZE*2);
+finalization
+ NetIn.Free();
+ NetOut.Free();
+ NetBuf[NET_UNRELIABLE].Free();
+ NetBuf[NET_RELIABLE].Free();
end.
end.