X-Git-Url: http://deadsoftware.ru/gitweb?p=d2df-sdl.git;a=blobdiff_plain;f=src%2Fgame%2Fg_net.pas;h=11acd5f079370213b7f3db2e0c7799149a4670d2;hp=c15df2bae9406b2cc043fce515bdc11b113b21da;hb=2304c541d7bdbf7de389437482ecdff37fc7fbd5;hpb=87ddd721327c2ed38e6f9d324c731b58566a60b7 diff --git a/src/game/g_net.pas b/src/game/g_net.pas index c15df2b..11acd5f 100644 --- a/src/game/g_net.pas +++ b/src/game/g_net.pas @@ -2,8 +2,7 @@ * * 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. + * 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 @@ -19,13 +18,13 @@ unit g_net; interface uses - e_log, e_msg, ENet, Classes, MAPDEF{$IFDEF USE_MINIUPNPC}, miniupnpc;{$ELSE};{$ENDIF} + e_log, e_msg, ENet, Classes, md5, MAPDEF{$IFDEF USE_MINIUPNPC}, miniupnpc;{$ELSE};{$ENDIF} const - NET_PROTOCOL_VER = 174; + NET_PROTOCOL_VER = 182; NET_MAXCLIENTS = 24; - NET_CHANS = 11; + NET_CHANS = 12; NET_CHAN_SERVICE = 0; NET_CHAN_IMPORTANT = 1; @@ -38,6 +37,7 @@ const NET_CHAN_CHAT = 8; NET_CHAN_DOWNLOAD = 9; NET_CHAN_SHOTS = 10; + NET_CHAN_DOWNLOAD_EX = 11; NET_NONE = 0; NET_SERVER = 1; @@ -48,6 +48,9 @@ const 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; @@ -58,30 +61,58 @@ const 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_CONNECT_TIMEOUT = 1000 * 10; + BANLIST_FILENAME = 'banlist.txt'; NETDUMP_FILENAME = 'netdump'; - {$IFDEF FREEBSD} + {$IF DEFINED(FREEBSD) OR DEFINED(DARWIN)} NilThreadId = nil; {$ELSE} NilThreadId = 0; {$ENDIF} type + TNetMapResourceInfo = record + wadName: AnsiString; // wad file name, without a path + size: Integer; // wad file size (-1: size and hash are not known) + hash: TMD5Digest; // wad hash + end; + + TNetMapResourceInfoArray = array of TNetMapResourceInfo; + + 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 + resumed: Boolean; + end; + TNetClient = record - ID: Byte; - Used: Boolean; - State: Byte; - Peer: pENetPeer; - Player: Word; + ID: Byte; + Used: Boolean; + State: Byte; + Peer: pENetPeer; + Player: Word; RequestedFullUpdate: Boolean; + WaitForFirstSpawn: Boolean; // set to `true` in server, used to spawn a player on first full state request RCONAuth: Boolean; Voted: Boolean; + Transfer: TNetFileTransfer; // only one transfer may be active + NetOut: array [0..1] of TMsg; end; TBanRecord = record IP: LongWord; @@ -124,6 +155,7 @@ var NetClientPort: Word = 25666; NetIn, NetOut: TMsg; + NetBuf: array [0..1] of TMsg; NetClients: array of TNetClient; NetClientCount: Byte = 0; @@ -159,6 +191,9 @@ var NetDumpFile: TStream; + g_Res_received_map_start: Integer = 0; // set if we received "map change" event + + function g_Net_Init(): Boolean; procedure g_Net_Cleanup(); procedure g_Net_Free(); @@ -180,7 +215,8 @@ 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; +//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; @@ -201,17 +237,1002 @@ procedure g_Net_DumpEnd(); function g_Net_ForwardPorts(ForwardPongPort: Boolean = True): Boolean; procedure g_Net_UnforwardPorts(); +function g_Net_UserRequestExit: Boolean; + +function g_Net_Wait_MapInfo (var tf: TNetFileTransfer; var resList: TNetMapResourceInfoArray): 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, - g_main, g_game, g_language, g_weapons, utils; + g_main, g_game, g_language, g_weapons, utils, ctypes, g_system, + g_map; + +const + FILE_CHUNK_SIZE = 8192; + +var + g_Net_DownloadTimeout: Single; + trans_omsg: TMsg; + + +//************************************************************************** +// +// 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; -{ /// SERVICE FUNCTIONS /// } +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_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; + + +//************************************************************************** +// +// file transfer declaraions and host packet processor +// +//************************************************************************** + +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 + + +// disconnect client due to some file transfer error +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; + + +// send file transfer message from server to client +function ftransSendServerMsg (var nc: TNetClient; var m: TMsg): Boolean; +var + pkt: PENetPacket; +begin + result := false; + if (m.CurSize < 1) then exit; + pkt := enet_packet_create(m.Data, m.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; + result := true; +end; + + +// send file transfer message from client to server +function ftransSendClientMsg (var m: TMsg): Boolean; +var + pkt: PENetPacket; +begin + result := false; + if (m.CurSize < 1) then exit; + pkt := enet_packet_create(m.Data, m.CurSize, ENET_PACKET_FLAG_RELIABLE); + if not Assigned(pkt) then exit; + if (enet_peer_send(NetPeer, NET_CHAN_DOWNLOAD_EX, pkt) <> 0) then exit; + result := true; +end; + + +// file chunk sender +procedure ProcessChunkSend (var nc: TNetClient); +var + tf: ^TNetFileTransfer; + ct: Int64; + chunks: Integer; + rd: Integer; +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; + + trans_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]); + trans_omsg.Write(Byte(NTF_SERVER_DONE)); + clearNetClientTransfers(nc); + end + else + begin + // packet type + trans_omsg.Write(Byte(NTF_SERVER_CHUNK)); + trans_omsg.Write(LongInt(tf.lastSentChunk)); + // read chunk + rd := tf.size-(tf.lastSentChunk*tf.chunkSize); + if (rd > tf.chunkSize) then rd := tf.chunkSize; + trans_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); + trans_omsg.WriteData(tf.diskBuffer, rd); + except // sorry + killClientByFT(nc); + exit; + end; + end; + // send packet + ftransSendServerMsg(nc, trans_omsg); +end; + + +// server file transfer packet processor +// received packet is in `NetEvent` +procedure ProcessDownloadExPacket (); +var + f: Integer; + nc: ^TNetClient; + nid: Integer = -1; + msg: TMsg; + cmd: Byte; + tf: ^TNetFileTransfer; + fname: string; + chunk: Integer; + ridx: Integer; + dfn: AnsiString; + md5: TMD5Digest; + //st: TStream; + size: LongInt; + fi: TDiskFileInfo; +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 >= length(gExternalResources)) 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].diskName; + 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 + //tf.hash := MD5File(tf.diskName); + if (ridx < 0) then tf.hash := gWADHash else tf.hash := gExternalResources[ridx].hash; + // 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 + trans_omsg.Clear(); + trans_omsg.Write(Byte(NTF_SERVER_FILE_INFO)); + trans_omsg.Write(tf.hash); + trans_omsg.Write(tf.size); + trans_omsg.Write(tf.chunkSize); + trans_omsg.Write(ExtractFileName(fname)); + if not ftransSendServerMsg(nc^, trans_omsg) then exit; + 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; + ProcessChunkSend(nc^); + 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]); + ProcessChunkSend(nc^); + end; + NTF_CLIENT_MAP_REQUEST: + begin + e_LogWritefln('client #%d requested map info', [nc.ID]); + trans_omsg.Clear(); + dfn := findDiskWad(MapsDir+gGameSettings.WAD); + if (dfn = '') then dfn := '!wad_not_found!.wad'; //FIXME + //md5 := MD5File(dfn); + md5 := gWADHash; + if (not GetDiskFileInfo(dfn, fi)) then + begin + e_LogWritefln('client #%d requested map info, but i cannot get file info', [nc.ID]); + killClientByFT(nc^); + exit; + end; + size := fi.size; + { + st := openDiskFileRO(dfn); + if not assigned(st) then exit; //wtf?! + size := st.size; + st.Free; + } + // packet type + trans_omsg.Write(Byte(NTF_SERVER_MAP_INFO)); + // map wad name + trans_omsg.Write(gGameSettings.WAD); + // map wad md5 + trans_omsg.Write(md5); + // map wad size + trans_omsg.Write(size); + // number of external resources for map + trans_omsg.Write(LongInt(length(gExternalResources))); + // external resource names + for f := 0 to High(gExternalResources) do + begin + // old style packet + //trans_omsg.Write(ExtractFileName(gExternalResources[f])); // GameDir+'/wads/'+ResList.Strings[i] + // new style packet + trans_omsg.Write('!'); + trans_omsg.Write(LongInt(gExternalResources[f].size)); + trans_omsg.Write(gExternalResources[f].hash); + trans_omsg.Write(ExtractFileName(gExternalResources[f].diskName)); + end; + // send packet + if not ftransSendServerMsg(nc^, trans_omsg) then exit; + end; + else + begin + killClientByFT(nc^); + exit; + end; + end; +end; + + +//************************************************************************** +// +// file transfer crap (both client and server) +// +//************************************************************************** + +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; + + +// send map request to server, and wait for "map info" server reply +// +// returns `false` on error or user abort +// fills: +// diskName: map wad file name (without a path) +// hash: map wad hash +// size: map wad size +// chunkSize: set too +// resList: list of resource wads +// 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; var resList: TNetMapResourceInfoArray): Integer; +var + ev: ENetEvent; + rMsgId: Byte; + Ptr: Pointer; + msg: TMsg; + freePacket: Boolean = false; + ct, ett: Int64; + status: cint; + s: AnsiString; + rc, f: LongInt; + ri: ^TNetMapResourceInfo; +begin + SetLength(resList, 0); + + // send request + trans_omsg.Clear(); + trans_omsg.Write(Byte(NTF_CLIENT_MAP_REQUEST)); + if not ftransSendClientMsg(trans_omsg) then begin result := -1; exit; 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 + 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 < 0) 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]); + SetLength(resList, 0); // just in case + // 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]); + SetLength(resList, rc); + // external resource names + for f := 0 to rc-1 do + begin + ri := @resList[f]; + s := msg.ReadString(); + if (length(s) = 0) then begin result := -1; exit; end; + if (s = '!') then + begin + // extended packet + ri.size := msg.ReadLongInt(); + ri.hash := msg.ReadMD5(); + ri.wadName := ExtractFileName(msg.ReadString()); + if (length(ri.wadName) = 0) or (ri.size < 0) then begin result := -1; exit; end; + end + else + begin + // old-style packet, only name + ri.wadName := ExtractFileName(s); + if (length(ri.wadName) = 0) then begin result := -1; exit; end; + ri.size := -1; // unknown + end; + 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(); + 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; + + +// send file request to server, and wait for server reply +// +// 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; +begin + // send request + trans_omsg.Clear(); + trans_omsg.Write(Byte(NTF_CLIENT_FILE_REQUEST)); + trans_omsg.Write(resIndex); + if not ftransSendClientMsg(trans_omsg) then begin result := -1; exit; 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 + 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_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 < 0) 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; + if (freePacket) then begin freePacket := false; enet_packet_destroy(ev.packet); end; + end; + ProcessLoading(); + 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; + + +// call this to cancel file transfer requested by `g_Net_RequestResFileInfo()` +function g_Net_AbortResTransfer (var tf: TNetFileTransfer): Boolean; +begin + result := false; + e_LogWritefln('aborting file transfer...', []); + // send request + trans_omsg.Clear(); + trans_omsg.Write(Byte(NTF_CLIENT_ABORT)); + result := ftransSendClientMsg(trans_omsg); + if result then enet_host_flush(NetHost); +end; + + +// call this to start file transfer requested by `g_Net_RequestResFileInfo()` +// +// 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; + freePacket: Boolean = false; + ct, ett: Int64; + status: cint; + nextChunk: Integer = 0; + chunkTotal: Integer; + chunk: Integer; + csize: Integer; + buf: PChar = nil; + resumed: Boolean; + //stx: Int64; +begin + tf.resumed := false; + e_LogWritefln('file `%s`, size=%d (%d)', [tf.diskName, Integer(strm.size), tf.size], TMsgType.Notify); + // check if we should resume downloading + resumed := (strm.size > tf.chunkSize) and (strm.size < tf.size); + // send request + trans_omsg.Clear(); + trans_omsg.Write(Byte(NTF_CLIENT_START)); + if resumed then chunk := strm.size div tf.chunkSize else chunk := 0; + trans_omsg.Write(LongInt(chunk)); + if not ftransSendClientMsg(trans_omsg) then begin result := -1; exit; end; + + strm.Seek(chunk*tf.chunkSize, soFromBeginning); + 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); + tf.resumed := resumed; + + if (chunk > 0) then g_Game_StepLoading(chunk); + nextChunk := chunk; + + // 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 < 0) 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 + trans_omsg.Clear(); + trans_omsg.Write(Byte(NTF_CLIENT_ACK)); + trans_omsg.Write(LongInt(chunk)); + if not ftransSendClientMsg(trans_omsg) then begin result := -1; exit; 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(); + 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; + + +//************************************************************************** +// +// common functions +// +//************************************************************************** + function g_Net_FindSlot(): Integer; var I: Integer; @@ -254,9 +1275,11 @@ begin NetClients[N].Used := True; NetClients[N].ID := N; NetClients[N].RequestedFullUpdate := False; + NetClients[N].WaitForFirstSpawn := False; NetClients[N].RCONAuth := False; NetClients[N].Voted := False; NetClients[N].Player := 0; + clearNetClientTransfers(NetClients[N]); // just in case end; Result := N; @@ -270,7 +1293,10 @@ var begin NetIn.Clear(); NetOut.Clear(); - SetLength(NetClients, 0); + NetBuf[NET_UNRELIABLE].Clear(); + NetBuf[NET_RELIABLE].Clear(); + //SetLength(NetClients, 0); + clearNetClients(true); // clear array NetPeer := nil; NetHost := nil; NetMyID := -1; @@ -296,22 +1322,71 @@ begin end; procedure g_Net_Flush(); +var + T: Integer; + P: pENetPacket; + F, Chan: enet_uint32; + I: Integer; 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 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; NetHost := nil; - NetMPeer := nil; - NetMHost := nil; + g_Net_Slist_NetworkStopped(); + //g_Net_Slist_Disconnect(false); // do not spam console NetMyID := -1; NetPlrUID1 := -1; NetPlrUID2 := -1; @@ -344,8 +1419,11 @@ begin end; -{ /// SERVER FUNCTIONS /// } - +//************************************************************************** +// +// SERVER FUNCTIONS +// +//************************************************************************** function ForwardThread(Param: Pointer): PtrInt; begin @@ -408,6 +1486,8 @@ begin NetMode := NET_SERVER; NetOut.Clear(); + NetBuf[NET_UNRELIABLE].Clear(); + NetBuf[NET_RELIABLE].Clear(); if NetDump then g_Net_DumpStart(); @@ -436,9 +1516,13 @@ begin 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; - if (NetMPeer <> nil) and (NetMHost <> nil) then g_Net_Slist_Disconnect; + clearNetClients(false); // don't clear array + //if (g_Net_Slist_IsConnectionActive) then g_Net_Slist_Disconnect; + g_Net_Slist_NetworkStopped(); if NetPongSock <> ENET_SOCKET_NULL then enet_socket_destroy(NetPongSock); @@ -454,34 +1538,29 @@ 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 - F := LongWord(ENET_PACKET_FLAG_RELIABLE) + T := NET_RELIABLE else - F := 0; + T := NET_UNRELIABLE; if (ID >= 0) then begin if ID > High(NetClients) then Exit; if NetClients[ID].Peer = nil then Exit; - - P := enet_packet_create(NetOut.Data, NetOut.CurSize, 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 - P := enet_packet_create(NetOut.Data, NetOut.CurSize, F); - if not Assigned(P) then Exit; - - enet_host_broadcast(NetHost, Chan, P); + // write size first + NetBuf[T].Write(Integer(NetOut.CurSize)); + NetBuf[T].Write(NetOut); end; if NetDump then g_Net_DumpSendBuffer(); - g_Net_Flush(); NetOut.Clear(); end; @@ -513,7 +1592,8 @@ begin NetOut.Write(Byte(Ord('F'))); NetOut.Write(NetPort); NetOut.Write(ClTime); - g_Net_Slist_WriteInfo(); + //g_Net_Slist_WriteInfo(); + TMasterHost.writeInfo(NetOut); NPl := 0; if gPlayer1 <> nil then Inc(NPl); if gPlayer2 <> nil then Inc(NPl); @@ -528,6 +1608,7 @@ begin end; end; + function g_Net_Host_Update(): enet_size_t; var IP: string; @@ -540,7 +1621,10 @@ begin Result := 0; if NetUseMaster then - g_Net_Slist_Check; + begin + //g_Net_Slist_Check; + g_Net_Slist_Pulse(); + end; g_Net_Host_CheckPings; while (enet_host_service(NetHost, @NetEvent, 0) > 0) do @@ -582,6 +1666,9 @@ begin 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); @@ -591,18 +1678,27 @@ 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]; - if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength); - 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^); if ID > High(NetClients) then Exit; + clearNetClient(NetClients[ID]); TC := @NetClients[ID]; if TC = nil then Exit; @@ -624,21 +1720,31 @@ begin TC^.Peer := nil; TC^.Player := 0; TC^.RequestedFullUpdate := False; + TC^.WaitForFirstSpawn := False; + TC^.NetOut[NET_UNRELIABLE].Free(); + TC^.NetOut[NET_RELIABLE].Free(); FreeMemory(NetEvent.peer^.data); NetEvent.peer^.data := nil; g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_HOST_DISC], [ID])); Dec(NetClientCount); - if NetUseMaster then g_Net_Slist_Update; + if NetUseMaster then + begin + //g_Net_Slist_Update; + g_Net_Slist_Pulse(); + end; end; end; end; end; -{ /// CLIENT FUNCTIONS /// } - +//************************************************************************** +// +// CLIENT FUNCTIONS +// +//************************************************************************** procedure g_Net_Disconnect(Forced: Boolean = False); begin @@ -688,21 +1794,20 @@ 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 - F := LongWord(ENET_PACKET_FLAG_RELIABLE) + T := NET_RELIABLE else - F := 0; + T := NET_UNRELIABLE; - P := enet_packet_create(NetOut.Data, NetOut.CurSize, 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); if NetDump then g_Net_DumpSendBuffer(); - g_Net_Flush(); NetOut.Clear(); + g_Net_Flush(); // FIXME: for now, send immediately end; function g_Net_Client_Update(): enet_size_t; @@ -713,8 +1818,9 @@ begin case NetEvent.kind of ENET_EVENT_TYPE_RECEIVE: 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_ClientMsgHandler(NetEvent.packet); + g_Net_Client_HandlePacket(NetEvent.packet, g_Net_ClientMsgHandler); end; ENET_EVENT_TYPE_DISCONNECT: @@ -735,8 +1841,9 @@ begin case NetEvent.kind of ENET_EVENT_TYPE_RECEIVE: 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_ClientLightMsgHandler(NetEvent.packet); + g_Net_Client_HandlePacket(NetEvent.packet, g_Net_ClientLightMsgHandler); end; ENET_EVENT_TYPE_DISCONNECT: @@ -753,6 +1860,7 @@ end; function g_Net_Connect(IP: string; Port: enet_uint16): Boolean; var OuterLoop: Boolean; + TimeoutTime, T: Int64; begin if NetMode <> NET_NONE then begin @@ -801,6 +1909,9 @@ begin Exit; end; + // предупредить что ждем слишком долго через N секунд + TimeoutTime := sys_GetTicks() + NET_CONNECT_TIMEOUT; + OuterLoop := True; while OuterLoop do begin @@ -820,13 +1931,23 @@ begin end; end; + T := sys_GetTicks(); + 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) or e_KeyPressed(VK_ESCAPE) 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); + 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 @@ -936,61 +2057,6 @@ begin enet_host_flush(NetHost); end; -function g_Net_Wait_Event(msgId: Word): TMemoryStream; -var - downloadEvent: ENetEvent; - OuterLoop: Boolean; - MID: Byte; - Ptr: Pointer; - msgStream: TMemoryStream; -begin - FillChar(downloadEvent, SizeOf(downloadEvent), 0); - 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 - begin - Ptr := downloadEvent.packet^.data; - - MID := Byte(Ptr^); - - if (MID = msgId) then - 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); - 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; - end; - - ProcessLoading(true); - - if e_KeyPressed(IK_ESCAPE) or e_KeyPressed(IK_SPACE) or e_KeyPressed(VK_ESCAPE) then - break; - end; - Result := msgStream; -end; - function g_Net_IsHostBanned(IP: LongWord; Perm: Boolean = False): Boolean; var I: Integer; @@ -1254,14 +2320,19 @@ begin end; {$ENDIF} -initialization +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); + trans_omsg.Alloc(NET_BUFSIZE); finalization - NetIn.Free(); NetOut.Free(); - + NetBuf[NET_UNRELIABLE].Free(); + NetBuf[NET_RELIABLE].Free(); end.