DEADSOFTWARE

net: don't ban people for timing out by default
[d2df-sdl.git] / src / game / g_net.pas
1 (* Copyright (C) Doom 2D: Forever Developers
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, version 3 of the License ONLY.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 *)
15 {$INCLUDE ../shared/a_modes.inc}
16 unit g_net;
18 interface
20 uses
21 e_log, e_msg, utils, ENet, Classes, md5, MAPDEF{$IFDEF USE_MINIUPNPC}, miniupnpc;{$ELSE};{$ENDIF}
23 const
24 NET_PROTOCOL_VER = 188;
26 NET_MAXCLIENTS = 24;
27 NET_CHANS = 12;
29 NET_CHAN_SERVICE = 0;
30 NET_CHAN_IMPORTANT = 1;
31 NET_CHAN_GAME = 2;
32 NET_CHAN_PLAYER = 3;
33 NET_CHAN_PLAYERPOS = 4;
34 NET_CHAN_MONSTER = 5;
35 NET_CHAN_MONSTERPOS = 6;
36 NET_CHAN_LARGEDATA = 7;
37 NET_CHAN_CHAT = 8;
38 NET_CHAN_DOWNLOAD = 9;
39 NET_CHAN_SHOTS = 10;
40 NET_CHAN_DOWNLOAD_EX = 11;
42 NET_NONE = 0;
43 NET_SERVER = 1;
44 NET_CLIENT = 2;
46 NET_BUFSIZE = $FFFF;
47 NET_PING_PORT = $DF2D;
49 NET_EVERYONE = -1;
51 NET_UNRELIABLE = 0;
52 NET_RELIABLE = 1;
54 NET_DISC_NONE: enet_uint32 = 0;
55 NET_DISC_PROTOCOL: enet_uint32 = 1;
56 NET_DISC_VERSION: enet_uint32 = 2;
57 NET_DISC_FULL: enet_uint32 = 3;
58 NET_DISC_KICK: enet_uint32 = 4;
59 NET_DISC_DOWN: enet_uint32 = 5;
60 NET_DISC_PASSWORD: enet_uint32 = 6;
61 NET_DISC_TEMPBAN: enet_uint32 = 7;
62 NET_DISC_BAN: enet_uint32 = 8;
63 NET_DISC_MAX: enet_uint32 = 8;
64 NET_DISC_FILE_TIMEOUT: enet_uint32 = 13;
66 NET_STATE_NONE = 0;
67 NET_STATE_AUTH = 1;
68 NET_STATE_GAME = 2;
70 NET_CONNECT_TIMEOUT = 1000 * 10;
72 BANLIST_FILENAME = 'banlist.txt';
73 NETDUMP_FILENAME = 'netdump';
75 type
76 TNetMapResourceInfo = record
77 wadName: AnsiString; // wad file name, without a path
78 size: Integer; // wad file size (-1: size and hash are not known)
79 hash: TMD5Digest; // wad hash
80 end;
82 TNetMapResourceInfoArray = array of TNetMapResourceInfo;
84 TNetFileTransfer = record
85 diskName: string;
86 hash: TMD5Digest;
87 stream: TStream;
88 size: Integer; // file size in bytes
89 chunkSize: Integer;
90 lastSentChunk: Integer;
91 lastAckChunk: Integer;
92 lastAckTime: Int64; // msecs; if not "in progress", we're waiting for the first ack
93 inProgress: Boolean;
94 diskBuffer: PChar; // of `chunkSize` bytes
95 resumed: Boolean;
96 end;
98 TNetClient = record
99 ID: Byte;
100 Used: Boolean;
101 State: Byte;
102 Peer: pENetPeer;
103 Player: Word;
104 RequestedFullUpdate: Boolean;
105 WaitForFirstSpawn: Boolean; // set to `true` in server, used to spawn a player on first full state request
106 RCONAuth: Boolean;
107 Voted: Boolean;
108 Crimes: Integer;
109 AuthTime: LongWord;
110 MsgTime: LongWord;
111 Transfer: TNetFileTransfer; // only one transfer may be active
112 NetOut: array [0..1] of TMsg;
113 end;
114 TBanRecord = record
115 IP: LongWord;
116 Perm: Boolean;
117 end;
118 pTNetClient = ^TNetClient;
120 AByte = array of Byte;
122 var
123 NetInitDone: Boolean = False;
124 NetMode: Byte = NET_NONE;
125 NetDump: Boolean = False;
127 NetServerName: string = 'Unnamed Server';
128 NetPassword: string = '';
129 NetPort: Word = 25666;
131 NetAllowRCON: Boolean = False;
132 NetRCONPassword: string = '';
134 NetTimeToUpdate: Cardinal = 0;
135 NetTimeToReliable: Cardinal = 0;
136 NetTimeToMaster: Cardinal = 0;
138 NetHost: pENetHost = nil;
139 NetPeer: pENetPeer = nil;
140 NetEvent: ENetEvent;
141 NetAddr: ENetAddress;
143 NetPongAddr: ENetAddress;
144 NetPongSock: ENetSocket = ENET_SOCKET_NULL;
146 NetUseMaster: Boolean = True;
147 NetMasterList: string = 'mpms.doom2d.org:25665, deadsoftware.ru:25665';
149 NetClientIP: string = '127.0.0.1';
150 NetClientPort: Word = 25666;
152 NetIn, NetOut: TMsg;
153 NetBuf: array [0..1] of TMsg;
155 NetClients: array of TNetClient;
156 NetClientCount: Byte = 0;
157 NetMaxClients: Byte = 255;
158 NetBannedHosts: array of TBanRecord;
160 NetAutoBanLimit: Integer = 5;
161 NetAutoBanPerm: Boolean = True;
162 NetAutoBanWarn: Boolean = False;
163 NetAutoBanForTimeout: Boolean = False;
165 NetAuthTimeout: Integer = 30 * 1000;
166 NetPacketTimeout: Integer = 60 * 1000;
168 NetState: Integer = NET_STATE_NONE;
170 NetMyID: Integer = -1;
171 NetPlrUID1: Integer = -1;
172 NetPlrUID2: Integer = -1;
174 NetInterpLevel: Integer = 1;
175 NetUpdateRate: Cardinal = 0; // as soon as possible
176 NetRelupdRate: Cardinal = 18; // around two times a second
177 NetMasterRate: Cardinal = 60000;
179 NetForcePlayerUpdate: Boolean = False;
180 NetPredictSelf: Boolean = True;
181 NetForwardPorts: Boolean = False;
183 NetGotEverything: Boolean = False;
184 NetGotKeys: Boolean = False;
186 NetDeafLevel: Integer = 0;
188 {$IFDEF USE_MINIUPNPC}
189 NetPortForwarded: Word = 0;
190 NetPongForwarded: Boolean = False;
191 NetIGDControl: AnsiString;
192 NetIGDService: TURLStr;
193 {$ENDIF}
195 NetPortThread: TThreadID = NilThreadId;
197 NetDumpFile: TStream;
199 g_Res_received_map_start: Integer = 0; // set if we received "map change" event
202 function g_Net_Init(): Boolean;
203 procedure g_Net_Cleanup();
204 procedure g_Net_Free();
205 procedure g_Net_Flush();
207 function g_Net_Host(IPAddr: LongWord; Port: enet_uint16; MaxClients: Cardinal = 16): Boolean;
208 procedure g_Net_Host_Die();
209 procedure g_Net_Host_Send(ID: Integer; Reliable: Boolean; Chan: Byte = NET_CHAN_GAME);
210 function g_Net_Host_Update(): enet_size_t;
212 function g_Net_Connect(IP: string; Port: enet_uint16): Boolean;
213 procedure g_Net_Disconnect(Forced: Boolean = False);
214 procedure g_Net_Client_Send(Reliable: Boolean; Chan: Byte = NET_CHAN_GAME);
215 function g_Net_Client_Update(): enet_size_t;
216 function g_Net_Client_UpdateWhileLoading(): enet_size_t;
218 function g_Net_Client_ByName(Name: string): pTNetClient;
219 function g_Net_Client_ByPlayer(PID: Word): pTNetClient;
220 function g_Net_ClientName_ByID(ID: Integer): string;
222 procedure g_Net_SendData(Data: AByte; peer: pENetPeer; Reliable: Boolean; Chan: Byte = NET_CHAN_DOWNLOAD);
223 //function g_Net_Wait_Event(msgId: Word): TMemoryStream;
224 //function g_Net_Wait_FileInfo (var tf: TNetFileTransfer; asMap: Boolean; out resList: TStringList): Integer;
226 function IpToStr(IP: LongWord): string;
227 function StrToIp(IPstr: string; var IP: LongWord): Boolean;
229 function g_Net_IsHostBanned(IP: LongWord; Perm: Boolean = False): Boolean;
230 procedure g_Net_BanHost(IP: LongWord; Perm: Boolean = True); overload;
231 procedure g_Net_BanHost(IP: string; Perm: Boolean = True); overload;
232 function g_Net_UnbanHost(IP: string): Boolean; overload;
233 function g_Net_UnbanHost(IP: LongWord): Boolean; overload;
234 procedure g_Net_UnbanNonPermHosts();
235 procedure g_Net_SaveBanList();
237 procedure g_Net_Penalize(C: pTNetClient; Reason: string);
239 procedure g_Net_DumpStart();
240 procedure g_Net_DumpSendBuffer();
241 procedure g_Net_DumpRecvBuffer(Buf: penet_uint8; Len: LongWord);
242 procedure g_Net_DumpEnd();
244 function g_Net_ForwardPorts(ForwardPongPort: Boolean = True): Boolean;
245 procedure g_Net_UnforwardPorts();
247 function g_Net_UserRequestExit: Boolean;
249 function g_Net_Wait_MapInfo (var tf: TNetFileTransfer; var resList: TNetMapResourceInfoArray): Integer;
250 function g_Net_RequestResFileInfo (resIndex: LongInt; out tf: TNetFileTransfer): Integer;
251 function g_Net_AbortResTransfer (var tf: TNetFileTransfer): Boolean;
252 function g_Net_ReceiveResourceFile (resIndex: LongInt; var tf: TNetFileTransfer; strm: TStream): Integer;
254 function g_Net_IsNetworkAvailable (): Boolean;
255 procedure g_Net_InitLowLevel ();
256 procedure g_Net_DeinitLowLevel ();
258 procedure NetServerCVars(P: SSArray);
261 implementation
263 // *enet_host_service()*
264 // fuck! https://www.mail-archive.com/enet-discuss@cubik.org/msg00852.html
265 // tl;dr: on shitdows, we can get -1 sometimes, and it is *NOT* a failure.
266 // thank you, enet. let's ignore failures altogether then.
268 uses
269 SysUtils,
270 e_input, e_res,
271 g_nethandler, g_netmsg, g_netmaster, g_player, g_window, g_console,
272 g_main, g_game, g_language, g_weapons, ctypes, g_system, g_map;
274 const
275 FILE_CHUNK_SIZE = 8192;
277 var
278 enet_init_success: Boolean = false;
279 g_Net_DownloadTimeout: Single;
280 trans_omsg: TMsg;
283 function g_Net_IsNetworkAvailable (): Boolean;
284 begin
285 result := enet_init_success;
286 end;
288 procedure g_Net_InitLowLevel ();
289 var v: ENetVersion;
290 begin
291 v := enet_linked_version();
292 e_LogWritefln('ENet Version: %s.%s.%s', [ENET_VERSION_GET_MAJOR(v), ENET_VERSION_GET_MINOR(v), ENET_VERSION_GET_PATCH(v)]);
293 if enet_init_success then raise Exception.Create('wuta?!');
294 enet_init_success := (enet_initialize() = 0);
295 end;
297 procedure g_Net_DeinitLowLevel ();
298 begin
299 if enet_init_success then
300 begin
301 enet_deinitialize();
302 enet_init_success := false;
303 end;
304 end;
307 //**************************************************************************
308 //
309 // SERVICE FUNCTIONS
310 //
311 //**************************************************************************
313 procedure clearNetClientTransfers (var nc: TNetClient);
314 begin
315 nc.Transfer.stream.Free;
316 nc.Transfer.diskName := ''; // just in case
317 if (nc.Transfer.diskBuffer <> nil) then FreeMem(nc.Transfer.diskBuffer);
318 nc.Transfer.stream := nil;
319 nc.Transfer.diskBuffer := nil;
320 end;
323 procedure clearNetClient (var nc: TNetClient);
324 begin
325 clearNetClientTransfers(nc);
326 end;
329 procedure clearNetClients (clearArray: Boolean);
330 var
331 f: Integer;
332 begin
333 for f := Low(NetClients) to High(NetClients) do clearNetClient(NetClients[f]);
334 if (clearArray) then SetLength(NetClients, 0);
335 end;
338 function g_Net_UserRequestExit (): Boolean;
339 begin
340 Result := {e_KeyPressed(IK_SPACE) or}
341 e_KeyPressed(IK_ESCAPE) or
342 e_KeyPressed(VK_ESCAPE) or
343 e_KeyPressed(JOY0_JUMP) or
344 e_KeyPressed(JOY1_JUMP) or
345 e_KeyPressed(JOY2_JUMP) or
346 e_KeyPressed(JOY3_JUMP)
347 end;
349 //**************************************************************************
350 //
351 // file transfer declaraions and host packet processor
352 //
353 //**************************************************************************
355 const
356 // server packet type
357 NTF_SERVER_DONE = 10; // done with this file
358 NTF_SERVER_FILE_INFO = 11; // sent after client request
359 NTF_SERVER_CHUNK = 12; // next chunk; chunk number follows
360 NTF_SERVER_ABORT = 13; // server abort
361 NTF_SERVER_MAP_INFO = 14;
363 // client packet type
364 NTF_CLIENT_MAP_REQUEST = 100; // map file request; also, returns list of additional wads to download
365 NTF_CLIENT_FILE_REQUEST = 101; // resource file request (by index)
366 NTF_CLIENT_ABORT = 102; // do not send requested file, or abort current transfer
367 NTF_CLIENT_START = 103; // start transfer; client may resume download by sending non-zero starting chunk
368 NTF_CLIENT_ACK = 104; // chunk ack; chunk number follows
371 // disconnect client due to some file transfer error
372 procedure killClientByFT (var nc: TNetClient);
373 begin
374 e_LogWritefln('disconnected client #%d due to file transfer error', [nc.ID], TMsgType.Warning);
375 enet_peer_disconnect(nc.Peer, NET_DISC_FILE_TIMEOUT);
376 clearNetClientTransfers(nc);
377 g_Net_Slist_ServerPlayerLeaves();
378 end;
381 // send file transfer message from server to client
382 function ftransSendServerMsg (var nc: TNetClient; var m: TMsg): Boolean;
383 var
384 pkt: PENetPacket;
385 begin
386 result := false;
387 if (m.CurSize < 1) then exit;
388 pkt := enet_packet_create(m.Data, m.CurSize, ENET_PACKET_FLAG_RELIABLE);
389 if not Assigned(pkt) then begin killClientByFT(nc); exit; end;
390 if (enet_peer_send(nc.Peer, NET_CHAN_DOWNLOAD_EX, pkt) <> 0) then begin killClientByFT(nc); exit; end;
391 result := true;
392 end;
395 // send file transfer message from client to server
396 function ftransSendClientMsg (var m: TMsg): Boolean;
397 var
398 pkt: PENetPacket;
399 begin
400 result := false;
401 if (m.CurSize < 1) then exit;
402 pkt := enet_packet_create(m.Data, m.CurSize, ENET_PACKET_FLAG_RELIABLE);
403 if not Assigned(pkt) then exit;
404 if (enet_peer_send(NetPeer, NET_CHAN_DOWNLOAD_EX, pkt) <> 0) then exit;
405 result := true;
406 end;
409 // file chunk sender
410 procedure ProcessChunkSend (var nc: TNetClient);
411 var
412 tf: ^TNetFileTransfer;
413 ct: Int64;
414 chunks: Integer;
415 rd: Integer;
416 begin
417 tf := @nc.Transfer;
418 if (tf.stream = nil) then exit;
419 ct := GetTimerMS();
420 // arbitrary timeout number
421 if (ct-tf.lastAckTime >= 5000) then
422 begin
423 killClientByFT(nc);
424 exit;
425 end;
426 // check if we need to send something
427 if (not tf.inProgress) then exit; // waiting for the initial ack
428 // ok, we're sending chunks
429 if (tf.lastAckChunk <> tf.lastSentChunk) then exit;
430 Inc(tf.lastSentChunk);
431 // do it one chunk at a time; client ack will advance our chunk counter
432 chunks := (tf.size+tf.chunkSize-1) div tf.chunkSize;
434 if (tf.lastSentChunk > chunks) then
435 begin
436 killClientByFT(nc);
437 exit;
438 end;
440 trans_omsg.Clear();
441 if (tf.lastSentChunk = chunks) then
442 begin
443 // we're done with this file
444 e_LogWritefln('download: client #%d, DONE sending chunks #%d/#%d', [nc.ID, tf.lastSentChunk, chunks]);
445 trans_omsg.Write(Byte(NTF_SERVER_DONE));
446 clearNetClientTransfers(nc);
447 end
448 else
449 begin
450 // packet type
451 trans_omsg.Write(Byte(NTF_SERVER_CHUNK));
452 trans_omsg.Write(LongInt(tf.lastSentChunk));
453 // read chunk
454 rd := tf.size-(tf.lastSentChunk*tf.chunkSize);
455 if (rd > tf.chunkSize) then rd := tf.chunkSize;
456 trans_omsg.Write(LongInt(rd));
457 //e_LogWritefln('download: client #%d, sending chunk #%d/#%d (%d bytes)', [nc.ID, tf.lastSentChunk, chunks, rd]);
458 //FIXME: check for errors here
459 try
460 tf.stream.Seek(tf.lastSentChunk*tf.chunkSize, soFromBeginning);
461 tf.stream.ReadBuffer(tf.diskBuffer^, rd);
462 trans_omsg.WriteData(tf.diskBuffer, rd);
463 except // sorry
464 killClientByFT(nc);
465 exit;
466 end;
467 end;
468 // send packet
469 ftransSendServerMsg(nc, trans_omsg);
470 end;
473 // server file transfer packet processor
474 // received packet is in `NetEvent`
475 procedure ProcessDownloadExPacket ();
476 var
477 f: Integer;
478 nc: ^TNetClient;
479 nid: Integer = -1;
480 msg: TMsg;
481 cmd: Byte;
482 tf: ^TNetFileTransfer;
483 fname: string;
484 chunk: Integer;
485 ridx: Integer;
486 dfn: AnsiString;
487 md5: TMD5Digest;
488 //st: TStream;
489 size: LongInt;
490 fi: TDiskFileInfo;
491 begin
492 // find client index by peer
493 for f := Low(NetClients) to High(NetClients) do
494 begin
495 if (not NetClients[f].Used) then continue;
496 if (NetClients[f].Peer = NetEvent.peer) then
497 begin
498 nid := f;
499 break;
500 end;
501 end;
502 //e_LogWritefln('RECEIVE: dlpacket; client=%d (datalen=%u)', [nid, NetEvent.packet^.dataLength]);
504 if (nid < 0) then exit; // wtf?!
505 nc := @NetClients[nid];
507 if (NetEvent.packet^.dataLength = 0) then
508 begin
509 killClientByFT(nc^);
510 exit;
511 end;
513 // don't time out clients during a file transfer
514 if (NetAuthTimeout > 0) then
515 nc^.AuthTime := gTime + NetAuthTimeout;
516 if (NetPacketTimeout > 0) then
517 nc^.MsgTime := gTime + NetPacketTimeout;
519 tf := @NetClients[nid].Transfer;
520 tf.lastAckTime := GetTimerMS();
522 cmd := Byte(NetEvent.packet^.data^);
523 //e_LogWritefln('RECEIVE: nid=%d; cmd=%u', [nid, cmd]);
524 case cmd of
525 NTF_CLIENT_FILE_REQUEST: // file request
526 begin
527 if (tf.stream <> nil) then
528 begin
529 killClientByFT(nc^);
530 exit;
531 end;
532 if (NetEvent.packet^.dataLength < 2) then
533 begin
534 killClientByFT(nc^);
535 exit;
536 end;
537 // new transfer request; build packet
538 if not msg.Init(NetEvent.packet^.data+1, NetEvent.packet^.dataLength-1, True) then
539 begin
540 killClientByFT(nc^);
541 exit;
542 end;
543 // get resource index
544 ridx := msg.ReadLongInt();
545 if (ridx < -1) or (ridx >= length(gExternalResources)) then
546 begin
547 e_LogWritefln('Invalid resource index %d', [ridx], TMsgType.Warning);
548 killClientByFT(nc^);
549 exit;
550 end;
551 if (ridx < 0) then fname := gGameSettings.WAD else fname := gExternalResources[ridx].diskName;
552 if (length(fname) = 0) then
553 begin
554 e_WriteLog('Invalid filename: '+fname, TMsgType.Warning);
555 killClientByFT(nc^);
556 exit;
557 end;
558 tf.diskName := findDiskWad(fname);
559 if (length(tf.diskName) = 0) then
560 begin
561 e_LogWritefln('NETWORK: file "%s" not found!', [fname], TMsgType.Fatal);
562 killClientByFT(nc^);
563 exit;
564 end;
565 // calculate hash
566 //tf.hash := MD5File(tf.diskName);
567 if (ridx < 0) then tf.hash := gWADHash else tf.hash := gExternalResources[ridx].hash;
568 // create file stream
569 tf.diskName := findDiskWad(fname);
570 try
571 tf.stream := openDiskFileRO(tf.diskName);
572 except
573 tf.stream := nil;
574 end;
575 if (tf.stream = nil) then
576 begin
577 e_WriteLog(Format('NETWORK: file "%s" not found!', [fname]), TMsgType.Fatal);
578 killClientByFT(nc^);
579 exit;
580 end;
581 e_LogWritefln('client #%d requested resource #%d (file is `%s` : `%s`)', [nc.ID, ridx, fname, tf.diskName]);
582 tf.size := tf.stream.size;
583 tf.chunkSize := FILE_CHUNK_SIZE; // arbitrary
584 tf.lastSentChunk := -1;
585 tf.lastAckChunk := -1;
586 tf.lastAckTime := GetTimerMS();
587 tf.inProgress := False; // waiting for the first ACK or for the cancel
588 GetMem(tf.diskBuffer, tf.chunkSize);
589 // sent file info message
590 trans_omsg.Clear();
591 trans_omsg.Write(Byte(NTF_SERVER_FILE_INFO));
592 trans_omsg.Write(tf.hash);
593 trans_omsg.Write(tf.size);
594 trans_omsg.Write(tf.chunkSize);
595 trans_omsg.Write(ExtractFileName(fname));
596 if not ftransSendServerMsg(nc^, trans_omsg) then exit;
597 end;
598 NTF_CLIENT_ABORT: // do not send requested file, or abort current transfer
599 begin
600 e_LogWritefln('client #%d aborted file transfer', [nc.ID]);
601 clearNetClientTransfers(nc^);
602 end;
603 NTF_CLIENT_START: // start transfer; client may resume download by sending non-zero starting chunk
604 begin
605 if not Assigned(tf.stream) then
606 begin
607 killClientByFT(nc^);
608 exit;
609 end;
610 if (tf.lastSentChunk <> -1) or (tf.lastAckChunk <> -1) or (tf.inProgress) then
611 begin
612 // double ack, get lost
613 killClientByFT(nc^);
614 exit;
615 end;
616 if (NetEvent.packet^.dataLength < 2) then
617 begin
618 killClientByFT(nc^);
619 exit;
620 end;
621 // build packet
622 if not msg.Init(NetEvent.packet^.data+1, NetEvent.packet^.dataLength-1, True) then
623 begin
624 killClientByFT(nc^);
625 exit;
626 end;
627 chunk := msg.ReadLongInt();
628 if (chunk < 0) or (chunk > (tf.size+tf.chunkSize-1) div tf.chunkSize) then
629 begin
630 killClientByFT(nc^);
631 exit;
632 end;
633 e_LogWritefln('client #%d started file transfer from chunk %d', [nc.ID, chunk]);
634 // start sending chunks
635 tf.inProgress := True;
636 tf.lastSentChunk := chunk-1;
637 tf.lastAckChunk := chunk-1;
638 ProcessChunkSend(nc^);
639 end;
640 NTF_CLIENT_ACK: // chunk ack; chunk number follows
641 begin
642 if not Assigned(tf.stream) then
643 begin
644 killClientByFT(nc^);
645 exit;
646 end;
647 if (tf.lastSentChunk < 0) or (not tf.inProgress) then
648 begin
649 // double ack, get lost
650 killClientByFT(nc^);
651 exit;
652 end;
653 if (NetEvent.packet^.dataLength < 2) then
654 begin
655 killClientByFT(nc^);
656 exit;
657 end;
658 // build packet
659 if not msg.Init(NetEvent.packet^.data+1, NetEvent.packet^.dataLength-1, True) then
660 begin
661 killClientByFT(nc^);
662 exit;
663 end;
664 chunk := msg.ReadLongInt();
665 if (chunk < 0) or (chunk > (tf.size+tf.chunkSize-1) div tf.chunkSize) then
666 begin
667 killClientByFT(nc^);
668 exit;
669 end;
670 // do it this way, so client may seek, or request retransfers for some reason
671 tf.lastAckChunk := chunk;
672 tf.lastSentChunk := chunk;
673 //e_LogWritefln('client #%d acked file transfer chunk %d', [nc.ID, chunk]);
674 ProcessChunkSend(nc^);
675 end;
676 NTF_CLIENT_MAP_REQUEST:
677 begin
678 e_LogWritefln('client #%d requested map info', [nc.ID]);
679 trans_omsg.Clear();
680 dfn := findDiskWad(gGameSettings.WAD);
681 if (dfn = '') then dfn := '!wad_not_found!.wad'; //FIXME
682 //md5 := MD5File(dfn);
683 md5 := gWADHash;
684 if (not GetDiskFileInfo(dfn, fi)) then
685 begin
686 e_LogWritefln('client #%d requested map info, but i cannot get file info', [nc.ID]);
687 killClientByFT(nc^);
688 exit;
689 end;
690 size := fi.size;
692 st := openDiskFileRO(dfn);
693 if not assigned(st) then exit; //wtf?!
694 size := st.size;
695 st.Free;
697 // packet type
698 trans_omsg.Write(Byte(NTF_SERVER_MAP_INFO));
699 // map wad name
700 trans_omsg.Write(ExtractFileName(gGameSettings.WAD));
701 // map wad md5
702 trans_omsg.Write(md5);
703 // map wad size
704 trans_omsg.Write(size);
705 // number of external resources for map
706 trans_omsg.Write(LongInt(length(gExternalResources)));
707 // external resource names
708 for f := 0 to High(gExternalResources) do
709 begin
710 // old style packet
711 //trans_omsg.Write(ExtractFileName(gExternalResources[f])); // GameDir+'/wads/'+ResList.Strings[i]
712 // new style packet
713 trans_omsg.Write('!');
714 trans_omsg.Write(LongInt(gExternalResources[f].size));
715 trans_omsg.Write(gExternalResources[f].hash);
716 trans_omsg.Write(ExtractFileName(gExternalResources[f].diskName));
717 end;
718 // send packet
719 if not ftransSendServerMsg(nc^, trans_omsg) then exit;
720 end;
721 else
722 begin
723 killClientByFT(nc^);
724 exit;
725 end;
726 end;
727 end;
730 //**************************************************************************
731 //
732 // file transfer crap (both client and server)
733 //
734 //**************************************************************************
736 function getNewTimeoutEnd (): Int64;
737 begin
738 result := GetTimerMS();
739 if (g_Net_DownloadTimeout <= 0) then
740 begin
741 result := result+1000*60*3; // 3 minutes
742 end
743 else
744 begin
745 result := result+trunc(g_Net_DownloadTimeout*1000);
746 end;
747 end;
750 // send map request to server, and wait for "map info" server reply
751 //
752 // returns `false` on error or user abort
753 // fills:
754 // diskName: map wad file name (without a path)
755 // hash: map wad hash
756 // size: map wad size
757 // chunkSize: set too
758 // resList: list of resource wads
759 // returns:
760 // <0 on error
761 // 0 on success
762 // 1 on user abort
763 // 2 on server abort
764 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
765 function g_Net_Wait_MapInfo (var tf: TNetFileTransfer; var resList: TNetMapResourceInfoArray): Integer;
766 var
767 ev: ENetEvent;
768 rMsgId: Byte;
769 Ptr: Pointer;
770 msg: TMsg;
771 freePacket: Boolean = false;
772 ct, ett: Int64;
773 status: cint;
774 s: AnsiString;
775 rc, f: LongInt;
776 ri: ^TNetMapResourceInfo;
777 begin
778 SetLength(resList, 0);
780 // send request
781 trans_omsg.Clear();
782 trans_omsg.Write(Byte(NTF_CLIENT_MAP_REQUEST));
783 if not ftransSendClientMsg(trans_omsg) then begin result := -1; exit; end;
785 FillChar(ev, SizeOf(ev), 0);
786 Result := -1;
787 try
788 ett := getNewTimeoutEnd();
789 repeat
790 status := enet_host_service(NetHost, @ev, 300);
792 if (status < 0) then
793 begin
794 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True);
795 Result := -1;
796 exit;
797 end;
799 if (status <= 0) then
800 begin
801 // check for timeout
802 ct := GetTimerMS();
803 if (ct >= ett) then
804 begin
805 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' timeout reached', True);
806 Result := -1;
807 exit;
808 end;
809 end
810 else
811 begin
812 // some event
813 case ev.kind of
814 ENET_EVENT_TYPE_RECEIVE:
815 begin
816 freePacket := true;
817 if (ev.channelID <> NET_CHAN_DOWNLOAD_EX) then
818 begin
819 //e_LogWritefln('g_Net_Wait_MapInfo: skip message from non-transfer channel', []);
820 freePacket := false;
821 g_Net_Client_HandlePacket(ev.packet, g_Net_ClientLightMsgHandler);
822 if (g_Res_received_map_start < 0) then begin result := -666; exit; end;
823 end
824 else
825 begin
826 ett := getNewTimeoutEnd();
827 if (ev.packet.dataLength < 1) then
828 begin
829 e_LogWritefln('g_Net_Wait_MapInfo: invalid server packet (no data)', []);
830 Result := -1;
831 exit;
832 end;
833 Ptr := ev.packet^.data;
834 rMsgId := Byte(Ptr^);
835 e_LogWritefln('g_Net_Wait_MapInfo: got message %u from server (dataLength=%u)', [rMsgId, ev.packet^.dataLength]);
836 if (rMsgId = NTF_SERVER_FILE_INFO) then
837 begin
838 e_LogWritefln('g_Net_Wait_MapInfo: waiting for map info reply, but got file info reply', []);
839 Result := -1;
840 exit;
841 end
842 else if (rMsgId = NTF_SERVER_ABORT) then
843 begin
844 e_LogWritefln('g_Net_Wait_MapInfo: server aborted transfer', []);
845 Result := 2;
846 exit;
847 end
848 else if (rMsgId = NTF_SERVER_MAP_INFO) then
849 begin
850 e_LogWritefln('g_Net_Wait_MapInfo: creating map info packet...', []);
851 if not msg.Init(ev.packet^.data+1, ev.packet^.dataLength-1, True) then exit;
852 e_LogWritefln('g_Net_Wait_MapInfo: parsing map info packet (rd=%d; max=%d)...', [msg.ReadCount, msg.MaxSize]);
853 SetLength(resList, 0); // just in case
854 // map wad name
855 tf.diskName := msg.ReadString();
856 e_LogWritefln('g_Net_Wait_MapInfo: map wad is `%s`', [tf.diskName]);
857 // map wad md5
858 tf.hash := msg.ReadMD5();
859 // map wad size
860 tf.size := msg.ReadLongInt();
861 e_LogWritefln('g_Net_Wait_MapInfo: map wad size is %d', [tf.size]);
862 // number of external resources for map
863 rc := msg.ReadLongInt();
864 if (rc < 0) or (rc > 1024) then
865 begin
866 e_LogWritefln('g_Net_Wait_Event: invalid number of map external resources (%d)', [rc]);
867 Result := -1;
868 exit;
869 end;
870 e_LogWritefln('g_Net_Wait_MapInfo: map external resource count is %d', [rc]);
871 SetLength(resList, rc);
872 // external resource names
873 for f := 0 to rc-1 do
874 begin
875 ri := @resList[f];
876 s := msg.ReadString();
877 if (length(s) = 0) then begin result := -1; exit; end;
878 if (s = '!') then
879 begin
880 // extended packet
881 ri.size := msg.ReadLongInt();
882 ri.hash := msg.ReadMD5();
883 ri.wadName := ExtractFileName(msg.ReadString());
884 if (length(ri.wadName) = 0) or (ri.size < 0) then begin result := -1; exit; end;
885 end
886 else
887 begin
888 // old-style packet, only name
889 ri.wadName := ExtractFileName(s);
890 if (length(ri.wadName) = 0) then begin result := -1; exit; end;
891 ri.size := -1; // unknown
892 end;
893 end;
894 e_LogWritefln('g_Net_Wait_MapInfo: got map info', []);
895 Result := 0; // success
896 exit;
897 end
898 else
899 begin
900 e_LogWritefln('g_Net_Wait_Event: invalid server packet type', []);
901 Result := -1;
902 exit;
903 end;
904 end;
905 end;
906 ENET_EVENT_TYPE_DISCONNECT:
907 begin
908 if (ev.data <= NET_DISC_MAX) then
909 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' ' + _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + ev.data)], True);
910 Result := -1;
911 exit;
912 end;
913 else
914 begin
915 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' unknown ENet event ' + IntToStr(Ord(ev.kind)), True);
916 result := -1;
917 exit;
918 end;
919 end;
920 if (freePacket) then begin freePacket := false; enet_packet_destroy(ev.packet); end;
921 end;
922 ProcessLoading();
923 if g_Net_UserRequestExit() then
924 begin
925 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' user abort', True);
926 Result := 1;
927 exit;
928 end;
929 until false;
930 finally
931 if (freePacket) then enet_packet_destroy(ev.packet);
932 end;
933 end;
936 // send file request to server, and wait for server reply
937 //
938 // returns `false` on error or user abort
939 // fills:
940 // diskName (actually, base name)
941 // hash
942 // size
943 // chunkSize
944 // returns:
945 // <0 on error
946 // 0 on success
947 // 1 on user abort
948 // 2 on server abort
949 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
950 function g_Net_RequestResFileInfo (resIndex: LongInt; out tf: TNetFileTransfer): Integer;
951 var
952 ev: ENetEvent;
953 rMsgId: Byte;
954 Ptr: Pointer;
955 msg: TMsg;
956 freePacket: Boolean = false;
957 ct, ett: Int64;
958 status: cint;
959 begin
960 // send request
961 trans_omsg.Clear();
962 trans_omsg.Write(Byte(NTF_CLIENT_FILE_REQUEST));
963 trans_omsg.Write(resIndex);
964 if not ftransSendClientMsg(trans_omsg) then begin result := -1; exit; end;
966 FillChar(ev, SizeOf(ev), 0);
967 Result := -1;
968 try
969 ett := getNewTimeoutEnd();
970 repeat
971 status := enet_host_service(NetHost, @ev, 300);
973 if (status < 0) then
974 begin
975 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True);
976 Result := -1;
977 exit;
978 end;
980 if (status <= 0) then
981 begin
982 // check for timeout
983 ct := GetTimerMS();
984 if (ct >= ett) then
985 begin
986 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' timeout reached', True);
987 Result := -1;
988 exit;
989 end;
990 end
991 else
992 begin
993 // some event
994 case ev.kind of
995 ENET_EVENT_TYPE_RECEIVE:
996 begin
997 freePacket := true;
998 if (ev.channelID <> NET_CHAN_DOWNLOAD_EX) then
999 begin
1000 //e_LogWriteln('g_Net_Wait_Event: skip message from non-transfer channel');
1001 freePacket := false;
1002 g_Net_Client_HandlePacket(ev.packet, g_Net_ClientLightMsgHandler);
1003 if (g_Res_received_map_start < 0) then begin result := -666; exit; end;
1004 end
1005 else
1006 begin
1007 ett := getNewTimeoutEnd();
1008 if (ev.packet.dataLength < 1) then
1009 begin
1010 e_LogWriteln('g_Net_Wait_Event: invalid server packet (no data)');
1011 Result := -1;
1012 exit;
1013 end;
1014 Ptr := ev.packet^.data;
1015 rMsgId := Byte(Ptr^);
1016 e_LogWritefln('received transfer packet with id %d (%u bytes)', [rMsgId, ev.packet^.dataLength]);
1017 if (rMsgId = NTF_SERVER_FILE_INFO) then
1018 begin
1019 if not msg.Init(ev.packet^.data+1, ev.packet^.dataLength-1, True) then exit;
1020 tf.hash := msg.ReadMD5();
1021 tf.size := msg.ReadLongInt();
1022 tf.chunkSize := msg.ReadLongInt();
1023 tf.diskName := ExtractFileName(msg.readString());
1024 if (tf.size < 0) or (tf.chunkSize <> FILE_CHUNK_SIZE) or (length(tf.diskName) = 0) then
1025 begin
1026 e_LogWritefln('g_Net_RequestResFileInfo: invalid file info packet', []);
1027 Result := -1;
1028 exit;
1029 end;
1030 e_LogWritefln('got file info for resource #%d: size=%d; name=%s', [resIndex, tf.size, tf.diskName]);
1031 Result := 0; // success
1032 exit;
1033 end
1034 else if (rMsgId = NTF_SERVER_ABORT) then
1035 begin
1036 e_LogWriteln('g_Net_RequestResFileInfo: server aborted transfer');
1037 Result := 2;
1038 exit;
1039 end
1040 else if (rMsgId = NTF_SERVER_MAP_INFO) then
1041 begin
1042 e_LogWriteln('g_Net_RequestResFileInfo: waiting for map info reply, but got file info reply');
1043 Result := -1;
1044 exit;
1045 end
1046 else
1047 begin
1048 e_LogWriteln('g_Net_RequestResFileInfo: invalid server packet type');
1049 Result := -1;
1050 exit;
1051 end;
1052 end;
1053 end;
1054 ENET_EVENT_TYPE_DISCONNECT:
1055 begin
1056 if (ev.data <= NET_DISC_MAX) then
1057 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' ' + _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + ev.data)], True);
1058 Result := -1;
1059 exit;
1060 end;
1061 else
1062 begin
1063 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' unknown ENet event ' + IntToStr(Ord(ev.kind)), True);
1064 result := -1;
1065 exit;
1066 end;
1067 end;
1068 if (freePacket) then begin freePacket := false; enet_packet_destroy(ev.packet); end;
1069 end;
1070 ProcessLoading();
1071 if g_Net_UserRequestExit() then
1072 begin
1073 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' user abort', True);
1074 Result := 1;
1075 exit;
1076 end;
1077 until false;
1078 finally
1079 if (freePacket) then enet_packet_destroy(ev.packet);
1080 end;
1081 end;
1084 // call this to cancel file transfer requested by `g_Net_RequestResFileInfo()`
1085 function g_Net_AbortResTransfer (var tf: TNetFileTransfer): Boolean;
1086 begin
1087 result := false;
1088 e_LogWritefln('aborting file transfer...', []);
1089 // send request
1090 trans_omsg.Clear();
1091 trans_omsg.Write(Byte(NTF_CLIENT_ABORT));
1092 result := ftransSendClientMsg(trans_omsg);
1093 if result then enet_host_flush(NetHost);
1094 end;
1097 // call this to start file transfer requested by `g_Net_RequestResFileInfo()`
1098 //
1099 // returns `false` on error or user abort
1100 // fills:
1101 // hash
1102 // size
1103 // chunkSize
1104 // returns:
1105 // <0 on error
1106 // 0 on success
1107 // 1 on user abort
1108 // 2 on server abort
1109 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
1110 function g_Net_ReceiveResourceFile (resIndex: LongInt; var tf: TNetFileTransfer; strm: TStream): Integer;
1111 var
1112 ev: ENetEvent;
1113 rMsgId: Byte;
1114 Ptr: Pointer;
1115 msg: TMsg;
1116 freePacket: Boolean = false;
1117 ct, ett: Int64;
1118 status: cint;
1119 nextChunk: Integer = 0;
1120 chunkTotal: Integer;
1121 chunk: Integer;
1122 csize: Integer;
1123 buf: PChar = nil;
1124 resumed: Boolean;
1125 //stx: Int64;
1126 begin
1127 tf.resumed := false;
1128 e_LogWritefln('file `%s`, size=%d (%d)', [tf.diskName, Integer(strm.size), tf.size], TMsgType.Notify);
1129 // check if we should resume downloading
1130 resumed := (strm.size > tf.chunkSize) and (strm.size < tf.size);
1131 // send request
1132 trans_omsg.Clear();
1133 trans_omsg.Write(Byte(NTF_CLIENT_START));
1134 if resumed then chunk := strm.size div tf.chunkSize else chunk := 0;
1135 trans_omsg.Write(LongInt(chunk));
1136 if not ftransSendClientMsg(trans_omsg) then begin result := -1; exit; end;
1138 strm.Seek(chunk*tf.chunkSize, soFromBeginning);
1139 chunkTotal := (tf.size+tf.chunkSize-1) div tf.chunkSize;
1140 e_LogWritefln('receiving file `%s` (%d chunks)', [tf.diskName, chunkTotal], TMsgType.Notify);
1141 g_Game_SetLoadingText('downloading "'+ExtractFileName(tf.diskName)+'"', chunkTotal, False);
1142 tf.resumed := resumed;
1144 if (chunk > 0) then g_Game_StepLoading(chunk);
1145 nextChunk := chunk;
1147 // wait for reply data
1148 FillChar(ev, SizeOf(ev), 0);
1149 Result := -1;
1150 GetMem(buf, tf.chunkSize);
1151 try
1152 ett := getNewTimeoutEnd();
1153 repeat
1154 //stx := -GetTimerMS();
1155 status := enet_host_service(NetHost, @ev, 300);
1157 if (status < 0) then
1158 begin
1159 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True);
1160 Result := -1;
1161 exit;
1162 end;
1164 if (status <= 0) then
1165 begin
1166 // check for timeout
1167 ct := GetTimerMS();
1168 if (ct >= ett) then
1169 begin
1170 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' timeout reached', True);
1171 Result := -1;
1172 exit;
1173 end;
1174 end
1175 else
1176 begin
1177 // some event
1178 case ev.kind of
1179 ENET_EVENT_TYPE_RECEIVE:
1180 begin
1181 freePacket := true;
1182 if (ev.channelID <> NET_CHAN_DOWNLOAD_EX) then
1183 begin
1184 //e_LogWritefln('g_Net_Wait_Event: skip message from non-transfer channel', []);
1185 freePacket := false;
1186 g_Net_Client_HandlePacket(ev.packet, g_Net_ClientLightMsgHandler);
1187 if (g_Res_received_map_start < 0) then begin result := -666; exit; end;
1188 end
1189 else
1190 begin
1191 //stx := stx+GetTimerMS();
1192 //e_LogWritefln('g_Net_ReceiveResourceFile: stx=%d', [Integer(stx)]);
1193 //stx := -GetTimerMS();
1194 ett := getNewTimeoutEnd();
1195 if (ev.packet.dataLength < 1) then
1196 begin
1197 e_LogWritefln('g_Net_ReceiveResourceFile: invalid server packet (no data)', []);
1198 Result := -1;
1199 exit;
1200 end;
1201 Ptr := ev.packet^.data;
1202 rMsgId := Byte(Ptr^);
1203 if (rMsgId = NTF_SERVER_DONE) then
1204 begin
1205 e_LogWritefln('file transfer complete.', []);
1206 result := 0;
1207 exit;
1208 end
1209 else if (rMsgId = NTF_SERVER_CHUNK) then
1210 begin
1211 if not msg.Init(ev.packet^.data+1, ev.packet^.dataLength-1, True) then exit;
1212 chunk := msg.ReadLongInt();
1213 csize := msg.ReadLongInt();
1214 if (chunk <> nextChunk) then
1215 begin
1216 e_LogWritefln('received chunk %d, but expected chunk %d', [chunk, nextChunk]);
1217 Result := -1;
1218 exit;
1219 end;
1220 if (csize < 0) or (csize > tf.chunkSize) then
1221 begin
1222 e_LogWritefln('received chunk with size %d, but expected chunk size is %d', [csize, tf.chunkSize]);
1223 Result := -1;
1224 exit;
1225 end;
1226 //e_LogWritefln('got chunk #%d of #%d (csize=%d)', [chunk, (tf.size+tf.chunkSize-1) div tf.chunkSize, csize]);
1227 msg.ReadData(buf, csize);
1228 strm.WriteBuffer(buf^, csize);
1229 nextChunk := chunk+1;
1230 g_Game_StepLoading();
1231 // send ack
1232 trans_omsg.Clear();
1233 trans_omsg.Write(Byte(NTF_CLIENT_ACK));
1234 trans_omsg.Write(LongInt(chunk));
1235 if not ftransSendClientMsg(trans_omsg) then begin result := -1; exit; end;
1236 end
1237 else if (rMsgId = NTF_SERVER_ABORT) then
1238 begin
1239 e_LogWritefln('g_Net_ReceiveResourceFile: server aborted transfer', []);
1240 Result := 2;
1241 exit;
1242 end
1243 else
1244 begin
1245 e_LogWritefln('g_Net_ReceiveResourceFile: invalid server packet type', []);
1246 Result := -1;
1247 exit;
1248 end;
1249 //stx := stx+GetTimerMS();
1250 //e_LogWritefln('g_Net_ReceiveResourceFile: process stx=%d', [Integer(stx)]);
1251 end;
1252 end;
1253 ENET_EVENT_TYPE_DISCONNECT:
1254 begin
1255 if (ev.data <= NET_DISC_MAX) then
1256 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' ' + _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + ev.data)], True);
1257 Result := -1;
1258 exit;
1259 end;
1260 else
1261 begin
1262 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' unknown ENet event ' + IntToStr(Ord(ev.kind)), True);
1263 result := -1;
1264 exit;
1265 end;
1266 end;
1267 if (freePacket) then begin freePacket := false; enet_packet_destroy(ev.packet); end;
1268 end;
1269 ProcessLoading();
1270 if g_Net_UserRequestExit() then
1271 begin
1272 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' user abort', True);
1273 Result := 1;
1274 exit;
1275 end;
1276 until false;
1277 finally
1278 FreeMem(buf);
1279 if (freePacket) then enet_packet_destroy(ev.packet);
1280 end;
1281 end;
1284 //**************************************************************************
1285 //
1286 // common functions
1287 //
1288 //**************************************************************************
1290 function g_Net_FindSlot(): Integer;
1291 var
1292 I: Integer;
1293 F: Boolean;
1294 N, C: Integer;
1295 begin
1296 N := -1;
1297 F := False;
1298 C := 0;
1299 for I := Low(NetClients) to High(NetClients) do
1300 begin
1301 if NetClients[I].Used then
1302 Inc(C)
1303 else
1304 if not F then
1305 begin
1306 F := True;
1307 N := I;
1308 end;
1309 end;
1310 if C >= NetMaxClients then
1311 begin
1312 Result := -1;
1313 Exit;
1314 end;
1316 if not F then
1317 begin
1318 if (Length(NetClients) >= NetMaxClients) then
1319 N := -1
1320 else
1321 begin
1322 SetLength(NetClients, Length(NetClients) + 1);
1323 N := High(NetClients);
1324 end;
1325 end;
1327 if N >= 0 then
1328 begin
1329 NetClients[N].Used := True;
1330 NetClients[N].ID := N;
1331 NetClients[N].RequestedFullUpdate := False;
1332 NetClients[N].WaitForFirstSpawn := False;
1333 NetClients[N].RCONAuth := False;
1334 NetClients[N].Voted := False;
1335 NetClients[N].Player := 0;
1336 clearNetClientTransfers(NetClients[N]); // just in case
1337 end;
1339 Result := N;
1340 end;
1343 function g_Net_Init(): Boolean;
1344 var
1345 F: TextFile;
1346 IPstr: string;
1347 IP: LongWord;
1348 path: AnsiString;
1349 begin
1350 NetIn.Clear();
1351 NetOut.Clear();
1352 NetBuf[NET_UNRELIABLE].Clear();
1353 NetBuf[NET_RELIABLE].Clear();
1354 //SetLength(NetClients, 0);
1355 clearNetClients(true); // clear array
1356 NetPeer := nil;
1357 NetHost := nil;
1358 NetMyID := -1;
1359 NetPlrUID1 := -1;
1360 NetPlrUID2 := -1;
1361 NetAddr.port := 25666;
1362 SetLength(NetBannedHosts, 0);
1363 path := BANLIST_FILENAME;
1364 if e_FindResource(DataDirs, path) = true then
1365 begin
1366 Assign(F, path);
1367 Reset(F);
1368 while not EOF(F) do
1369 begin
1370 Readln(F, IPstr);
1371 if StrToIp(IPstr, IP) then
1372 g_Net_BanHost(IP);
1373 end;
1374 CloseFile(F);
1375 g_Net_SaveBanList();
1376 end;
1378 //Result := (enet_initialize() = 0);
1379 Result := enet_init_success;
1380 end;
1382 procedure g_Net_Flush();
1383 var
1384 T: Integer;
1385 P: pENetPacket;
1386 F, Chan: enet_uint32;
1387 I: Integer;
1388 begin
1389 F := 0;
1390 Chan := NET_CHAN_GAME;
1392 if NetMode = NET_SERVER then
1393 for T := NET_UNRELIABLE to NET_RELIABLE do
1394 begin
1395 if NetBuf[T].CurSize > 0 then
1396 begin
1397 P := enet_packet_create(NetBuf[T].Data, NetBuf[T].CurSize, F);
1398 if not Assigned(P) then continue;
1399 enet_host_broadcast(NetHost, Chan, P);
1400 NetBuf[T].Clear();
1401 end;
1403 for I := Low(NetClients) to High(NetClients) do
1404 begin
1405 if not NetClients[I].Used then continue;
1406 if NetClients[I].NetOut[T].CurSize <= 0 then continue;
1407 P := enet_packet_create(NetClients[I].NetOut[T].Data, NetClients[I].NetOut[T].CurSize, F);
1408 if not Assigned(P) then continue;
1409 enet_peer_send(NetClients[I].Peer, Chan, P);
1410 NetClients[I].NetOut[T].Clear();
1411 end;
1413 // next and last iteration is always RELIABLE
1414 F := LongWord(ENET_PACKET_FLAG_RELIABLE);
1415 Chan := NET_CHAN_IMPORTANT;
1416 end
1417 else if NetMode = NET_CLIENT then
1418 for T := NET_UNRELIABLE to NET_RELIABLE do
1419 begin
1420 if NetBuf[T].CurSize > 0 then
1421 begin
1422 P := enet_packet_create(NetBuf[T].Data, NetBuf[T].CurSize, F);
1423 if not Assigned(P) then continue;
1424 enet_peer_send(NetPeer, Chan, P);
1425 NetBuf[T].Clear();
1426 end;
1427 // next and last iteration is always RELIABLE
1428 F := LongWord(ENET_PACKET_FLAG_RELIABLE);
1429 Chan := NET_CHAN_IMPORTANT;
1430 end;
1431 end;
1433 procedure g_Net_Cleanup();
1434 begin
1435 NetIn.Clear();
1436 NetOut.Clear();
1437 NetBuf[NET_UNRELIABLE].Clear();
1438 NetBuf[NET_RELIABLE].Clear();
1440 //SetLength(NetClients, 0);
1441 clearNetClients(true); // clear array
1442 NetClientCount := 0;
1444 NetPeer := nil;
1445 NetHost := nil;
1446 g_Net_Slist_ServerClosed();
1447 NetMyID := -1;
1448 NetPlrUID1 := -1;
1449 NetPlrUID2 := -1;
1450 NetState := NET_STATE_NONE;
1452 NetPongSock := ENET_SOCKET_NULL;
1454 NetTimeToMaster := 0;
1455 NetTimeToUpdate := 0;
1456 NetTimeToReliable := 0;
1458 NetMode := NET_NONE;
1460 if NetPortThread <> NilThreadId then
1461 WaitForThreadTerminate(NetPortThread, 66666);
1463 NetPortThread := NilThreadId;
1464 g_Net_UnforwardPorts();
1466 if NetDump then
1467 g_Net_DumpEnd();
1468 end;
1470 procedure g_Net_Free();
1471 begin
1472 g_Net_Cleanup();
1474 //enet_deinitialize();
1475 NetInitDone := False;
1476 end;
1479 //**************************************************************************
1480 //
1481 // SERVER FUNCTIONS
1482 //
1483 //**************************************************************************
1485 function ForwardThread(Param: Pointer): PtrInt;
1486 begin
1487 Result := 0;
1488 if not g_Net_ForwardPorts() then Result := -1;
1489 end;
1491 function g_Net_Host(IPAddr: LongWord; Port: enet_uint16; MaxClients: Cardinal = 16): Boolean;
1492 begin
1493 if NetMode <> NET_NONE then
1494 begin
1495 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_INGAME]);
1496 Result := False;
1497 Exit;
1498 end;
1500 Result := True;
1502 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_HOST], [Port]));
1503 if not NetInitDone then
1504 begin
1505 if (not g_Net_Init()) then
1506 begin
1507 g_Console_Add(_lc[I_NET_MSG_FERROR] + _lc[I_NET_ERR_ENET]);
1508 Result := False;
1509 Exit;
1510 end
1511 else
1512 NetInitDone := True;
1513 end;
1515 NetAddr.host := IPAddr;
1516 NetAddr.port := Port;
1518 NetHost := enet_host_create(@NetAddr, NET_MAXCLIENTS, NET_CHANS, 0, 0);
1520 if (NetHost = nil) then
1521 begin
1522 g_Console_Add(_lc[I_NET_MSG_ERROR] + Format(_lc[I_NET_ERR_HOST], [Port]));
1523 Result := False;
1524 g_Net_Cleanup;
1525 Exit;
1526 end;
1528 if NetForwardPorts then NetPortThread := BeginThread(ForwardThread);
1530 NetPongSock := enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
1531 if NetPongSock <> ENET_SOCKET_NULL then
1532 begin
1533 NetPongAddr.host := IPAddr;
1534 NetPongAddr.port := NET_PING_PORT;
1535 if enet_socket_bind(NetPongSock, @NetPongAddr) < 0 then
1536 begin
1537 enet_socket_destroy(NetPongSock);
1538 NetPongSock := ENET_SOCKET_NULL;
1539 end
1540 else
1541 enet_socket_set_option(NetPongSock, ENET_SOCKOPT_NONBLOCK, 1);
1542 end;
1544 NetMode := NET_SERVER;
1545 NetOut.Clear();
1546 NetBuf[NET_UNRELIABLE].Clear();
1547 NetBuf[NET_RELIABLE].Clear();
1549 if NetDump then
1550 g_Net_DumpStart();
1551 end;
1553 procedure g_Net_Host_Die();
1554 var
1555 I: Integer;
1556 begin
1557 if NetMode <> NET_SERVER then Exit;
1559 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_DISCALL]);
1560 for I := 0 to High(NetClients) do
1561 if NetClients[I].Used then
1562 enet_peer_disconnect(NetClients[I].Peer, NET_DISC_DOWN);
1564 while enet_host_service(NetHost, @NetEvent, 1000) > 0 do
1565 if NetEvent.kind = ENET_EVENT_TYPE_RECEIVE then
1566 enet_packet_destroy(NetEvent.packet);
1568 for I := 0 to High(NetClients) do
1569 if NetClients[I].Used then
1570 begin
1571 FreeMemory(NetClients[I].Peer^.data);
1572 NetClients[I].Peer^.data := nil;
1573 enet_peer_reset(NetClients[I].Peer);
1574 NetClients[I].Peer := nil;
1575 NetClients[I].Used := False;
1576 NetClients[I].Player := 0;
1577 NetClients[I].Crimes := 0;
1578 NetClients[I].AuthTime := 0;
1579 NetClients[I].MsgTime := 0;
1580 NetClients[I].NetOut[NET_UNRELIABLE].Free();
1581 NetClients[I].NetOut[NET_RELIABLE].Free();
1582 end;
1584 clearNetClients(false); // don't clear array
1585 g_Net_Slist_ServerClosed();
1586 if NetPongSock <> ENET_SOCKET_NULL then
1587 enet_socket_destroy(NetPongSock);
1589 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_DIE]);
1590 enet_host_destroy(NetHost);
1592 NetMode := NET_NONE;
1594 g_Net_Cleanup;
1595 e_WriteLog('NET: Server stopped', TMsgType.Notify);
1596 end;
1599 procedure g_Net_Host_Send(ID: Integer; Reliable: Boolean; Chan: Byte = NET_CHAN_GAME);
1600 var
1601 T: Integer;
1602 begin
1603 if (Reliable) then
1604 T := NET_RELIABLE
1605 else
1606 T := NET_UNRELIABLE;
1608 if (ID >= 0) then
1609 begin
1610 if ID > High(NetClients) then Exit;
1611 if NetClients[ID].Peer = nil then Exit;
1612 // write size first
1613 NetClients[ID].NetOut[T].Write(Integer(NetOut.CurSize));
1614 NetClients[ID].NetOut[T].Write(NetOut);
1615 end
1616 else
1617 begin
1618 // write size first
1619 NetBuf[T].Write(Integer(NetOut.CurSize));
1620 NetBuf[T].Write(NetOut);
1621 end;
1623 if NetDump then g_Net_DumpSendBuffer();
1624 NetOut.Clear();
1625 end;
1627 procedure g_Net_Host_Disconnect_Client(ID: Integer; Force: Boolean = False);
1628 var
1629 TP: TPlayer;
1630 TC: pTNetClient;
1631 begin
1632 TC := @NetClients[ID];
1633 if (TC = nil) then Exit;
1634 clearNetClient(NetClients[ID]);
1635 if not (TC^.Used) then Exit;
1637 TP := g_Player_Get(TC^.Player);
1639 if TP <> nil then
1640 begin
1641 TP.Lives := 0;
1642 TP.Kill(K_SIMPLEKILL, 0, HIT_DISCON);
1643 g_Console_Add(Format(_lc[I_PLAYER_LEAVE], [TP.Name]), True);
1644 e_WriteLog('NET: Client ' + TP.Name + ' [' + IntToStr(TC^.ID) + '] disconnected.', TMsgType.Notify);
1645 g_Player_Remove(TP.UID);
1646 end;
1648 if (TC^.Peer^.data <> nil) then
1649 begin
1650 FreeMemory(TC^.Peer^.data);
1651 TC^.Peer^.data := nil;
1652 end;
1654 if (Force) then
1655 enet_peer_reset(TC^.Peer);
1657 TC^.Used := False;
1658 TC^.State := NET_STATE_NONE;
1659 TC^.Peer := nil;
1660 TC^.Player := 0;
1661 TC^.Crimes := 0;
1662 TC^.AuthTime := 0;
1663 TC^.MsgTime := 0;
1664 TC^.RequestedFullUpdate := False;
1665 TC^.WaitForFirstSpawn := False;
1666 TC^.NetOut[NET_UNRELIABLE].Free();
1667 TC^.NetOut[NET_RELIABLE].Free();
1669 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_HOST_DISC], [ID]));
1670 Dec(NetClientCount);
1672 if NetUseMaster then g_Net_Slist_ServerPlayerLeaves();
1673 end;
1675 procedure g_Net_Host_CheckPings();
1676 var
1677 ClAddr: ENetAddress;
1678 Buf: ENetBuffer;
1679 Len: Integer;
1680 ClTime: Int64;
1681 Ping: array [0..9] of Byte;
1682 NPl: Byte;
1683 begin
1684 if (NetPongSock = ENET_SOCKET_NULL) or (NetHost = nil) then Exit;
1686 Buf.data := Addr(Ping[0]);
1687 Buf.dataLength := 2+8;
1689 Ping[0] := 0;
1691 Len := enet_socket_receive(NetPongSock, @ClAddr, @Buf, 1);
1692 if Len < 0 then Exit;
1694 if (Ping[0] = Ord('D')) and (Ping[1] = Ord('F')) then
1695 begin
1696 ClTime := Int64(Addr(Ping[2])^);
1698 NetOut.Clear();
1699 NetOut.Write(Byte(Ord('D')));
1700 NetOut.Write(Byte(Ord('F')));
1701 NetOut.Write(NetHost.address.port);
1702 NetOut.Write(ClTime);
1703 TMasterHost.writeInfo(NetOut);
1704 NPl := 0;
1705 if gPlayer1 <> nil then Inc(NPl);
1706 if gPlayer2 <> nil then Inc(NPl);
1707 NetOut.Write(NPl);
1708 NetOut.Write(gNumBots);
1710 Buf.data := NetOut.Data;
1711 Buf.dataLength := NetOut.CurSize;
1712 enet_socket_send(NetPongSock, @ClAddr, @Buf, 1);
1714 NetOut.Clear();
1715 end;
1716 end;
1718 procedure g_Net_Host_CheckTimeouts();
1719 var
1720 ID: Integer;
1721 begin
1722 for ID := Low(NetClients) to High(NetClients) do
1723 begin
1724 with NetClients[ID] do
1725 begin
1726 if (Peer = nil) or (State = NET_STATE_NONE) then continue;
1727 if (State = NET_STATE_AUTH) and (AuthTime > 0) and (AuthTime <= gTime) then
1728 begin
1729 g_Net_Penalize(@NetClients[ID], 'auth taking too long');
1730 AuthTime := gTime + 1000; // do it every second to give them a chance
1731 end
1732 else if (State = NET_STATE_GAME) and (MsgTime > 0) and (MsgTime <= gTime) then
1733 begin
1734 // client hasn't sent packets in a while; either ban em or kick em
1735 if (NetAutoBanForTimeout) then
1736 begin
1737 g_Net_Penalize(@NetClients[ID], 'message timeout');
1738 MsgTime := gTime + (NetPacketTimeout div 2) + 500; // wait less for the next check
1739 end
1740 else
1741 begin
1742 e_LogWritefln('NET: client #%u (cid #%u) timed out', [ID, Player]);
1743 g_Net_Host_Disconnect_Client(ID, True);
1744 end;
1745 end;
1746 end;
1747 end;
1748 end;
1751 function g_Net_Host_Update(): enet_size_t;
1752 var
1753 IP: string;
1754 Port: Word;
1755 ID: Integer;
1756 TC: pTNetClient;
1757 begin
1758 IP := '';
1759 Result := 0;
1761 if NetUseMaster then g_Net_Slist_Pulse();
1762 g_Net_Host_CheckPings();
1763 g_Net_Host_CheckTimeouts();
1765 while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
1766 begin
1767 case (NetEvent.kind) of
1768 ENET_EVENT_TYPE_CONNECT:
1769 begin
1770 IP := IpToStr(NetEvent.Peer^.address.host);
1771 Port := NetEvent.Peer^.address.port;
1772 g_Console_Add(_lc[I_NET_MSG] +
1773 Format(_lc[I_NET_MSG_HOST_CONN], [IP, Port]));
1774 e_WriteLog('NET: Connection request from ' + IP + '.', TMsgType.Notify);
1776 if (NetEvent.data <> NET_PROTOCOL_VER) then
1777 begin
1778 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_REJECT] +
1779 _lc[I_NET_DISC_PROTOCOL]);
1780 e_WriteLog('NET: Connection request from ' + IP + ' rejected: version mismatch',
1781 TMsgType.Notify);
1782 NetEvent.peer^.data := GetMemory(SizeOf(Byte));
1783 Byte(NetEvent.peer^.data^) := 255;
1784 enet_peer_disconnect(NetEvent.peer, NET_DISC_PROTOCOL);
1785 enet_host_flush(NetHost);
1786 Exit;
1787 end;
1789 if g_Net_IsHostBanned(NetEvent.Peer^.address.host) then
1790 begin
1791 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_REJECT] +
1792 _lc[I_NET_DISC_BAN]);
1793 e_WriteLog('NET: Connection request from ' + IP + ' rejected: banned',
1794 TMsgType.Notify);
1795 NetEvent.peer^.data := GetMemory(SizeOf(Byte));
1796 Byte(NetEvent.peer^.data^) := 255;
1797 enet_peer_disconnect(NetEvent.Peer, NET_DISC_BAN);
1798 enet_host_flush(NetHost);
1799 Exit;
1800 end;
1802 ID := g_Net_FindSlot();
1804 if ID < 0 then
1805 begin
1806 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_REJECT] +
1807 _lc[I_NET_DISC_FULL]);
1808 e_WriteLog('NET: Connection request from ' + IP + ' rejected: server full',
1809 TMsgType.Notify);
1810 NetEvent.Peer^.data := GetMemory(SizeOf(Byte));
1811 Byte(NetEvent.peer^.data^) := 255;
1812 enet_peer_disconnect(NetEvent.peer, NET_DISC_FULL);
1813 enet_host_flush(NetHost);
1814 Exit;
1815 end;
1817 NetClients[ID].Peer := NetEvent.peer;
1818 NetClients[ID].Peer^.data := GetMemory(SizeOf(Byte));
1819 Byte(NetClients[ID].Peer^.data^) := ID;
1820 NetClients[ID].State := NET_STATE_AUTH;
1821 NetClients[ID].Player := 0;
1822 NetClients[ID].Crimes := 0;
1823 NetClients[ID].RCONAuth := False;
1824 NetClients[ID].NetOut[NET_UNRELIABLE].Alloc(NET_BUFSIZE*2);
1825 NetClients[ID].NetOut[NET_RELIABLE].Alloc(NET_BUFSIZE*2);
1826 if (NetAuthTimeout > 0) then
1827 NetClients[ID].AuthTime := gTime + NetAuthTimeout
1828 else
1829 NetClients[ID].AuthTime := 0;
1830 if (NetPacketTimeout > 0) then
1831 NetClients[ID].MsgTime := gTime + NetPacketTimeout
1832 else
1833 NetClients[ID].MsgTime := 0;
1834 clearNetClientTransfers(NetClients[ID]); // just in case
1836 enet_peer_timeout(NetEvent.peer, ENET_PEER_TIMEOUT_LIMIT * 2, ENET_PEER_TIMEOUT_MINIMUM * 2, ENET_PEER_TIMEOUT_MAXIMUM * 2);
1838 Inc(NetClientCount);
1839 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_HOST_ADD], [ID]));
1840 end;
1842 ENET_EVENT_TYPE_RECEIVE:
1843 begin
1844 //e_LogWritefln('RECEIVE: chan=%u', [NetEvent.channelID]);
1845 if (NetEvent.channelID = NET_CHAN_DOWNLOAD_EX) then
1846 begin
1847 ProcessDownloadExPacket();
1848 end
1849 else
1850 begin
1851 ID := Byte(NetEvent.peer^.data^);
1852 if ID > High(NetClients) then Exit;
1853 TC := @NetClients[ID];
1855 if (NetPacketTimeout > 0) then
1856 TC^.MsgTime := gTime + NetPacketTimeout;
1858 if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
1859 g_Net_Host_HandlePacket(TC, NetEvent.packet, g_Net_HostMsgHandler);
1860 end;
1861 end;
1863 ENET_EVENT_TYPE_DISCONNECT:
1864 begin
1865 ID := Byte(NetEvent.peer^.data^);
1866 if ID > High(NetClients) then Exit;
1867 g_Net_Host_Disconnect_Client(ID);
1868 end;
1869 end;
1870 end;
1871 end;
1874 //**************************************************************************
1875 //
1876 // CLIENT FUNCTIONS
1877 //
1878 //**************************************************************************
1880 procedure g_Net_Disconnect(Forced: Boolean = False);
1881 begin
1882 if NetMode <> NET_CLIENT then Exit;
1883 if (NetHost = nil) or (NetPeer = nil) then Exit;
1885 if not Forced then
1886 begin
1887 enet_peer_disconnect(NetPeer, NET_DISC_NONE);
1889 while (enet_host_service(NetHost, @NetEvent, 1500) > 0) do
1890 begin
1891 if (NetEvent.kind = ENET_EVENT_TYPE_DISCONNECT) then
1892 begin
1893 NetPeer := nil;
1894 break;
1895 end;
1897 if (NetEvent.kind = ENET_EVENT_TYPE_RECEIVE) then
1898 enet_packet_destroy(NetEvent.packet);
1899 end;
1901 if NetPeer <> nil then
1902 begin
1903 enet_peer_reset(NetPeer);
1904 NetPeer := nil;
1905 end;
1906 end
1907 else
1908 begin
1909 e_WriteLog('NET: Kicked from server: ' + IntToStr(NetEvent.data), TMsgType.Notify);
1910 if (NetEvent.data <= NET_DISC_MAX) then
1911 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_KICK] +
1912 _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + NetEvent.data)], True);
1913 end;
1915 if NetHost <> nil then
1916 begin
1917 enet_host_destroy(NetHost);
1918 NetHost := nil;
1919 end;
1920 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_CLIENT_DISC]);
1922 g_Net_Cleanup;
1923 e_WriteLog('NET: Disconnected', TMsgType.Notify);
1924 end;
1926 procedure g_Net_Client_Send(Reliable: Boolean; Chan: Byte = NET_CHAN_GAME);
1927 var
1928 T: Integer;
1929 begin
1930 if (Reliable) then
1931 T := NET_RELIABLE
1932 else
1933 T := NET_UNRELIABLE;
1935 // write size first
1936 NetBuf[T].Write(Integer(NetOut.CurSize));
1937 NetBuf[T].Write(NetOut);
1939 if NetDump then g_Net_DumpSendBuffer();
1940 NetOut.Clear();
1941 g_Net_Flush(); // FIXME: for now, send immediately
1942 end;
1944 function g_Net_Client_Update(): enet_size_t;
1945 begin
1946 Result := 0;
1947 while (NetHost <> nil) and (enet_host_service(NetHost, @NetEvent, 0) > 0) do
1948 begin
1949 case NetEvent.kind of
1950 ENET_EVENT_TYPE_RECEIVE:
1951 begin
1952 if (NetEvent.channelID = NET_CHAN_DOWNLOAD_EX) then continue; // ignore all download packets, they're processed by separate code
1953 if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
1954 g_Net_Client_HandlePacket(NetEvent.packet, g_Net_ClientMsgHandler);
1955 end;
1957 ENET_EVENT_TYPE_DISCONNECT:
1958 begin
1959 g_Net_Disconnect(True);
1960 Result := 1;
1961 Exit;
1962 end;
1963 end;
1964 end
1965 end;
1967 function g_Net_Client_UpdateWhileLoading(): enet_size_t;
1968 begin
1969 Result := 0;
1970 while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
1971 begin
1972 case NetEvent.kind of
1973 ENET_EVENT_TYPE_RECEIVE:
1974 begin
1975 if (NetEvent.channelID = NET_CHAN_DOWNLOAD_EX) then continue; // ignore all download packets, they're processed by separate code
1976 if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
1977 g_Net_Client_HandlePacket(NetEvent.packet, g_Net_ClientLightMsgHandler);
1978 end;
1980 ENET_EVENT_TYPE_DISCONNECT:
1981 begin
1982 g_Net_Disconnect(True);
1983 Result := 1;
1984 Exit;
1985 end;
1986 end;
1987 end;
1988 g_Net_Flush();
1989 end;
1991 function g_Net_Connect(IP: string; Port: enet_uint16): Boolean;
1992 var
1993 OuterLoop: Boolean;
1994 TimeoutTime, T: Int64;
1995 begin
1996 if NetMode <> NET_NONE then
1997 begin
1998 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_ERR_INGAME], True);
1999 Result := False;
2000 Exit;
2001 end;
2003 Result := True;
2005 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_CLIENT_CONN],
2006 [IP, Port]));
2007 if not NetInitDone then
2008 begin
2009 if (not g_Net_Init()) then
2010 begin
2011 g_Console_Add(_lc[I_NET_MSG_FERROR] + _lc[I_NET_ERR_ENET], True);
2012 Result := False;
2013 Exit;
2014 end
2015 else
2016 NetInitDone := True;
2017 end;
2019 NetHost := enet_host_create(nil, 1, NET_CHANS, 0, 0);
2021 if (NetHost = nil) then
2022 begin
2023 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CLIENT], True);
2024 g_Net_Cleanup;
2025 Result := False;
2026 Exit;
2027 end;
2029 enet_address_set_host(@NetAddr, PChar(Addr(IP[1])));
2030 NetAddr.port := Port;
2032 NetPeer := enet_host_connect(NetHost, @NetAddr, NET_CHANS, NET_PROTOCOL_VER);
2034 if (NetPeer = nil) then
2035 begin
2036 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CLIENT], True);
2037 enet_host_destroy(NetHost);
2038 g_Net_Cleanup;
2039 Result := False;
2040 Exit;
2041 end;
2043 // предупредить что ждем слишком долго через N секунд
2044 TimeoutTime := sys_GetTicks() + NET_CONNECT_TIMEOUT;
2046 OuterLoop := True;
2047 while OuterLoop do
2048 begin
2049 while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
2050 begin
2051 if (NetEvent.kind = ENET_EVENT_TYPE_CONNECT) then
2052 begin
2053 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_CLIENT_DONE]);
2054 NetMode := NET_CLIENT;
2055 NetOut.Clear();
2056 enet_peer_timeout(NetPeer, ENET_PEER_TIMEOUT_LIMIT * 2, ENET_PEER_TIMEOUT_MINIMUM * 2, ENET_PEER_TIMEOUT_MAXIMUM * 2);
2057 NetClientIP := IP;
2058 NetClientPort := Port;
2059 if NetDump then
2060 g_Net_DumpStart();
2061 Exit;
2062 end;
2063 end;
2065 T := sys_GetTicks();
2066 if T > TimeoutTime then
2067 begin
2068 TimeoutTime := T + NET_CONNECT_TIMEOUT * 100; // одного предупреждения хватит
2069 g_Console_Add(_lc[I_NET_MSG_TIMEOUT_WARN], True);
2070 g_Console_Add(Format(_lc[I_NET_MSG_PORTS], [Integer(Port), Integer(NET_PING_PORT)]), True);
2071 end;
2073 ProcessLoading(true);
2075 if e_KeyPressed(IK_SPACE) or e_KeyPressed(IK_ESCAPE) or e_KeyPressed(VK_ESCAPE) or
2076 e_KeyPressed(JOY0_JUMP) or e_KeyPressed(JOY1_JUMP) or e_KeyPressed(JOY2_JUMP) or e_KeyPressed(JOY3_JUMP) then
2077 OuterLoop := False;
2078 end;
2080 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_TIMEOUT], True);
2081 g_Console_Add(Format(_lc[I_NET_MSG_PORTS], [Integer(Port), Integer(NET_PING_PORT)]), True);
2082 if NetPeer <> nil then enet_peer_reset(NetPeer);
2083 if NetHost <> nil then
2084 begin
2085 enet_host_destroy(NetHost);
2086 NetHost := nil;
2087 end;
2088 g_Net_Cleanup();
2089 Result := False;
2090 end;
2092 function IpToStr(IP: LongWord): string;
2093 var
2094 Ptr: Pointer;
2095 begin
2096 Ptr := Addr(IP);
2097 Result := IntToStr(PByte(Ptr + 0)^) + '.';
2098 Result := Result + IntToStr(PByte(Ptr + 1)^) + '.';
2099 Result := Result + IntToStr(PByte(Ptr + 2)^) + '.';
2100 Result := Result + IntToStr(PByte(Ptr + 3)^);
2101 end;
2103 function StrToIp(IPstr: string; var IP: LongWord): Boolean;
2104 var
2105 EAddr: ENetAddress;
2106 begin
2107 Result := enet_address_set_host(@EAddr, PChar(@IPstr[1])) = 0;
2108 IP := EAddr.host;
2109 end;
2111 function g_Net_Client_ByName(Name: string): pTNetClient;
2112 var
2113 a: Integer;
2114 pl: TPlayer;
2115 begin
2116 Result := nil;
2117 for a := Low(NetClients) to High(NetClients) do
2118 if (NetClients[a].Used) and (NetClients[a].State = NET_STATE_GAME) then
2119 begin
2120 pl := g_Player_Get(NetClients[a].Player);
2121 if pl = nil then continue;
2122 if Copy(LowerCase(pl.Name), 1, Length(Name)) <> LowerCase(Name) then continue;
2123 if NetClients[a].Peer <> nil then
2124 begin
2125 Result := @NetClients[a];
2126 Exit;
2127 end;
2128 end;
2129 end;
2131 function g_Net_Client_ByPlayer(PID: Word): pTNetClient;
2132 var
2133 a: Integer;
2134 begin
2135 Result := nil;
2136 for a := Low(NetClients) to High(NetClients) do
2137 if (NetClients[a].Used) and (NetClients[a].State = NET_STATE_GAME) then
2138 if NetClients[a].Player = PID then
2139 begin
2140 Result := @NetClients[a];
2141 Exit;
2142 end;
2143 end;
2145 function g_Net_ClientName_ByID(ID: Integer): string;
2146 var
2147 a: Integer;
2148 pl: TPlayer;
2149 begin
2150 Result := '';
2151 if ID = NET_EVERYONE then
2152 Exit;
2153 for a := Low(NetClients) to High(NetClients) do
2154 if (NetClients[a].ID = ID) and (NetClients[a].Used) and (NetClients[a].State = NET_STATE_GAME) then
2155 begin
2156 pl := g_Player_Get(NetClients[a].Player);
2157 if pl = nil then Exit;
2158 Result := pl.Name;
2159 end;
2160 end;
2162 procedure g_Net_SendData(Data: AByte; peer: pENetPeer; Reliable: Boolean; Chan: Byte = NET_CHAN_DOWNLOAD);
2163 var
2164 P: pENetPacket;
2165 F: enet_uint32;
2166 dataLength: Cardinal;
2167 begin
2168 dataLength := Length(Data);
2170 if (Reliable) then
2171 F := LongWord(ENET_PACKET_FLAG_RELIABLE)
2172 else
2173 F := 0;
2175 if (peer <> nil) then
2176 begin
2177 P := enet_packet_create(@Data[0], dataLength, F);
2178 if not Assigned(P) then Exit;
2179 enet_peer_send(peer, Chan, P);
2180 end
2181 else
2182 begin
2183 P := enet_packet_create(@Data[0], dataLength, F);
2184 if not Assigned(P) then Exit;
2185 enet_host_broadcast(NetHost, Chan, P);
2186 end;
2188 enet_host_flush(NetHost);
2189 end;
2191 function g_Net_IsHostBanned(IP: LongWord; Perm: Boolean = False): Boolean;
2192 var
2193 I: Integer;
2194 begin
2195 Result := False;
2196 if NetBannedHosts = nil then
2197 Exit;
2198 for I := 0 to High(NetBannedHosts) do
2199 if (NetBannedHosts[I].IP = IP) and ((not Perm) or (NetBannedHosts[I].Perm)) then
2200 begin
2201 Result := True;
2202 break;
2203 end;
2204 end;
2206 procedure g_Net_BanHost(IP: LongWord; Perm: Boolean = True); overload;
2207 var
2208 I, P: Integer;
2209 begin
2210 if IP = 0 then
2211 Exit;
2212 if g_Net_IsHostBanned(IP, Perm) then
2213 Exit;
2215 P := -1;
2216 for I := Low(NetBannedHosts) to High(NetBannedHosts) do
2217 if NetBannedHosts[I].IP = 0 then
2218 begin
2219 P := I;
2220 break;
2221 end;
2223 if P < 0 then
2224 begin
2225 SetLength(NetBannedHosts, Length(NetBannedHosts) + 1);
2226 P := High(NetBannedHosts);
2227 end;
2229 NetBannedHosts[P].IP := IP;
2230 NetBannedHosts[P].Perm := Perm;
2231 end;
2233 procedure g_Net_BanHost(IP: string; Perm: Boolean = True); overload;
2234 var
2235 a: LongWord;
2236 b: Boolean;
2237 begin
2238 b := StrToIp(IP, a);
2239 if b then
2240 g_Net_BanHost(a, Perm);
2241 end;
2243 procedure g_Net_UnbanNonPermHosts();
2244 var
2245 I: Integer;
2246 begin
2247 if NetBannedHosts = nil then
2248 Exit;
2249 for I := Low(NetBannedHosts) to High(NetBannedHosts) do
2250 if (NetBannedHosts[I].IP > 0) and not NetBannedHosts[I].Perm then
2251 begin
2252 NetBannedHosts[I].IP := 0;
2253 NetBannedHosts[I].Perm := True;
2254 end;
2255 end;
2257 function g_Net_UnbanHost(IP: string): Boolean; overload;
2258 var
2259 a: LongWord;
2260 begin
2261 Result := StrToIp(IP, a);
2262 if Result then
2263 Result := g_Net_UnbanHost(a);
2264 end;
2266 function g_Net_UnbanHost(IP: LongWord): Boolean; overload;
2267 var
2268 I: Integer;
2269 begin
2270 Result := False;
2271 if IP = 0 then
2272 Exit;
2273 if NetBannedHosts = nil then
2274 Exit;
2275 for I := 0 to High(NetBannedHosts) do
2276 if NetBannedHosts[I].IP = IP then
2277 begin
2278 NetBannedHosts[I].IP := 0;
2279 NetBannedHosts[I].Perm := True;
2280 Result := True;
2281 // no break here to clear all bans of this host, perm and non-perm
2282 end;
2283 end;
2285 procedure g_Net_SaveBanList();
2286 var
2287 F: TextFile;
2288 I: Integer;
2289 path: AnsiString;
2290 begin
2291 path := e_GetWriteableDir(DataDirs);
2292 if path <> '' then
2293 begin
2294 path := e_CatPath(path, BANLIST_FILENAME);
2295 Assign(F, path);
2296 Rewrite(F);
2297 if NetBannedHosts <> nil then
2298 for I := 0 to High(NetBannedHosts) do
2299 if NetBannedHosts[I].Perm and (NetBannedHosts[I].IP > 0) then
2300 Writeln(F, IpToStr(NetBannedHosts[I].IP));
2301 CloseFile(F)
2302 end
2303 end;
2305 procedure g_Net_Penalize(C: pTNetClient; Reason: string);
2306 var
2307 s: string;
2308 begin
2309 e_LogWritefln('NET: client #%u (cid #%u) triggered a penalty (%d/%d): %s',
2310 [C^.ID, C^.Player, C^.Crimes + 1, NetAutoBanLimit, Reason]);
2312 if (NetAutoBanLimit <= 0) then Exit;
2314 if (C^.Crimes >= NetAutoBanLimit) then
2315 begin
2316 // we have tried asking nicely before, now it is time to die
2317 e_LogWritefln('NET: client #%u (cid #%u) force kicked',
2318 [C^.ID, C^.Player]);
2319 g_Net_Host_Disconnect_Client(C^.ID, True);
2320 Exit;
2321 end;
2323 Inc(C^.Crimes);
2325 if (NetAutoBanWarn) then
2326 MH_SEND_Chat('You have been warned by the server: ' + Reason, NET_CHAT_SYSTEM, C^.ID);
2328 if (C^.Crimes >= NetAutoBanLimit) then
2329 begin
2330 s := '#' + IntToStr(C^.ID); // can't be arsed
2331 g_Net_BanHost(C^.Peer^.address.host, NetAutoBanPerm);
2332 enet_peer_disconnect(C^.Peer, NET_DISC_BAN);
2333 g_Console_Add(Format(_lc[I_PLAYER_BAN], [s]));
2334 MH_SEND_GameEvent(NET_EV_PLAYER_BAN, 0, s);
2335 g_Net_Slist_ServerPlayerLeaves();
2336 end;
2337 end;
2339 procedure g_Net_DumpStart();
2340 begin
2341 if NetMode = NET_SERVER then
2342 NetDumpFile := e_CreateResource(LogDirs, NETDUMP_FILENAME + '_server')
2343 else
2344 NetDumpFile := e_CreateResource(LogDirs, NETDUMP_FILENAME + '_client');
2345 end;
2347 procedure g_Net_DumpSendBuffer();
2348 begin
2349 writeInt(NetDumpFile, gTime);
2350 writeInt(NetDumpFile, LongWord(NetOut.CurSize));
2351 writeInt(NetDumpFile, Byte(1));
2352 NetDumpFile.WriteBuffer(NetOut.Data^, NetOut.CurSize);
2353 end;
2355 procedure g_Net_DumpRecvBuffer(Buf: penet_uint8; Len: LongWord);
2356 begin
2357 if (Buf = nil) or (Len = 0) then Exit;
2358 writeInt(NetDumpFile, gTime);
2359 writeInt(NetDumpFile, Len);
2360 writeInt(NetDumpFile, Byte(0));
2361 NetDumpFile.WriteBuffer(Buf^, Len);
2362 end;
2364 procedure g_Net_DumpEnd();
2365 begin
2366 NetDumpFile.Free();
2367 NetDumpFile := nil;
2368 end;
2370 function g_Net_ForwardPorts(ForwardPongPort: Boolean = True): Boolean;
2371 {$IFDEF USE_MINIUPNPC}
2372 var
2373 DevList: PUPNPDev;
2374 Urls: TUPNPUrls;
2375 Data: TIGDDatas;
2376 LanAddr: array [0..255] of Char;
2377 StrPort: AnsiString;
2378 Err, I: Integer;
2379 begin
2380 Result := False;
2382 if NetHost = nil then
2383 exit;
2385 if NetPortForwarded = NetHost.address.port then
2386 begin
2387 Result := True;
2388 exit;
2389 end;
2391 NetPongForwarded := False;
2392 NetPortForwarded := 0;
2394 DevList := upnpDiscover(1000, nil, nil, 0, 0, 2, Addr(Err));
2395 if DevList = nil then
2396 begin
2397 conwritefln('port forwarding failed: upnpDiscover() failed: %d', [Err]);
2398 exit;
2399 end;
2401 I := UPNP_GetValidIGD(DevList, @Urls, @Data, Addr(LanAddr[0]), 256);
2403 if I = 0 then
2404 begin
2405 conwriteln('port forwarding failed: could not find an IGD device on this LAN');
2406 FreeUPNPDevList(DevList);
2407 FreeUPNPUrls(@Urls);
2408 exit;
2409 end;
2411 StrPort := IntToStr(NetHost.address.port);
2412 I := UPNP_AddPortMapping(
2413 Urls.controlURL, Addr(data.first.servicetype[1]),
2414 PChar(StrPort), PChar(StrPort), Addr(LanAddr[0]), PChar('D2DF'),
2415 PChar('UDP'), nil, PChar('0')
2416 );
2418 if I <> 0 then
2419 begin
2420 conwritefln('forwarding port %d failed: error %d', [NetHost.address.port, I]);
2421 FreeUPNPDevList(DevList);
2422 FreeUPNPUrls(@Urls);
2423 exit;
2424 end;
2426 if ForwardPongPort then
2427 begin
2428 StrPort := IntToStr(NET_PING_PORT);
2429 I := UPNP_AddPortMapping(
2430 Urls.controlURL, Addr(data.first.servicetype[1]),
2431 PChar(StrPort), PChar(StrPort), Addr(LanAddr[0]), PChar('D2DF'),
2432 PChar('UDP'), nil, PChar('0')
2433 );
2435 if I <> 0 then
2436 begin
2437 conwritefln('forwarding port %d failed: error %d', [NET_PING_PORT, I]);
2438 NetPongForwarded := False;
2439 end
2440 else
2441 begin
2442 conwritefln('forwarded port %d successfully', [NET_PING_PORT]);
2443 NetPongForwarded := True;
2444 end;
2445 end;
2447 conwritefln('forwarded port %d successfully', [NetHost.address.port]);
2448 NetIGDControl := AnsiString(Urls.controlURL);
2449 NetIGDService := data.first.servicetype;
2450 NetPortForwarded := NetHost.address.port;
2452 FreeUPNPDevList(DevList);
2453 FreeUPNPUrls(@Urls);
2454 Result := True;
2455 end;
2456 {$ELSE}
2457 begin
2458 Result := False;
2459 end;
2460 {$ENDIF}
2462 procedure g_Net_UnforwardPorts();
2463 {$IFDEF USE_MINIUPNPC}
2464 var
2465 I: Integer;
2466 StrPort: AnsiString;
2467 begin
2468 if NetPortForwarded = 0 then Exit;
2470 conwriteln('unforwarding ports...');
2472 StrPort := IntToStr(NetPortForwarded);
2473 I := UPNP_DeletePortMapping(
2474 PChar(NetIGDControl), Addr(NetIGDService[1]),
2475 PChar(StrPort), PChar('UDP'), nil
2476 );
2477 conwritefln(' port %d: %d', [NetPortForwarded, I]);
2479 if NetPongForwarded then
2480 begin
2481 NetPongForwarded := False;
2482 StrPort := IntToStr(NET_PING_PORT);
2483 I := UPNP_DeletePortMapping(
2484 PChar(NetIGDControl), Addr(NetIGDService[1]),
2485 PChar(StrPort), PChar('UDP'), nil
2486 );
2487 conwritefln(' port %d: %d', [NET_PING_PORT, I]);
2488 end;
2490 NetPortForwarded := 0;
2491 end;
2492 {$ELSE}
2493 begin
2494 end;
2495 {$ENDIF}
2497 procedure NetServerCVars(P: SSArray);
2498 var
2499 cmd, s: string;
2500 a, b: Integer;
2501 begin
2502 cmd := LowerCase(P[0]);
2503 case cmd of
2504 'sv_name':
2505 begin
2506 if (Length(P) > 1) and (Length(P[1]) > 0) then
2507 begin
2508 NetServerName := P[1];
2509 if Length(NetServerName) > 64 then
2510 SetLength(NetServerName, 64);
2511 g_Net_Slist_ServerRenamed();
2512 end;
2513 g_Console_Add(cmd + ' "' + NetServerName + '"');
2514 end;
2515 'sv_passwd':
2516 begin
2517 if (Length(P) > 1) and (Length(P[1]) > 0) then
2518 begin
2519 NetPassword := P[1];
2520 if Length(NetPassword) > 24 then
2521 SetLength(NetPassword, 24);
2522 g_Net_Slist_ServerRenamed();
2523 end;
2524 g_Console_Add(cmd + ' "' + AnsiLowerCase(NetPassword) + '"');
2525 end;
2526 'sv_maxplrs':
2527 begin
2528 if (Length(P) > 1) then
2529 begin
2530 NetMaxClients := nclamp(StrToIntDef(P[1], NetMaxClients), 1, NET_MAXCLIENTS);
2531 if g_Game_IsServer and g_Game_IsNet then
2532 begin
2533 b := 0;
2534 for a := 0 to High(NetClients) do
2535 begin
2536 if NetClients[a].Used then
2537 begin
2538 Inc(b);
2539 if b > NetMaxClients then
2540 begin
2541 s := g_Player_Get(NetClients[a].Player).Name;
2542 enet_peer_disconnect(NetClients[a].Peer, NET_DISC_FULL);
2543 g_Console_Add(Format(_lc[I_PLAYER_KICK], [s]));
2544 MH_SEND_GameEvent(NET_EV_PLAYER_KICK, 0, s);
2545 end;
2546 end;
2547 end;
2548 g_Net_Slist_ServerRenamed();
2549 end;
2550 end;
2551 g_Console_Add(cmd + ' ' + IntToStr(NetMaxClients));
2552 end;
2553 'sv_public':
2554 begin
2555 if (Length(P) > 1) then
2556 begin
2557 NetUseMaster := StrToIntDef(P[1], Byte(NetUseMaster)) <> 0;
2558 if NetUseMaster then g_Net_Slist_Public() else g_Net_Slist_Private();
2559 end;
2560 g_Console_Add(cmd + ' ' + IntToStr(Byte(NetUseMaster)));
2561 end;
2562 'sv_port':
2563 begin
2564 if (Length(P) > 1) then
2565 begin
2566 if not g_Game_IsNet then
2567 NetPort := nclamp(StrToIntDef(P[1], NetPort), 0, $FFFF)
2568 else
2569 g_Console_Add(_lc[I_MSG_NOT_NETGAME]);
2570 end;
2571 g_Console_Add(cmd + ' ' + IntToStr(Ord(NetUseMaster)));
2572 end;
2573 end;
2574 end;
2576 initialization
2577 conRegVar('cl_downloadtimeout', @g_Net_DownloadTimeout, 0.0, 1000000.0, '', 'timeout in seconds, 0 to disable it');
2578 conRegVar('cl_predictself', @NetPredictSelf, '', 'predict local player');
2579 conRegVar('cl_forceplayerupdate', @NetForcePlayerUpdate, '', 'update net players on NET_MSG_PLRPOS');
2580 conRegVar('cl_interp', @NetInterpLevel, '', 'net player interpolation steps');
2581 conRegVar('cl_last_ip', @NetClientIP, '', 'address of the last you have connected to');
2582 conRegVar('cl_last_port', @NetClientPort, '', 'port of the last server you have connected to');
2583 conRegVar('cl_deafen', @NetDeafLevel, '', 'filter server messages (0-3)');
2585 conRegVar('sv_forwardports', @NetForwardPorts, '', 'forward server port using miniupnpc (requires server restart)');
2586 conRegVar('sv_rcon', @NetAllowRCON, '', 'enable remote console');
2587 conRegVar('sv_rcon_password', @NetRCONPassword, '', 'remote console password');
2588 conRegVar('sv_update_interval', @NetUpdateRate, '', 'unreliable update interval');
2589 conRegVar('sv_reliable_interval', @NetRelupdRate, '', 'reliable update interval');
2590 conRegVar('sv_master_interval', @NetMasterRate, '', 'master server update interval');
2592 conRegVar('sv_autoban_threshold', @NetAutoBanLimit, '', 'max crimes before autoban (0 = no autoban)');
2593 conRegVar('sv_autoban_permanent', @NetAutoBanPerm, '', 'whether autobans are permanent');
2594 conRegVar('sv_autoban_warn', @NetAutoBanWarn, '', 'send warnings to the client when he triggers penalties');
2595 conRegVar('sv_autoban_packet_timeout', @NetAutoBanForTimeout, '', 'autoban for packet timeouts');
2597 conRegVar('sv_auth_timeout', @NetAuthTimeout, '', 'number of msec in which connecting clients must complete auth (0 = unlimited)');
2598 conRegVar('sv_packet_timeout', @NetPacketTimeout, '', 'number of msec the client must idle to be kicked (0 = unlimited)');
2600 conRegVar('net_master_list', @NetMasterList, '', 'list of master servers');
2602 SetLength(NetClients, 0);
2603 g_Net_DownloadTimeout := 60;
2604 NetIn.Alloc(NET_BUFSIZE);
2605 NetOut.Alloc(NET_BUFSIZE);
2606 NetBuf[NET_UNRELIABLE].Alloc(NET_BUFSIZE*2);
2607 NetBuf[NET_RELIABLE].Alloc(NET_BUFSIZE*2);
2608 trans_omsg.Alloc(NET_BUFSIZE);
2609 finalization
2610 NetIn.Free();
2611 NetOut.Free();
2612 NetBuf[NET_UNRELIABLE].Free();
2613 NetBuf[NET_RELIABLE].Free();
2614 end.