DEADSOFTWARE

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