DEADSOFTWARE

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