saveload: fix read/write unexisting value
[d2df-sdl.git] / src / game / g_net.pas
blob437eeae6bd05eb27cbb0d59dc0bad67f34f168a7
1 (* Copyright (C) Doom 2D: Forever Developers
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.
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.
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/>.
15 {$INCLUDE ../shared/a_modes.inc}
16 unit g_net;
18 interface
20 uses
21 {$IFDEF USE_MINIUPNPC}MiniUPnPc,{$ENDIF}
22 e_log, e_msg, utils, ENet, Classes, md5, MAPDEF;
24 const
25 NET_PROTOCOL_VER = 188;
26 NET_MAXCLIENTS = 24;
28 // NOTE: We use different channels for unreliable and reliable packets because ENet seems to
29 // discard preceeding RELIABLE packets if a later UNRELIABLE (but not UNSEQUENCED) packet sent
30 // on the same channel has arrived earlier, which is useful for occasional full-state updates.
31 // However, we use a separate download channel to avoid being delayed by other reliable packets.
32 NET_CHAN_UNRELIABLE = 2;
33 NET_CHAN_RELIABLE = 1;
34 NET_CHAN_DOWNLOAD = 11;
35 NET_CHANNELS = 12; // TODO: Reduce to 3 and re-enumerate channels. Requires protocol increment.
37 NET_NONE = 0;
38 NET_SERVER = 1;
39 NET_CLIENT = 2;
41 NET_BUFSIZE = $FFFF;
42 NET_PING_PORT = $DF2D;
44 NET_EVERYONE = -1;
46 NET_UNRELIABLE = 0;
47 NET_RELIABLE = 1;
49 NET_DISC_NONE: enet_uint32 = 0;
50 NET_DISC_PROTOCOL: enet_uint32 = 1;
51 NET_DISC_VERSION: enet_uint32 = 2;
52 NET_DISC_FULL: enet_uint32 = 3;
53 NET_DISC_KICK: enet_uint32 = 4;
54 NET_DISC_DOWN: enet_uint32 = 5;
55 NET_DISC_PASSWORD: enet_uint32 = 6;
56 NET_DISC_TEMPBAN: enet_uint32 = 7;
57 NET_DISC_BAN: enet_uint32 = 8;
58 NET_DISC_MAX: enet_uint32 = 8;
59 NET_DISC_FILE_TIMEOUT: enet_uint32 = 13;
61 NET_STATE_NONE = 0;
62 NET_STATE_AUTH = 1;
63 NET_STATE_GAME = 2;
65 NET_CONNECT_TIMEOUT = 1000 * 10;
67 BANLIST_FILENAME = 'banlist.txt';
68 NETDUMP_FILENAME = 'netdump';
70 type
71 TNetMapResourceInfo = record
72 wadName: AnsiString; // wad file name, without a path
73 size: Integer; // wad file size (-1: size and hash are not known)
74 hash: TMD5Digest; // wad hash
75 end;
77 TNetMapResourceInfoArray = array of TNetMapResourceInfo;
79 TNetFileTransfer = record
80 diskName: string;
81 hash: TMD5Digest;
82 stream: TStream;
83 size: Integer; // file size in bytes
84 chunkSize: Integer;
85 lastSentChunk: Integer;
86 lastAckChunk: Integer;
87 lastAckTime: Int64; // msecs; if not "in progress", we're waiting for the first ack
88 inProgress: Boolean;
89 diskBuffer: PChar; // of `chunkSize` bytes
90 resumed: Boolean;
91 end;
93 TNetClient = record
94 ID: Byte;
95 Used: Boolean;
96 State: Byte;
97 Peer: pENetPeer;
98 Player: Word;
99 RequestedFullUpdate: Boolean;
100 WaitForFirstSpawn: Boolean; // set to True on server, used to spawn a player on first full state request
101 FullUpdateSent: Boolean;
102 RCONAuth: Boolean;
103 Voted: Boolean;
104 Crimes: Integer;
105 AuthTime: LongWord;
106 MsgTime: LongWord;
107 Transfer: TNetFileTransfer; // only one transfer may be active
108 NetOut: array [0..1] of TMsg;
109 end;
110 TBanRecord = record
111 IP: LongWord;
112 Perm: Boolean;
113 end;
114 pTNetClient = ^TNetClient;
116 AByte = array of Byte;
119 NetInitDone: Boolean;
120 NetMode: Byte = NET_NONE;
121 NetDump: Boolean;
123 NetServerName: String = 'Unnamed Server';
124 NetPassword: String;
125 NetPort: Word = 25666;
127 NetAllowRCON: Boolean;
128 NetRCONPassword: String;
130 NetTimeToUpdate: Cardinal;
131 NetTimeToReliable: Cardinal;
132 NetTimeToMaster: Cardinal;
134 NetHost: pENetHost;
135 NetPeer: pENetPeer;
136 NetEvent: ENetEvent;
137 NetAddr: ENetAddress;
139 NetPongAddr: ENetAddress;
140 NetPongSock: ENetSocket = ENET_SOCKET_NULL;
142 NetUseMaster: Boolean = True;
143 NetMasterList: string = 'mpms.doom2d.org:25665, deadsoftware.ru:25665, terminalcorner.ru:25665';
145 NetClientIP: string = '127.0.0.1';
146 NetClientPort: Word = 25666;
148 NetIn, NetOut: TMsg;
149 NetBuf: array [0..1] of TMsg;
151 NetClients: array of TNetClient;
152 NetClientCount: Byte;
153 NetMaxClients: Byte = 255;
154 NetBannedHosts: array of TBanRecord;
156 NetAutoBanLimit: Integer = 5;
157 NetAutoBanPerm: Boolean = True;
158 NetAutoBanWarn: Boolean;
159 NetAutoBanForTimeout: Boolean;
161 NetAuthTimeout: Integer = 30 * 1000;
162 NetPacketTimeout: Integer = 60 * 1000;
164 NetState: Integer = NET_STATE_NONE;
166 NetMyID: Integer = -1;
167 NetPlrUID1: Integer = -1;
168 NetPlrUID2: Integer = -1;
170 NetInterpLevel: Integer = 1;
171 NetUpdateRate: Cardinal; // 0 - as soon as possible
172 NetRelupdRate: Cardinal = 18; // around two times a second
173 NetMasterRate: Cardinal = 60000;
175 NetForcePlayerUpdate: Boolean;
176 NetPredictSelf: Boolean = True;
177 NetForwardPorts: Boolean {$IFDEF USE_MINIUPNPC} = True {$ENDIF};
179 NetGotEverything: Boolean;
180 NetGotKeys: Boolean;
182 NetDeafLevel: Integer;
184 {$IFDEF USE_MINIUPNPC}
185 NetForwardedPort: Word;
186 NetPingPortForwarded: Boolean;
187 NetIGDControl: AnsiString;
188 NetIGDService: array[0..MINIUPNPC_URL_MAXSIZE-1] of AnsiChar;
189 {$ENDIF}
191 NetPortThread: TThreadID = NilThreadId;
193 NetDumpFile: TStream;
195 g_Res_received_map_start: Integer; // set if we received "map change" event
198 function g_Net_Init(): Boolean;
199 procedure g_Net_Cleanup();
200 procedure g_Net_Free();
201 procedure g_Net_Flush();
203 function g_Net_Host(IPAddr: LongWord; Port: enet_uint16; MaxClients: Cardinal = 16): Boolean;
204 procedure g_Net_Host_Die();
205 procedure g_Net_Host_Send(ID: Integer; Reliable: Boolean);
206 procedure g_Net_Host_Update();
207 procedure g_Net_Host_Kick(ID: Integer; Reason: enet_uint32);
208 procedure g_Net_Host_Ban(ID: Integer; Perm: Boolean);
209 procedure g_Net_Host_Ban(C: pTNetClient; Perm: Boolean);
211 function g_Net_Connect(IP: string; Port: enet_uint16): Boolean;
212 procedure g_Net_Disconnect(Forced: Boolean = False);
213 procedure g_Net_Client_Send(Reliable: Boolean);
214 procedure g_Net_Client_Update();
216 function g_Net_Client_ByName(Name: string): pTNetClient;
217 function g_Net_Client_ByPlayer(PID: Word): pTNetClient;
218 function g_Net_ClientName_ByID(ID: Integer): string;
220 function IpToStr(IP: LongWord): string;
221 function StrToIp(IPstr: string; var IP: LongWord): Boolean;
223 function g_Net_IsAddressBanned(IP: LongWord; Perm: Boolean = False): Boolean;
224 procedure g_Net_BanAddress(IP: LongWord; Perm: Boolean = True); overload;
225 procedure g_Net_BanAddress(IP: string; Perm: Boolean = True); overload;
226 function g_Net_UnbanAddress(IP: string): Boolean; overload;
227 function g_Net_UnbanAddress(IP: LongWord): Boolean; overload;
228 procedure g_Net_UnbanNonPerm();
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;
272 enet_init_success: Boolean;
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 //**************************************************************************
303 // SERVICE FUNCTIONS
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 ();
325 f: Integer;
326 begin
327 for f := Low(NetClients) to High(NetClients) do
328 clearNetClient(NetClients[f]);
329 SetLength(NetClients, 0);
330 end;
333 function g_Net_UserRequestExit (): Boolean;
334 begin
335 Result := {e_KeyPressed(IK_SPACE) or}
336 e_KeyPressed(IK_ESCAPE) or
337 e_KeyPressed(VK_ESCAPE) or
338 e_KeyPressed(JOY0_JUMP) or
339 e_KeyPressed(JOY1_JUMP) or
340 e_KeyPressed(JOY2_JUMP) or
341 e_KeyPressed(JOY3_JUMP)
342 end;
344 //**************************************************************************
346 // file transfer declaraions and host packet processor
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 g_Net_Host_Kick(nc.ID, 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;
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, 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;
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, pkt) <> 0) then exit;
400 result := true;
401 end;
404 // file chunk sender
405 procedure ProcessChunkSend (var nc: TNetClient);
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);
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
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 ();
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 // don't time out clients during a file transfer
509 if (NetAuthTimeout > 0) then
510 nc^.AuthTime := gTime + NetAuthTimeout;
511 if (NetPacketTimeout > 0) then
512 nc^.MsgTime := gTime + NetPacketTimeout;
514 tf := @NetClients[nid].Transfer;
515 tf.lastAckTime := GetTimerMS();
517 cmd := Byte(NetEvent.packet^.data^);
518 //e_LogWritefln('RECEIVE: nid=%d; cmd=%u', [nid, cmd]);
519 case cmd of
520 NTF_CLIENT_FILE_REQUEST: // file request
521 begin
522 if (tf.stream <> nil) then
523 begin
524 killClientByFT(nc^);
525 exit;
526 end;
527 if (NetEvent.packet^.dataLength < 2) then
528 begin
529 killClientByFT(nc^);
530 exit;
531 end;
532 // new transfer request; build packet
533 if not msg.Init(NetEvent.packet^.data+1, NetEvent.packet^.dataLength-1, True) then
534 begin
535 killClientByFT(nc^);
536 exit;
537 end;
538 // get resource index
539 ridx := msg.ReadLongInt();
540 if (ridx < -1) or (ridx >= length(gExternalResources)) then
541 begin
542 e_LogWritefln('Invalid resource index %d', [ridx], TMsgType.Warning);
543 killClientByFT(nc^);
544 exit;
545 end;
546 if (ridx < 0) then fname := gGameSettings.WAD else fname := gExternalResources[ridx].diskName;
547 if (length(fname) = 0) then
548 begin
549 e_WriteLog('Invalid filename: '+fname, TMsgType.Warning);
550 killClientByFT(nc^);
551 exit;
552 end;
553 tf.diskName := findDiskWad(fname);
554 if (length(tf.diskName) = 0) then
555 begin
556 e_LogWritefln('NETWORK: file "%s" not found!', [fname], TMsgType.Fatal);
557 killClientByFT(nc^);
558 exit;
559 end;
560 // calculate hash
561 //tf.hash := MD5File(tf.diskName);
562 if (ridx < 0) then tf.hash := gWADHash else tf.hash := gExternalResources[ridx].hash;
563 // create file stream
564 tf.diskName := findDiskWad(fname);
566 tf.stream := openDiskFileRO(tf.diskName);
567 except
568 tf.stream := nil;
569 end;
570 if (tf.stream = nil) then
571 begin
572 e_WriteLog(Format('NETWORK: file "%s" not found!', [fname]), TMsgType.Fatal);
573 killClientByFT(nc^);
574 exit;
575 end;
576 e_LogWritefln('client #%d requested resource #%d (file is `%s` : `%s`)', [nc.ID, ridx, fname, tf.diskName]);
577 tf.size := tf.stream.size;
578 tf.chunkSize := FILE_CHUNK_SIZE; // arbitrary
579 tf.lastSentChunk := -1;
580 tf.lastAckChunk := -1;
581 tf.lastAckTime := GetTimerMS();
582 tf.inProgress := False; // waiting for the first ACK or for the cancel
583 GetMem(tf.diskBuffer, tf.chunkSize);
584 // sent file info message
585 trans_omsg.Clear();
586 trans_omsg.Write(Byte(NTF_SERVER_FILE_INFO));
587 trans_omsg.Write(tf.hash);
588 trans_omsg.Write(tf.size);
589 trans_omsg.Write(tf.chunkSize);
590 trans_omsg.Write(ExtractFileName(fname));
591 if not ftransSendServerMsg(nc^, trans_omsg) then exit;
592 end;
593 NTF_CLIENT_ABORT: // do not send requested file, or abort current transfer
594 begin
595 e_LogWritefln('client #%d aborted file transfer', [nc.ID]);
596 clearNetClientTransfers(nc^);
597 end;
598 NTF_CLIENT_START: // start transfer; client may resume download by sending non-zero starting chunk
599 begin
600 if not Assigned(tf.stream) then
601 begin
602 killClientByFT(nc^);
603 exit;
604 end;
605 if (tf.lastSentChunk <> -1) or (tf.lastAckChunk <> -1) or (tf.inProgress) then
606 begin
607 // double ack, get lost
608 killClientByFT(nc^);
609 exit;
610 end;
611 if (NetEvent.packet^.dataLength < 2) then
612 begin
613 killClientByFT(nc^);
614 exit;
615 end;
616 // build packet
617 if not msg.Init(NetEvent.packet^.data+1, NetEvent.packet^.dataLength-1, True) then
618 begin
619 killClientByFT(nc^);
620 exit;
621 end;
622 chunk := msg.ReadLongInt();
623 if (chunk < 0) or (chunk > (tf.size+tf.chunkSize-1) div tf.chunkSize) then
624 begin
625 killClientByFT(nc^);
626 exit;
627 end;
628 e_LogWritefln('client #%d started file transfer from chunk %d', [nc.ID, chunk]);
629 // start sending chunks
630 tf.inProgress := True;
631 tf.lastSentChunk := chunk-1;
632 tf.lastAckChunk := chunk-1;
633 ProcessChunkSend(nc^);
634 end;
635 NTF_CLIENT_ACK: // chunk ack; chunk number follows
636 begin
637 if not Assigned(tf.stream) then
638 begin
639 killClientByFT(nc^);
640 exit;
641 end;
642 if (tf.lastSentChunk < 0) or (not tf.inProgress) then
643 begin
644 // double ack, get lost
645 killClientByFT(nc^);
646 exit;
647 end;
648 if (NetEvent.packet^.dataLength < 2) then
649 begin
650 killClientByFT(nc^);
651 exit;
652 end;
653 // build packet
654 if not msg.Init(NetEvent.packet^.data+1, NetEvent.packet^.dataLength-1, True) then
655 begin
656 killClientByFT(nc^);
657 exit;
658 end;
659 chunk := msg.ReadLongInt();
660 if (chunk < 0) or (chunk > (tf.size+tf.chunkSize-1) div tf.chunkSize) then
661 begin
662 killClientByFT(nc^);
663 exit;
664 end;
665 // do it this way, so client may seek, or request retransfers for some reason
666 tf.lastAckChunk := chunk;
667 tf.lastSentChunk := chunk;
668 //e_LogWritefln('client #%d acked file transfer chunk %d', [nc.ID, chunk]);
669 ProcessChunkSend(nc^);
670 end;
671 NTF_CLIENT_MAP_REQUEST:
672 begin
673 e_LogWritefln('client #%d requested map info', [nc.ID]);
674 trans_omsg.Clear();
675 dfn := findDiskWad(gGameSettings.WAD);
676 if (dfn = '') then dfn := '!wad_not_found!.wad'; //FIXME
677 //md5 := MD5File(dfn);
678 md5 := gWADHash;
679 if (not GetDiskFileInfo(dfn, fi)) then
680 begin
681 e_LogWritefln('client #%d requested map info, but i cannot get file info', [nc.ID]);
682 killClientByFT(nc^);
683 exit;
684 end;
685 size := fi.size;
687 st := openDiskFileRO(dfn);
688 if not assigned(st) then exit; //wtf?!
689 size := st.size;
690 st.Free;
692 // packet type
693 trans_omsg.Write(Byte(NTF_SERVER_MAP_INFO));
694 // map wad name
695 trans_omsg.Write(ExtractFileName(gGameSettings.WAD));
696 // map wad md5
697 trans_omsg.Write(md5);
698 // map wad size
699 trans_omsg.Write(size);
700 // number of external resources for map
701 trans_omsg.Write(LongInt(length(gExternalResources)));
702 // external resource names
703 for f := 0 to High(gExternalResources) do
704 begin
705 // old style packet
706 //trans_omsg.Write(ExtractFileName(gExternalResources[f])); // GameDir+'/wads/'+ResList.Strings[i]
707 // new style packet
708 trans_omsg.Write('!');
709 trans_omsg.Write(LongInt(gExternalResources[f].size));
710 trans_omsg.Write(gExternalResources[f].hash);
711 trans_omsg.Write(ExtractFileName(gExternalResources[f].diskName));
712 end;
713 // send packet
714 if not ftransSendServerMsg(nc^, trans_omsg) then exit;
715 end;
716 else
717 begin
718 killClientByFT(nc^);
719 exit;
720 end;
721 end;
722 end;
725 //**************************************************************************
727 // file transfer crap (both client and server)
729 //**************************************************************************
731 function getNewTimeoutEnd (): Int64;
732 begin
733 result := GetTimerMS();
734 if (g_Net_DownloadTimeout <= 0) then
735 begin
736 result := result+1000*60*3; // 3 minutes
738 else
739 begin
740 result := result+trunc(g_Net_DownloadTimeout*1000);
741 end;
742 end;
745 // send map request to server, and wait for "map info" server reply
747 // returns `false` on error or user abort
748 // fills:
749 // diskName: map wad file name (without a path)
750 // hash: map wad hash
751 // size: map wad size
752 // chunkSize: set too
753 // resList: list of resource wads
754 // returns:
755 // <0 on error
756 // 0 on success
757 // 1 on user abort
758 // 2 on server abort
759 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
760 function g_Net_Wait_MapInfo (var tf: TNetFileTransfer; var resList: TNetMapResourceInfoArray): Integer;
762 ev: ENetEvent;
763 rMsgId: Byte;
764 Ptr: Pointer;
765 msg: TMsg;
766 freePacket: Boolean = false;
767 ct, ett: Int64;
768 status: cint;
769 s: AnsiString;
770 rc, f: LongInt;
771 ri: ^TNetMapResourceInfo;
772 begin
773 SetLength(resList, 0);
775 // send request
776 trans_omsg.Clear();
777 trans_omsg.Write(Byte(NTF_CLIENT_MAP_REQUEST));
778 if not ftransSendClientMsg(trans_omsg) then begin result := -1; exit; end;
780 FillChar(ev, SizeOf(ev), 0);
781 Result := -1;
783 ett := getNewTimeoutEnd();
784 repeat
785 status := enet_host_service(NetHost, @ev, 300);
787 if (status < 0) then
788 begin
789 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True);
790 Result := -1;
791 exit;
792 end;
794 if (status <= 0) then
795 begin
796 // check for timeout
797 ct := GetTimerMS();
798 if (ct >= ett) then
799 begin
800 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' timeout reached', True);
801 Result := -1;
802 exit;
803 end;
805 else
806 begin
807 // some event
808 case ev.kind of
809 ENET_EVENT_TYPE_RECEIVE:
810 begin
811 freePacket := true;
812 if (ev.channelID <> NET_CHAN_DOWNLOAD) then
813 begin
814 //e_LogWritefln('g_Net_Wait_MapInfo: skip message from non-transfer channel', []);
815 freePacket := false;
816 g_Net_Client_HandlePacket(ev.packet, g_Net_ClientLightMsgHandler);
817 if (g_Res_received_map_start < 0) then begin result := -666; exit; end;
819 else
820 begin
821 ett := getNewTimeoutEnd();
822 if (ev.packet.dataLength < 1) then
823 begin
824 e_LogWritefln('g_Net_Wait_MapInfo: invalid server packet (no data)', []);
825 Result := -1;
826 exit;
827 end;
828 Ptr := ev.packet^.data;
829 rMsgId := Byte(Ptr^);
830 e_LogWritefln('g_Net_Wait_MapInfo: got message %u from server (dataLength=%u)', [rMsgId, ev.packet^.dataLength]);
831 if (rMsgId = NTF_SERVER_FILE_INFO) then
832 begin
833 e_LogWritefln('g_Net_Wait_MapInfo: waiting for map info reply, but got file info reply', []);
834 Result := -1;
835 exit;
837 else if (rMsgId = NTF_SERVER_ABORT) then
838 begin
839 e_LogWritefln('g_Net_Wait_MapInfo: server aborted transfer', []);
840 Result := 2;
841 exit;
843 else if (rMsgId = NTF_SERVER_MAP_INFO) then
844 begin
845 e_LogWritefln('g_Net_Wait_MapInfo: creating map info packet...', []);
846 if not msg.Init(ev.packet^.data+1, ev.packet^.dataLength-1, True) then exit;
847 e_LogWritefln('g_Net_Wait_MapInfo: parsing map info packet (rd=%d; max=%d)...', [msg.ReadCount, msg.MaxSize]);
848 SetLength(resList, 0); // just in case
849 // map wad name
850 tf.diskName := msg.ReadString();
851 e_LogWritefln('g_Net_Wait_MapInfo: map wad is `%s`', [tf.diskName]);
852 // map wad md5
853 tf.hash := msg.ReadMD5();
854 // map wad size
855 tf.size := msg.ReadLongInt();
856 e_LogWritefln('g_Net_Wait_MapInfo: map wad size is %d', [tf.size]);
857 // number of external resources for map
858 rc := msg.ReadLongInt();
859 if (rc < 0) or (rc > 1024) then
860 begin
861 e_LogWritefln('g_Net_Wait_MapInfo: invalid number of map external resources (%d)', [rc]);
862 Result := -1;
863 exit;
864 end;
865 e_LogWritefln('g_Net_Wait_MapInfo: map external resource count is %d', [rc]);
866 SetLength(resList, rc);
867 // external resource names
868 for f := 0 to rc-1 do
869 begin
870 ri := @resList[f];
871 s := msg.ReadString();
872 if (length(s) = 0) then begin result := -1; exit; end;
873 if (s = '!') then
874 begin
875 // extended packet
876 ri.size := msg.ReadLongInt();
877 ri.hash := msg.ReadMD5();
878 ri.wadName := ExtractFileName(msg.ReadString());
879 if (length(ri.wadName) = 0) or (ri.size < 0) then begin result := -1; exit; end;
881 else
882 begin
883 // old-style packet, only name
884 ri.wadName := ExtractFileName(s);
885 if (length(ri.wadName) = 0) then begin result := -1; exit; end;
886 ri.size := -1; // unknown
887 end;
888 end;
889 e_LogWritefln('g_Net_Wait_MapInfo: got map info', []);
890 Result := 0; // success
891 exit;
893 else
894 begin
895 e_LogWritefln('g_Net_Wait_MapInfo: invalid server packet type', []);
896 Result := -1;
897 exit;
898 end;
899 end;
900 end;
901 ENET_EVENT_TYPE_DISCONNECT:
902 begin
903 if (ev.data <= NET_DISC_MAX) then
904 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' ' + _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + ev.data)], True);
905 Result := -1;
906 exit;
907 end;
908 else
909 begin
910 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' unknown ENet event ' + IntToStr(Ord(ev.kind)), True);
911 result := -1;
912 exit;
913 end;
914 end;
915 if (freePacket) then begin freePacket := false; enet_packet_destroy(ev.packet); end;
916 end;
918 ProcessLoading(False);
919 if g_Net_UserRequestExit() then
920 begin
921 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' user abort', True);
922 Result := 1;
923 exit;
924 end;
925 until false;
926 finally
927 if (freePacket) then enet_packet_destroy(ev.packet);
928 end;
929 end;
932 // send file request to server, and wait for server reply
934 // returns `false` on error or user abort
935 // fills:
936 // diskName (actually, base name)
937 // hash
938 // size
939 // chunkSize
940 // returns:
941 // <0 on error
942 // 0 on success
943 // 1 on user abort
944 // 2 on server abort
945 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
946 function g_Net_RequestResFileInfo (resIndex: LongInt; out tf: TNetFileTransfer): Integer;
948 ev: ENetEvent;
949 rMsgId: Byte;
950 Ptr: Pointer;
951 msg: TMsg;
952 freePacket: Boolean = false;
953 ct, ett: Int64;
954 status: cint;
955 begin
956 // send request
957 trans_omsg.Clear();
958 trans_omsg.Write(Byte(NTF_CLIENT_FILE_REQUEST));
959 trans_omsg.Write(resIndex);
960 if not ftransSendClientMsg(trans_omsg) then begin result := -1; exit; end;
962 FillChar(ev, SizeOf(ev), 0);
963 Result := -1;
965 ett := getNewTimeoutEnd();
966 repeat
967 status := enet_host_service(NetHost, @ev, 300);
969 if (status < 0) then
970 begin
971 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True);
972 Result := -1;
973 exit;
974 end;
976 if (status <= 0) then
977 begin
978 // check for timeout
979 ct := GetTimerMS();
980 if (ct >= ett) then
981 begin
982 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' timeout reached', True);
983 Result := -1;
984 exit;
985 end;
987 else
988 begin
989 // some event
990 case ev.kind of
991 ENET_EVENT_TYPE_RECEIVE:
992 begin
993 freePacket := true;
994 if (ev.channelID <> NET_CHAN_DOWNLOAD) then
995 begin
996 //e_LogWriteln('g_Net_RequestResFileInfo: skip message from non-transfer channel');
997 freePacket := false;
998 g_Net_Client_HandlePacket(ev.packet, g_Net_ClientLightMsgHandler);
999 if (g_Res_received_map_start < 0) then begin result := -666; exit; end;
1001 else
1002 begin
1003 ett := getNewTimeoutEnd();
1004 if (ev.packet.dataLength < 1) then
1005 begin
1006 e_LogWriteln('g_Net_RequestResFileInfo: invalid server packet (no data)');
1007 Result := -1;
1008 exit;
1009 end;
1010 Ptr := ev.packet^.data;
1011 rMsgId := Byte(Ptr^);
1012 e_LogWritefln('received transfer packet with id %d (%u bytes)', [rMsgId, ev.packet^.dataLength]);
1013 if (rMsgId = NTF_SERVER_FILE_INFO) then
1014 begin
1015 if not msg.Init(ev.packet^.data+1, ev.packet^.dataLength-1, True) then exit;
1016 tf.hash := msg.ReadMD5();
1017 tf.size := msg.ReadLongInt();
1018 tf.chunkSize := msg.ReadLongInt();
1019 tf.diskName := ExtractFileName(msg.readString());
1020 if (tf.size < 0) or (tf.chunkSize <> FILE_CHUNK_SIZE) or (length(tf.diskName) = 0) then
1021 begin
1022 e_LogWritefln('g_Net_RequestResFileInfo: invalid file info packet', []);
1023 Result := -1;
1024 exit;
1025 end;
1026 e_LogWritefln('got file info for resource #%d: size=%d; name=%s', [resIndex, tf.size, tf.diskName]);
1027 Result := 0; // success
1028 exit;
1030 else if (rMsgId = NTF_SERVER_ABORT) then
1031 begin
1032 e_LogWriteln('g_Net_RequestResFileInfo: server aborted transfer');
1033 Result := 2;
1034 exit;
1036 else if (rMsgId = NTF_SERVER_MAP_INFO) then
1037 begin
1038 e_LogWriteln('g_Net_RequestResFileInfo: waiting for map info reply, but got file info reply');
1039 Result := -1;
1040 exit;
1042 else
1043 begin
1044 e_LogWriteln('g_Net_RequestResFileInfo: invalid server packet type');
1045 Result := -1;
1046 exit;
1047 end;
1048 end;
1049 end;
1050 ENET_EVENT_TYPE_DISCONNECT:
1051 begin
1052 if (ev.data <= NET_DISC_MAX) then
1053 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' ' + _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + ev.data)], True);
1054 Result := -1;
1055 exit;
1056 end;
1057 else
1058 begin
1059 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' unknown ENet event ' + IntToStr(Ord(ev.kind)), True);
1060 result := -1;
1061 exit;
1062 end;
1063 end;
1064 if (freePacket) then begin freePacket := false; enet_packet_destroy(ev.packet); end;
1065 end;
1067 ProcessLoading(False);
1068 if g_Net_UserRequestExit() then
1069 begin
1070 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' user abort', True);
1071 Result := 1;
1072 exit;
1073 end;
1074 until false;
1075 finally
1076 if (freePacket) then enet_packet_destroy(ev.packet);
1077 end;
1078 end;
1081 // call this to cancel file transfer requested by `g_Net_RequestResFileInfo()`
1082 function g_Net_AbortResTransfer (var tf: TNetFileTransfer): Boolean;
1083 begin
1084 result := false;
1085 e_LogWritefln('aborting file transfer...', []);
1086 // send request
1087 trans_omsg.Clear();
1088 trans_omsg.Write(Byte(NTF_CLIENT_ABORT));
1089 result := ftransSendClientMsg(trans_omsg);
1090 if result then enet_host_flush(NetHost);
1091 end;
1094 // call this to start file transfer requested by `g_Net_RequestResFileInfo()`
1096 // returns `false` on error or user abort
1097 // fills:
1098 // hash
1099 // size
1100 // chunkSize
1101 // returns:
1102 // <0 on error
1103 // 0 on success
1104 // 1 on user abort
1105 // 2 on server abort
1106 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
1107 function g_Net_ReceiveResourceFile (resIndex: LongInt; var tf: TNetFileTransfer; strm: TStream): Integer;
1109 ev: ENetEvent;
1110 rMsgId: Byte;
1111 Ptr: Pointer;
1112 msg: TMsg;
1113 freePacket: Boolean = false;
1114 ct, ett: Int64;
1115 status: cint;
1116 nextChunk: Integer = 0;
1117 chunkTotal: Integer;
1118 chunk: Integer;
1119 csize: Integer;
1120 buf: PChar = nil;
1121 resumed: Boolean;
1122 //stx: Int64;
1123 begin
1124 tf.resumed := false;
1125 e_LogWritefln('file `%s`, size=%d (%d)', [tf.diskName, Integer(strm.size), tf.size], TMsgType.Notify);
1126 // check if we should resume downloading
1127 resumed := (strm.size > tf.chunkSize) and (strm.size < tf.size);
1128 // send request
1129 trans_omsg.Clear();
1130 trans_omsg.Write(Byte(NTF_CLIENT_START));
1131 if resumed then chunk := strm.size div tf.chunkSize else chunk := 0;
1132 trans_omsg.Write(LongInt(chunk));
1133 if not ftransSendClientMsg(trans_omsg) then begin result := -1; exit; end;
1135 strm.Seek(chunk*tf.chunkSize, soFromBeginning);
1136 chunkTotal := (tf.size+tf.chunkSize-1) div tf.chunkSize;
1137 e_LogWritefln('receiving file `%s` (%d chunks)', [tf.diskName, chunkTotal], TMsgType.Notify);
1138 g_Game_SetLoadingText('downloading "'+ExtractFileName(tf.diskName)+'"', chunkTotal, False);
1139 tf.resumed := resumed;
1141 if (chunk > 0) then g_Game_StepLoading(chunk);
1142 nextChunk := chunk;
1144 // wait for reply data
1145 FillChar(ev, SizeOf(ev), 0);
1146 Result := -1;
1147 GetMem(buf, tf.chunkSize);
1149 ett := getNewTimeoutEnd();
1150 repeat
1151 //stx := -GetTimerMS();
1152 status := enet_host_service(NetHost, @ev, 300);
1154 if (status < 0) then
1155 begin
1156 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True);
1157 Result := -1;
1158 exit;
1159 end;
1161 if (status <= 0) then
1162 begin
1163 // check for timeout
1164 ct := GetTimerMS();
1165 if (ct >= ett) then
1166 begin
1167 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' timeout reached', True);
1168 Result := -1;
1169 exit;
1170 end;
1172 else
1173 begin
1174 // some event
1175 case ev.kind of
1176 ENET_EVENT_TYPE_RECEIVE:
1177 begin
1178 freePacket := true;
1179 if (ev.channelID <> NET_CHAN_DOWNLOAD) then
1180 begin
1181 //e_LogWritefln('g_Net_ReceiveResourceFile: skip message from non-transfer channel', []);
1182 freePacket := false;
1183 g_Net_Client_HandlePacket(ev.packet, g_Net_ClientLightMsgHandler);
1184 if (g_Res_received_map_start < 0) then begin result := -666; exit; end;
1186 else
1187 begin
1188 //stx := stx+GetTimerMS();
1189 //e_LogWritefln('g_Net_ReceiveResourceFile: stx=%d', [Integer(stx)]);
1190 //stx := -GetTimerMS();
1191 ett := getNewTimeoutEnd();
1192 if (ev.packet.dataLength < 1) then
1193 begin
1194 e_LogWritefln('g_Net_ReceiveResourceFile: invalid server packet (no data)', []);
1195 Result := -1;
1196 exit;
1197 end;
1198 Ptr := ev.packet^.data;
1199 rMsgId := Byte(Ptr^);
1200 if (rMsgId = NTF_SERVER_DONE) then
1201 begin
1202 e_LogWritefln('file transfer complete.', []);
1203 result := 0;
1204 exit;
1206 else if (rMsgId = NTF_SERVER_CHUNK) then
1207 begin
1208 if not msg.Init(ev.packet^.data+1, ev.packet^.dataLength-1, True) then exit;
1209 chunk := msg.ReadLongInt();
1210 csize := msg.ReadLongInt();
1211 if (chunk <> nextChunk) then
1212 begin
1213 e_LogWritefln('received chunk %d, but expected chunk %d', [chunk, nextChunk]);
1214 Result := -1;
1215 exit;
1216 end;
1217 if (csize < 0) or (csize > tf.chunkSize) then
1218 begin
1219 e_LogWritefln('received chunk with size %d, but expected chunk size is %d', [csize, tf.chunkSize]);
1220 Result := -1;
1221 exit;
1222 end;
1223 //e_LogWritefln('got chunk #%d of #%d (csize=%d)', [chunk, (tf.size+tf.chunkSize-1) div tf.chunkSize, csize]);
1224 msg.ReadData(buf, csize);
1225 strm.WriteBuffer(buf^, csize);
1226 nextChunk := chunk+1;
1227 g_Game_StepLoading();
1228 // send ack
1229 trans_omsg.Clear();
1230 trans_omsg.Write(Byte(NTF_CLIENT_ACK));
1231 trans_omsg.Write(LongInt(chunk));
1232 if not ftransSendClientMsg(trans_omsg) then begin result := -1; exit; end;
1234 else if (rMsgId = NTF_SERVER_ABORT) then
1235 begin
1236 e_LogWritefln('g_Net_ReceiveResourceFile: server aborted transfer', []);
1237 Result := 2;
1238 exit;
1240 else
1241 begin
1242 e_LogWritefln('g_Net_ReceiveResourceFile: invalid server packet type', []);
1243 Result := -1;
1244 exit;
1245 end;
1246 //stx := stx+GetTimerMS();
1247 //e_LogWritefln('g_Net_ReceiveResourceFile: process stx=%d', [Integer(stx)]);
1248 end;
1249 end;
1250 ENET_EVENT_TYPE_DISCONNECT:
1251 begin
1252 if (ev.data <= NET_DISC_MAX) then
1253 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' ' + _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + ev.data)], True);
1254 Result := -1;
1255 exit;
1256 end;
1257 else
1258 begin
1259 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' unknown ENet event ' + IntToStr(Ord(ev.kind)), True);
1260 result := -1;
1261 exit;
1262 end;
1263 end;
1264 if (freePacket) then begin freePacket := false; enet_packet_destroy(ev.packet); end;
1265 end;
1267 ProcessLoading(False);
1268 if g_Net_UserRequestExit() then
1269 begin
1270 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' user abort', True);
1271 Result := 1;
1272 exit;
1273 end;
1274 until false;
1275 finally
1276 FreeMem(buf);
1277 if (freePacket) then enet_packet_destroy(ev.packet);
1278 end;
1279 end;
1282 //**************************************************************************
1284 // common functions
1286 //**************************************************************************
1288 function g_Net_FindSlot(): Integer;
1290 I: Integer;
1291 F: Boolean;
1292 N, C: Integer;
1293 begin
1294 N := -1;
1295 F := False;
1296 C := 0;
1297 for I := Low(NetClients) to High(NetClients) do
1298 begin
1299 if NetClients[I].Used then
1300 Inc(C)
1301 else
1302 if not F then
1303 begin
1304 F := True;
1305 N := I;
1306 end;
1307 end;
1308 if C >= NetMaxClients then
1309 begin
1310 Result := -1;
1311 Exit;
1312 end;
1314 if not F then
1315 begin
1316 if (Length(NetClients) >= NetMaxClients) then
1317 N := -1
1318 else
1319 begin
1320 SetLength(NetClients, Length(NetClients) + 1);
1321 N := High(NetClients);
1322 end;
1323 end;
1325 if N >= 0 then
1326 begin
1327 NetClients[N].Used := True;
1328 NetClients[N].ID := N;
1329 NetClients[N].RequestedFullUpdate := False;
1330 NetClients[N].WaitForFirstSpawn := False;
1331 NetClients[N].RCONAuth := False;
1332 NetClients[N].Voted := False;
1333 NetClients[N].Player := 0;
1334 clearNetClientTransfers(NetClients[N]); // just in case
1335 end;
1337 Result := N;
1338 end;
1341 function g_Net_Init(): Boolean;
1343 F: TextFile;
1344 IPstr: string;
1345 IP: LongWord;
1346 path: AnsiString;
1347 begin
1348 NetIn.Clear();
1349 NetOut.Clear();
1350 NetBuf[NET_UNRELIABLE].Clear();
1351 NetBuf[NET_RELIABLE].Clear();
1352 clearNetClients();
1353 NetPeer := nil;
1354 NetHost := nil;
1355 NetMyID := -1;
1356 NetPlrUID1 := -1;
1357 NetPlrUID2 := -1;
1358 NetAddr.port := 25666;
1359 SetLength(NetBannedHosts, 0);
1360 path := BANLIST_FILENAME;
1361 if e_FindResource(DataDirs, path) = true then
1362 begin
1363 Assign(F, path);
1364 Reset(F);
1365 while not EOF(F) do
1366 begin
1367 Readln(F, IPstr);
1368 if StrToIp(IPstr, IP) then
1369 g_Net_BanAddress(IP);
1370 end;
1371 CloseFile(F);
1372 g_Net_SaveBanList();
1373 end;
1375 //Result := (enet_initialize() = 0);
1376 Result := enet_init_success;
1377 end;
1379 procedure g_Net_Flush();
1381 T: Integer;
1382 P: pENetPacket;
1383 F, Chan: enet_uint32;
1384 I: Integer;
1385 begin
1386 F := 0;
1387 Chan := NET_CHAN_UNRELIABLE;
1389 if NetMode = NET_SERVER then
1390 for T := NET_UNRELIABLE to NET_RELIABLE do
1391 begin
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_RELIABLE;
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_RELIABLE;
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 clearNetClients();
1430 NetClientCount := 0;
1432 NetPeer := nil;
1433 NetHost := nil;
1434 g_Net_Slist_ServerClosed();
1435 NetMyID := -1;
1436 NetPlrUID1 := -1;
1437 NetPlrUID2 := -1;
1438 NetState := NET_STATE_NONE;
1440 NetPongSock := ENET_SOCKET_NULL;
1442 NetTimeToMaster := 0;
1443 NetTimeToUpdate := 0;
1444 NetTimeToReliable := 0;
1446 NetMode := NET_NONE;
1448 if NetPortThread <> NilThreadId then
1449 begin
1450 // TODO: Use TThread instead of procedural threading API to manage thread resources properly.
1451 WaitForThreadTerminate(NetPortThread, 66666);
1452 CloseThread(NetPortThread);
1453 NetPortThread := NilThreadId;
1454 end;
1456 g_Net_UnforwardPorts();
1458 if NetDump then
1459 g_Net_DumpEnd();
1460 end;
1462 procedure g_Net_Free();
1463 begin
1464 g_Net_Cleanup();
1466 //enet_deinitialize();
1467 NetInitDone := False;
1468 end;
1471 //**************************************************************************
1473 // SERVER FUNCTIONS
1475 //**************************************************************************
1477 function ForwardThread(Param: Pointer): PtrInt;
1478 begin
1479 Result := 0;
1480 if not g_Net_ForwardPorts() then Result := -1;
1481 end;
1483 function g_Net_Host(IPAddr: LongWord; Port: enet_uint16; MaxClients: Cardinal): Boolean;
1484 begin
1485 if NetMode <> NET_NONE then
1486 begin
1487 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_INGAME]);
1488 Result := False;
1489 Exit;
1490 end;
1492 Result := True;
1494 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_HOST], [Port]));
1495 if not NetInitDone then
1496 begin
1497 if not g_Net_Init() then
1498 begin
1499 g_Console_Add(_lc[I_NET_MSG_FERROR] + _lc[I_NET_ERR_ENET]);
1500 Result := False;
1501 Exit;
1503 else
1504 NetInitDone := True;
1505 end;
1507 NetAddr.host := IPAddr;
1508 NetAddr.port := Port;
1510 NetHost := enet_host_create(@NetAddr, NET_MAXCLIENTS, NET_CHANNELS, 0, 0);
1512 if NetHost = nil then
1513 begin
1514 g_Console_Add(_lc[I_NET_MSG_ERROR] + Format(_lc[I_NET_ERR_HOST], [Port]));
1515 Result := False;
1516 g_Net_Cleanup();
1517 Exit;
1518 end;
1520 if NetForwardPorts then
1521 NetPortThread := BeginThread(ForwardThread);
1523 NetPongSock := enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
1524 if NetPongSock <> ENET_SOCKET_NULL then
1525 begin
1526 NetPongAddr.host := IPAddr;
1527 NetPongAddr.port := NET_PING_PORT;
1528 if enet_socket_bind(NetPongSock, @NetPongAddr) < 0 then
1529 begin
1530 enet_socket_destroy(NetPongSock);
1531 NetPongSock := ENET_SOCKET_NULL;
1533 else
1534 enet_socket_set_option(NetPongSock, ENET_SOCKOPT_NONBLOCK, 1);
1535 end;
1537 NetMode := NET_SERVER;
1538 NetOut.Clear();
1539 NetBuf[NET_UNRELIABLE].Clear();
1540 NetBuf[NET_RELIABLE].Clear();
1542 if NetDump then
1543 g_Net_DumpStart();
1544 end;
1546 procedure g_Net_Host_Die();
1548 I: Integer;
1549 begin
1550 if NetMode <> NET_SERVER then
1551 Exit;
1553 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_DISCALL]);
1554 for I := 0 to High(NetClients) do
1555 if NetClients[I].Used then
1556 enet_peer_disconnect(NetClients[I].Peer, NET_DISC_DOWN);
1558 while enet_host_service(NetHost, @NetEvent, 1000) > 0 do
1559 if NetEvent.kind = ENET_EVENT_TYPE_RECEIVE then
1560 enet_packet_destroy(NetEvent.packet);
1562 for I := 0 to High(NetClients) do
1563 if NetClients[I].Used then
1564 begin
1565 FreeMemory(NetClients[I].Peer^.data);
1566 NetClients[I].Peer^.data := nil;
1567 enet_peer_reset(NetClients[I].Peer);
1568 NetClients[I].Peer := nil;
1569 NetClients[I].Used := False;
1570 NetClients[I].Player := 0;
1571 NetClients[I].Crimes := 0;
1572 NetClients[I].AuthTime := 0;
1573 NetClients[I].MsgTime := 0;
1574 NetClients[I].NetOut[NET_UNRELIABLE].Free();
1575 NetClients[I].NetOut[NET_RELIABLE].Free();
1576 end;
1578 g_Net_Slist_ServerClosed();
1579 if NetPongSock <> ENET_SOCKET_NULL then
1580 enet_socket_destroy(NetPongSock);
1582 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_DIE]);
1583 enet_host_destroy(NetHost);
1585 NetMode := NET_NONE;
1587 g_Net_Cleanup();
1588 e_WriteLog('NET: Server stopped', TMsgType.Notify);
1589 end;
1592 procedure g_Net_Host_Send(ID: Integer; Reliable: Boolean);
1594 T: Integer;
1595 begin
1596 if Reliable
1597 then T := NET_RELIABLE
1598 else T := NET_UNRELIABLE;
1600 if (ID >= 0) then
1601 begin
1602 if ID > High(NetClients) then Exit;
1603 if NetClients[ID].Peer = nil then Exit;
1604 // write size first
1605 NetClients[ID].NetOut[T].Write(Integer(NetOut.CurSize));
1606 NetClients[ID].NetOut[T].Write(NetOut);
1608 else
1609 begin
1610 for ID := Low(NetClients) to High(NetClients) do
1611 begin
1612 if NetClients[ID].Used then
1613 begin
1614 // write size first
1615 NetClients[ID].NetOut[T].Write(Integer(NetOut.CurSize));
1616 NetClients[ID].NetOut[T].Write(NetOut);
1617 end;
1618 end;
1619 end;
1621 if NetDump then g_Net_DumpSendBuffer();
1622 NetOut.Clear();
1623 end;
1625 procedure g_Net_Host_Disconnect_Client(ID: Integer; Force: Boolean = False);
1627 TP: TPlayer;
1628 TC: pTNetClient;
1629 begin
1630 TC := @NetClients[ID];
1631 if (TC = nil) then Exit;
1632 clearNetClient(NetClients[ID]);
1633 if not (TC^.Used) then Exit;
1635 TP := g_Player_Get(TC^.Player);
1637 if TP <> nil then
1638 begin
1639 TP.Lives := 0;
1640 TP.Kill(K_SIMPLEKILL, 0, HIT_DISCON);
1641 g_Console_Add(Format(_lc[I_PLAYER_LEAVE], [TP.Name]), True);
1642 e_WriteLog('NET: Client ' + TP.Name + ' [' + IntToStr(TC^.ID) + '] disconnected.', TMsgType.Notify);
1643 g_Player_Remove(TP.UID);
1644 end;
1646 if (TC^.Peer^.data <> nil) then
1647 begin
1648 FreeMemory(TC^.Peer^.data);
1649 TC^.Peer^.data := nil;
1650 end;
1652 if (Force) then
1653 enet_peer_reset(TC^.Peer);
1655 TC^.Used := False;
1656 TC^.State := NET_STATE_NONE;
1657 TC^.Peer := nil;
1658 TC^.Player := 0;
1659 TC^.Crimes := 0;
1660 TC^.AuthTime := 0;
1661 TC^.MsgTime := 0;
1662 TC^.RequestedFullUpdate := False;
1663 TC^.FullUpdateSent := False;
1664 TC^.WaitForFirstSpawn := False;
1665 TC^.NetOut[NET_UNRELIABLE].Free();
1666 TC^.NetOut[NET_RELIABLE].Free();
1668 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_HOST_DISC], [ID]));
1669 Dec(NetClientCount);
1671 if NetUseMaster then g_Net_Slist_ServerPlayerLeaves();
1672 end;
1674 procedure g_Net_Host_Kick(ID: Integer; Reason: enet_uint32);
1676 Peer: pENetPeer;
1677 TC: pTNetClient;
1678 begin
1679 TC := @NetClients[ID];
1680 if (TC <> nil) and TC^.Used and (TC^.Peer <> nil) then
1681 begin
1682 Peer := TC^.Peer;
1683 g_Net_Host_Disconnect_Client(ID);
1684 enet_peer_disconnect(Peer, Reason);
1685 end;
1686 end;
1688 procedure g_Net_Host_CheckPings();
1690 ClAddr: ENetAddress;
1691 Buf: ENetBuffer;
1692 Len: Integer;
1693 ClTime: Int64;
1694 Ping: array [0..9] of Byte;
1695 NPl: Byte;
1696 begin
1697 if (NetPongSock = ENET_SOCKET_NULL) or (NetHost = nil) then Exit;
1699 Buf.data := Addr(Ping[0]);
1700 Buf.dataLength := 2+8;
1702 Ping[0] := 0;
1704 Len := enet_socket_receive(NetPongSock, @ClAddr, @Buf, 1);
1705 if Len < 0 then Exit;
1707 if (Ping[0] = Ord('D')) and (Ping[1] = Ord('F')) then
1708 begin
1709 ClTime := Int64(Addr(Ping[2])^);
1711 NetOut.Clear();
1712 NetOut.Write(Byte(Ord('D')));
1713 NetOut.Write(Byte(Ord('F')));
1714 NetOut.Write(NetHost.address.port);
1715 NetOut.Write(ClTime);
1716 TMasterHost.writeInfo(NetOut);
1717 NPl := 0;
1718 if gPlayer1 <> nil then Inc(NPl);
1719 if gPlayer2 <> nil then Inc(NPl);
1720 NetOut.Write(NPl);
1721 NetOut.Write(gNumBots);
1723 Buf.data := NetOut.Data;
1724 Buf.dataLength := NetOut.CurSize;
1725 enet_socket_send(NetPongSock, @ClAddr, @Buf, 1);
1727 NetOut.Clear();
1728 end;
1729 end;
1731 procedure g_Net_Host_CheckTimeouts();
1733 ID: Integer;
1734 begin
1735 for ID := Low(NetClients) to High(NetClients) do
1736 begin
1737 with NetClients[ID] do
1738 begin
1739 if (Peer = nil) or (State = NET_STATE_NONE) then continue;
1740 if (State = NET_STATE_AUTH) and (AuthTime > 0) and (AuthTime <= gTime) then
1741 begin
1742 g_Net_Penalize(@NetClients[ID], 'auth taking too long');
1743 AuthTime := gTime + 1000; // do it every second to give them a chance
1745 else if (State = NET_STATE_GAME) and (MsgTime > 0) and (MsgTime <= gTime) then
1746 begin
1747 // client hasn't sent packets in a while; either ban em or kick em
1748 if (NetAutoBanForTimeout) then
1749 begin
1750 g_Net_Penalize(@NetClients[ID], 'message timeout');
1751 MsgTime := gTime + (NetPacketTimeout div 2) + 500; // wait less for the next check
1753 else
1754 begin
1755 e_LogWritefln('NET: client #%u (cid #%u) timed out', [ID, Player]);
1756 g_Net_Host_Disconnect_Client(ID, True);
1757 end;
1758 end;
1759 end;
1760 end;
1761 end;
1764 procedure g_Net_Host_Update();
1766 IP: string;
1767 Port: Word;
1768 ID: Integer;
1769 TC: pTNetClient;
1770 begin
1771 IP := '';
1773 if NetUseMaster then g_Net_Slist_Pulse();
1774 g_Net_Host_CheckPings();
1775 g_Net_Host_CheckTimeouts();
1777 while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
1778 begin
1779 case (NetEvent.kind) of
1780 ENET_EVENT_TYPE_CONNECT:
1781 begin
1782 IP := IpToStr(NetEvent.Peer^.address.host);
1783 Port := NetEvent.Peer^.address.port;
1784 g_Console_Add(_lc[I_NET_MSG] +
1785 Format(_lc[I_NET_MSG_HOST_CONN], [IP, Port]));
1786 e_WriteLog('NET: Connection request from ' + IP + '.', TMsgType.Notify);
1788 if (NetEvent.data <> NET_PROTOCOL_VER) then
1789 begin
1790 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_REJECT] +
1791 _lc[I_NET_DISC_PROTOCOL]);
1792 e_WriteLog('NET: Connection request from ' + IP + ' rejected: version mismatch',
1793 TMsgType.Notify);
1794 enet_peer_disconnect(NetEvent.peer, NET_DISC_PROTOCOL);
1795 Exit;
1796 end;
1798 if g_Net_IsAddressBanned(NetEvent.Peer^.address.host) then
1799 begin
1800 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_REJECT] +
1801 _lc[I_NET_DISC_BAN]);
1802 e_WriteLog('NET: Connection request from ' + IP + ' rejected: banned',
1803 TMsgType.Notify);
1804 enet_peer_disconnect(NetEvent.Peer, NET_DISC_BAN);
1805 Exit;
1806 end;
1808 ID := g_Net_FindSlot();
1810 if ID < 0 then
1811 begin
1812 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_REJECT] +
1813 _lc[I_NET_DISC_FULL]);
1814 e_WriteLog('NET: Connection request from ' + IP + ' rejected: server full',
1815 TMsgType.Notify);
1816 enet_peer_disconnect(NetEvent.peer, NET_DISC_FULL);
1817 Exit;
1818 end;
1820 NetClients[ID].Peer := NetEvent.peer;
1821 NetClients[ID].Peer^.data := GetMemory(SizeOf(Byte));
1822 Byte(NetClients[ID].Peer^.data^) := ID;
1823 NetClients[ID].State := NET_STATE_AUTH;
1824 NetClients[ID].Player := 0;
1825 NetClients[ID].Crimes := 0;
1826 NetClients[ID].RCONAuth := False;
1827 NetClients[ID].NetOut[NET_UNRELIABLE].Alloc(NET_BUFSIZE*2);
1828 NetClients[ID].NetOut[NET_RELIABLE].Alloc(NET_BUFSIZE*2);
1829 if (NetAuthTimeout > 0) then
1830 NetClients[ID].AuthTime := gTime + NetAuthTimeout
1831 else
1832 NetClients[ID].AuthTime := 0;
1833 if (NetPacketTimeout > 0) then
1834 NetClients[ID].MsgTime := gTime + NetPacketTimeout
1835 else
1836 NetClients[ID].MsgTime := 0;
1837 clearNetClientTransfers(NetClients[ID]); // just in case
1839 enet_peer_timeout(NetEvent.peer, ENET_PEER_TIMEOUT_LIMIT * 2, ENET_PEER_TIMEOUT_MINIMUM * 2, ENET_PEER_TIMEOUT_MAXIMUM * 2);
1841 Inc(NetClientCount);
1842 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_HOST_ADD], [ID]));
1843 end;
1845 ENET_EVENT_TYPE_RECEIVE:
1846 begin
1847 //e_LogWritefln('RECEIVE: chan=%u', [NetEvent.channelID]);
1848 if (NetEvent.channelID = NET_CHAN_DOWNLOAD) then
1849 begin
1850 ProcessDownloadExPacket();
1852 else
1853 begin
1854 if NetEvent.peer^.data = nil then Exit;
1856 ID := Byte(NetEvent.peer^.data^);
1857 if ID > High(NetClients) then Exit;
1858 TC := @NetClients[ID];
1860 if (NetPacketTimeout > 0) then
1861 TC^.MsgTime := gTime + NetPacketTimeout;
1863 if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
1864 g_Net_Host_HandlePacket(TC, NetEvent.packet, g_Net_HostMsgHandler);
1865 end;
1866 end;
1868 ENET_EVENT_TYPE_DISCONNECT:
1869 begin
1870 if NetEvent.peer^.data <> nil then
1871 begin
1872 ID := Byte(NetEvent.peer^.data^);
1873 if ID > High(NetClients) then Exit;
1874 g_Net_Host_Disconnect_Client(ID);
1875 end;
1876 end;
1877 end;
1878 end;
1879 end;
1882 //**************************************************************************
1884 // CLIENT FUNCTIONS
1886 //**************************************************************************
1888 procedure g_Net_Disconnect(Forced: Boolean = False);
1889 begin
1890 if NetMode <> NET_CLIENT then Exit;
1891 if (NetHost = nil) or (NetPeer = nil) then Exit;
1893 if not Forced then
1894 begin
1895 enet_peer_disconnect(NetPeer, NET_DISC_NONE);
1897 while (enet_host_service(NetHost, @NetEvent, 1500) > 0) do
1898 begin
1899 if (NetEvent.kind = ENET_EVENT_TYPE_DISCONNECT) then
1900 begin
1901 NetPeer := nil;
1902 break;
1903 end;
1905 if (NetEvent.kind = ENET_EVENT_TYPE_RECEIVE) then
1906 enet_packet_destroy(NetEvent.packet);
1907 end;
1909 if NetPeer <> nil then
1910 begin
1911 enet_peer_reset(NetPeer);
1912 NetPeer := nil;
1913 end;
1915 else
1916 begin
1917 e_WriteLog('NET: Kicked from server: ' + IntToStr(NetEvent.data), TMsgType.Notify);
1918 if (NetEvent.data <= NET_DISC_MAX) then
1919 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_KICK] +
1920 _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + NetEvent.data)], True);
1921 end;
1923 if NetHost <> nil then
1924 begin
1925 enet_host_destroy(NetHost);
1926 NetHost := nil;
1927 end;
1928 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_CLIENT_DISC]);
1930 g_Net_Cleanup;
1931 e_WriteLog('NET: Disconnected', TMsgType.Notify);
1932 end;
1934 procedure g_Net_Client_Send(Reliable: Boolean);
1936 T: Integer;
1937 begin
1938 if Reliable
1939 then T := NET_RELIABLE
1940 else T := NET_UNRELIABLE;
1942 // write size first
1943 NetBuf[T].Write(Integer(NetOut.CurSize));
1944 NetBuf[T].Write(NetOut);
1946 if NetDump then g_Net_DumpSendBuffer();
1947 NetOut.Clear();
1948 g_Net_Flush(); // FIXME: for now, send immediately
1949 end;
1951 procedure g_Net_Client_Update();
1952 begin
1953 while (NetHost <> nil) and (enet_host_service(NetHost, @NetEvent, 0) > 0) do
1954 begin
1955 case NetEvent.kind of
1956 ENET_EVENT_TYPE_RECEIVE:
1957 begin
1958 if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
1959 g_Net_Client_HandlePacket(NetEvent.packet, g_Net_ClientMsgHandler);
1960 end;
1962 ENET_EVENT_TYPE_DISCONNECT:
1963 begin
1964 g_Net_Disconnect(True);
1965 Exit;
1966 end;
1967 end;
1969 end;
1971 function g_Net_Connect(IP: string; Port: enet_uint16): Boolean;
1973 OuterLoop: Boolean;
1974 TimeoutTime, T: Int64;
1975 begin
1976 if NetMode <> NET_NONE then
1977 begin
1978 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_ERR_INGAME], True);
1979 Result := False;
1980 Exit;
1981 end;
1983 Result := True;
1985 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_CLIENT_CONN],
1986 [IP, Port]));
1987 if not NetInitDone then
1988 begin
1989 if (not g_Net_Init()) then
1990 begin
1991 g_Console_Add(_lc[I_NET_MSG_FERROR] + _lc[I_NET_ERR_ENET], True);
1992 Result := False;
1993 Exit;
1995 else
1996 NetInitDone := True;
1997 end;
1999 NetHost := enet_host_create(nil, 1, NET_CHANNELS, 0, 0);
2001 if (NetHost = nil) then
2002 begin
2003 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CLIENT], True);
2004 g_Net_Cleanup;
2005 Result := False;
2006 Exit;
2007 end;
2009 enet_address_set_host(@NetAddr, PChar(Addr(IP[1])));
2010 NetAddr.port := Port;
2012 NetPeer := enet_host_connect(NetHost, @NetAddr, NET_CHANNELS, NET_PROTOCOL_VER);
2014 if (NetPeer = nil) then
2015 begin
2016 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CLIENT], True);
2017 enet_host_destroy(NetHost);
2018 g_Net_Cleanup;
2019 Result := False;
2020 Exit;
2021 end;
2023 // предупредить что ждем слишком долго через N секунд
2024 TimeoutTime := sys_GetTicks() + NET_CONNECT_TIMEOUT;
2026 OuterLoop := True;
2027 while OuterLoop do
2028 begin
2029 while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
2030 begin
2031 if (NetEvent.kind = ENET_EVENT_TYPE_CONNECT) then
2032 begin
2033 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_CLIENT_DONE]);
2034 NetMode := NET_CLIENT;
2035 NetOut.Clear();
2036 enet_peer_timeout(NetPeer, ENET_PEER_TIMEOUT_LIMIT * 2, ENET_PEER_TIMEOUT_MINIMUM * 2, ENET_PEER_TIMEOUT_MAXIMUM * 2);
2037 NetClientIP := IP;
2038 NetClientPort := Port;
2039 if NetDump then
2040 g_Net_DumpStart();
2041 Exit;
2042 end;
2043 end;
2045 T := sys_GetTicks();
2046 if T > TimeoutTime then
2047 begin
2048 TimeoutTime := T + NET_CONNECT_TIMEOUT * 100; // одного предупреждения хватит
2049 g_Console_Add(_lc[I_NET_MSG_TIMEOUT_WARN], True);
2050 g_Console_Add(Format(_lc[I_NET_MSG_PORTS], [Integer(Port), Integer(NET_PING_PORT)]), True);
2051 end;
2053 ProcessLoading(True);
2054 if e_KeyPressed(IK_SPACE) or g_Net_UserRequestExit() then
2055 OuterLoop := False;
2056 end;
2058 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_TIMEOUT], True);
2059 g_Console_Add(Format(_lc[I_NET_MSG_PORTS], [Integer(Port), Integer(NET_PING_PORT)]), True);
2060 if NetPeer <> nil then enet_peer_reset(NetPeer);
2061 if NetHost <> nil then
2062 begin
2063 enet_host_destroy(NetHost);
2064 NetHost := nil;
2065 end;
2066 g_Net_Cleanup();
2067 Result := False;
2068 end;
2070 function IpToStr(IP: LongWord): string;
2072 Ptr: Pointer;
2073 begin
2074 Ptr := Addr(IP);
2075 Result := IntToStr(PByte(Ptr + 0)^) + '.';
2076 Result := Result + IntToStr(PByte(Ptr + 1)^) + '.';
2077 Result := Result + IntToStr(PByte(Ptr + 2)^) + '.';
2078 Result := Result + IntToStr(PByte(Ptr + 3)^);
2079 end;
2081 function StrToIp(IPstr: string; var IP: LongWord): Boolean;
2083 EAddr: ENetAddress;
2084 begin
2085 Result := enet_address_set_host(@EAddr, PChar(@IPstr[1])) = 0;
2086 IP := EAddr.host;
2087 end;
2089 function g_Net_Client_ByName(Name: string): pTNetClient;
2091 a: Integer;
2092 pl: TPlayer;
2093 begin
2094 Result := nil;
2095 for a := Low(NetClients) to High(NetClients) do
2096 if (NetClients[a].Used) and (NetClients[a].State = NET_STATE_GAME) then
2097 begin
2098 pl := g_Player_Get(NetClients[a].Player);
2099 if pl = nil then continue;
2100 if Copy(LowerCase(pl.Name), 1, Length(Name)) <> LowerCase(Name) then continue;
2101 if NetClients[a].Peer <> nil then
2102 begin
2103 Result := @NetClients[a];
2104 Exit;
2105 end;
2106 end;
2107 end;
2109 function g_Net_Client_ByPlayer(PID: Word): pTNetClient;
2111 a: Integer;
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 if NetClients[a].Player = PID then
2117 begin
2118 Result := @NetClients[a];
2119 Exit;
2120 end;
2121 end;
2123 function g_Net_ClientName_ByID(ID: Integer): string;
2125 a: Integer;
2126 pl: TPlayer;
2127 begin
2128 Result := '';
2129 if ID = NET_EVERYONE then
2130 Exit;
2131 for a := Low(NetClients) to High(NetClients) do
2132 if (NetClients[a].ID = ID) and (NetClients[a].Used) and (NetClients[a].State = NET_STATE_GAME) then
2133 begin
2134 pl := g_Player_Get(NetClients[a].Player);
2135 if pl = nil then Exit;
2136 Result := pl.Name;
2137 Exit;
2138 end;
2139 Result := 'Client #' + IntToStr(ID);
2140 end;
2142 function g_Net_IsAddressBanned(IP: LongWord; Perm: Boolean = False): Boolean;
2144 I: Integer;
2145 begin
2146 Result := False;
2147 if NetBannedHosts = nil then
2148 Exit;
2149 for I := 0 to High(NetBannedHosts) do
2150 if (NetBannedHosts[I].IP = IP) and ((not Perm) or (NetBannedHosts[I].Perm)) then
2151 begin
2152 Result := True;
2153 break;
2154 end;
2155 end;
2157 procedure g_Net_BanAddress(IP: LongWord; Perm: Boolean = True); overload;
2159 I, P: Integer;
2160 begin
2161 if IP = 0 then
2162 Exit;
2163 if g_Net_IsAddressBanned(IP, Perm) then
2164 Exit;
2166 P := -1;
2167 for I := Low(NetBannedHosts) to High(NetBannedHosts) do
2168 if NetBannedHosts[I].IP = 0 then
2169 begin
2170 P := I;
2171 break;
2172 end;
2174 if P < 0 then
2175 begin
2176 SetLength(NetBannedHosts, Length(NetBannedHosts) + 1);
2177 P := High(NetBannedHosts);
2178 end;
2180 NetBannedHosts[P].IP := IP;
2181 NetBannedHosts[P].Perm := Perm;
2182 end;
2184 procedure g_Net_BanAddress(IP: string; Perm: Boolean = True); overload;
2186 a: LongWord;
2187 b: Boolean;
2188 begin
2189 b := StrToIp(IP, a);
2190 if b then
2191 g_Net_BanAddress(a, Perm);
2192 end;
2194 procedure g_Net_UnbanNonPerm();
2196 I: Integer;
2197 begin
2198 if NetBannedHosts = nil then
2199 Exit;
2200 for I := Low(NetBannedHosts) to High(NetBannedHosts) do
2201 if (NetBannedHosts[I].IP > 0) and not NetBannedHosts[I].Perm then
2202 begin
2203 NetBannedHosts[I].IP := 0;
2204 NetBannedHosts[I].Perm := True;
2205 end;
2206 end;
2208 function g_Net_UnbanAddress(IP: string): Boolean; overload;
2210 a: LongWord;
2211 begin
2212 Result := StrToIp(IP, a);
2213 if Result then
2214 Result := g_Net_UnbanAddress(a);
2215 end;
2217 function g_Net_UnbanAddress(IP: LongWord): Boolean; overload;
2219 I: Integer;
2220 begin
2221 Result := False;
2222 if IP = 0 then
2223 Exit;
2224 if NetBannedHosts = nil then
2225 Exit;
2226 for I := 0 to High(NetBannedHosts) do
2227 if NetBannedHosts[I].IP = IP then
2228 begin
2229 NetBannedHosts[I].IP := 0;
2230 NetBannedHosts[I].Perm := True;
2231 Result := True;
2232 // no break here to clear all bans of this host, perm and non-perm
2233 end;
2234 end;
2236 procedure g_Net_SaveBanList();
2238 F: TextFile;
2239 I: Integer;
2240 path: AnsiString;
2241 begin
2242 path := e_GetWriteableDir(DataDirs);
2243 if path <> '' then
2244 begin
2245 path := ConcatPaths([path, BANLIST_FILENAME]);
2246 Assign(F, path);
2247 Rewrite(F);
2248 if NetBannedHosts <> nil then
2249 for I := 0 to High(NetBannedHosts) do
2250 if NetBannedHosts[I].Perm and (NetBannedHosts[I].IP > 0) then
2251 Writeln(F, IpToStr(NetBannedHosts[I].IP));
2252 CloseFile(F)
2254 end;
2256 procedure g_Net_Host_Ban(C: pTNetClient; Perm: Boolean);
2258 KickReason: enet_uint32;
2259 Name: string;
2260 begin
2261 if (not C^.Used) then
2262 exit;
2264 if Perm then
2265 KickReason := NET_DISC_BAN
2266 else
2267 KickReason := NET_DISC_TEMPBAN;
2269 Name := g_Net_ClientName_ByID(C^.ID);
2271 g_Net_BanAddress(C^.Peer^.address.host, Perm);
2272 g_Net_Host_Kick(C^.ID, KickReason);
2273 g_Console_Add(Format(_lc[I_PLAYER_BAN], [Name]));
2274 MH_SEND_GameEvent(NET_EV_PLAYER_BAN, 0, Name);
2275 g_Net_Slist_ServerPlayerLeaves();
2276 g_Net_SaveBanList();
2277 end;
2279 procedure g_Net_Host_Ban(ID: Integer; Perm: Boolean);
2280 begin
2281 if (ID < 0) or (ID > High(NetClients)) then
2282 exit;
2283 g_Net_Host_Ban(@NetClients[ID], Perm);
2284 end;
2286 procedure g_Net_Penalize(C: pTNetClient; Reason: string);
2288 s: string;
2289 begin
2290 e_LogWritefln('NET: client #%u (cid #%u) triggered a penalty (%d/%d): %s',
2291 [C^.ID, C^.Player, C^.Crimes + 1, NetAutoBanLimit, Reason]);
2293 if (NetAutoBanLimit <= 0) then Exit;
2295 if (C^.Crimes >= NetAutoBanLimit) then
2296 begin
2297 // we have tried asking nicely before, now it is time to die
2298 e_LogWritefln('NET: client #%u (cid #%u) force kicked',
2299 [C^.ID, C^.Player]);
2300 g_Net_Host_Disconnect_Client(C^.ID, True);
2301 Exit;
2302 end;
2304 Inc(C^.Crimes);
2306 if (NetAutoBanWarn) then
2307 MH_SEND_Chat('You have been warned by the server: ' + Reason, NET_CHAT_SYSTEM, C^.ID);
2309 if (C^.Crimes >= NetAutoBanLimit) then
2310 begin
2312 end;
2313 end;
2315 procedure g_Net_DumpStart();
2316 begin
2317 if NetMode = NET_SERVER
2318 then NetDumpFile := e_CreateResource(LogDirs, NETDUMP_FILENAME + '_server')
2319 else NetDumpFile := e_CreateResource(LogDirs, NETDUMP_FILENAME + '_client');
2320 end;
2322 procedure g_Net_DumpSendBuffer();
2323 begin
2324 NetDumpFile.WriteDWordLE(gTime);
2325 NetDumpFile.WriteDWordLE(NetOut.CurSize);
2326 NetDumpFile.WriteByte(1);
2327 NetDumpFile.WriteBuffer(NetOut.Data^, NetOut.CurSize);
2328 end;
2330 procedure g_Net_DumpRecvBuffer(Buf: penet_uint8; Len: LongWord);
2331 begin
2332 if (Buf = nil) or (Len = 0) then Exit;
2333 NetDumpFile.WriteDWordLE(gTime);
2334 NetDumpFile.WriteDWordLE(Len);
2335 NetDumpFile.WriteByte(0);
2336 NetDumpFile.WriteBuffer(Buf^, Len);
2337 end;
2339 procedure g_Net_DumpEnd();
2340 begin
2341 FreeAndNil(NetDumpFile);
2342 end;
2344 // FIXME: Are calls to conwritefln() thread-safe?
2345 function g_Net_ForwardPorts(ForwardPongPort: Boolean): Boolean;
2346 {$IFDEF USE_MINIUPNPC}
2348 DevList: PUPNPDev;
2349 Urls: TUPNPUrls;
2350 Data: TIGDDatas;
2351 LanAddr: array [0..255] of Char;
2352 StrPort: AnsiString;
2353 Err, I: Integer;
2354 begin
2355 Result := False;
2356 if NetHost = nil then
2357 Exit;
2359 if NetForwardedPort = NetHost.address.port then
2360 Exit(True);
2362 NetPingPortForwarded := False;
2363 NetForwardedPort := 0;
2365 DevList := upnpDiscover(1000, nil, nil, 0, 0, 2, Addr(Err));
2366 if DevList = nil then
2367 begin
2368 conwritefln('port forwarding failed: upnpDiscover() failed: %d', [Err]);
2369 Exit;
2370 end;
2372 I := UPNP_GetValidIGD(DevList, @Urls, @Data, Addr(LanAddr[0]), 256);
2373 if I = 0 then
2374 begin
2375 conwriteln('port forwarding failed: could not find an IGD device on this LAN');
2376 FreeUPNPDevList(DevList);
2377 FreeUPNPUrls(@Urls);
2378 Exit;
2379 end;
2381 StrPort := IntToStr(NetHost.address.port);
2382 I := UPNP_AddPortMapping(
2383 Urls.controlURL, Addr(data.first.servicetype[1]),
2384 PChar(StrPort), PChar(StrPort), Addr(LanAddr[0]), PChar('D2DF'),
2385 PChar('UDP'), nil, PChar('0')
2388 if I <> 0 then
2389 begin
2390 conwritefln('forwarding port %d failed: error %d', [NetHost.address.port, I]);
2391 FreeUPNPDevList(DevList);
2392 FreeUPNPUrls(@Urls);
2393 Exit;
2394 end;
2396 if ForwardPongPort then
2397 begin
2398 StrPort := IntToStr(NET_PING_PORT);
2399 I := UPNP_AddPortMapping(
2400 Urls.controlURL, Addr(data.first.servicetype[1]),
2401 PChar(StrPort), PChar(StrPort), Addr(LanAddr[0]), PChar('D2DF'),
2402 PChar('UDP'), nil, PChar('0')
2405 NetPingPortForwarded := I = 0;
2406 if NetPingPortForwarded
2407 then conwritefln('forwarded ping port %d successfully', [NET_PING_PORT])
2408 else conwritefln('forwarding ping port %d failed: error %d', [NET_PING_PORT, I]);
2409 end;
2411 conwritefln('forwarded port %d successfully', [NetHost.address.port]);
2412 NetIGDControl := AnsiString(Urls.controlURL);
2413 NetIGDService := data.first.servicetype;
2414 NetForwardedPort := NetHost.address.port;
2416 FreeUPNPDevList(DevList);
2417 FreeUPNPUrls(@Urls);
2418 Result := True;
2419 end;
2420 {$ELSE}
2421 begin
2422 Result := False;
2423 end;
2424 {$ENDIF}
2426 procedure g_Net_UnforwardPorts();
2427 {$IFDEF USE_MINIUPNPC}
2429 I: Integer;
2430 StrPort: AnsiString;
2431 begin
2432 if NetForwardedPort = 0 then
2433 Exit;
2435 conwriteln('unforwarding ports...');
2437 StrPort := IntToStr(NetForwardedPort);
2438 I := UPNP_DeletePortMapping(PChar(NetIGDControl), PChar(NetIGDService), PChar(StrPort),
2439 PChar('UDP'), nil);
2440 conwritefln(' port %d: %d', [NetForwardedPort, I]);
2442 if NetPingPortForwarded then
2443 begin
2444 NetPingPortForwarded := False;
2445 StrPort := IntToStr(NET_PING_PORT);
2446 I := UPNP_DeletePortMapping(PChar(NetIGDControl), PChar(NetIGDService), PChar(StrPort),
2447 PChar('UDP'), nil);
2448 conwritefln(' ping port %d: %d', [NET_PING_PORT, I]);
2449 end;
2451 NetForwardedPort := 0;
2452 end;
2453 {$ELSE}
2454 begin
2455 end;
2456 {$ENDIF}
2458 procedure NetServerCVars(P: SSArray);
2460 cmd, s: string;
2461 a, b: Integer;
2462 begin
2463 cmd := LowerCase(P[0]);
2464 case cmd of
2465 'sv_name':
2466 begin
2467 if (Length(P) > 1) and (Length(P[1]) > 0) then
2468 begin
2469 NetServerName := P[1];
2470 if Length(NetServerName) > 64 then
2471 SetLength(NetServerName, 64);
2472 g_Net_Slist_ServerRenamed();
2473 end;
2474 g_Console_Add(cmd + ' "' + NetServerName + '"');
2475 end;
2476 'sv_passwd':
2477 begin
2478 if (Length(P) > 1) and (Length(P[1]) > 0) then
2479 begin
2480 NetPassword := P[1];
2481 if Length(NetPassword) > 24 then
2482 SetLength(NetPassword, 24);
2483 g_Net_Slist_ServerRenamed();
2484 end;
2485 g_Console_Add(cmd + ' "' + AnsiLowerCase(NetPassword) + '"');
2486 end;
2487 'sv_maxplrs':
2488 begin
2489 if (Length(P) > 1) then
2490 begin
2491 NetMaxClients := nclamp(StrToIntDef(P[1], NetMaxClients), 1, NET_MAXCLIENTS);
2492 if g_Game_IsServer and g_Game_IsNet then
2493 begin
2494 b := 0;
2495 for a := 0 to High(NetClients) do
2496 begin
2497 if NetClients[a].Used then
2498 begin
2499 Inc(b);
2500 if b > NetMaxClients then
2501 begin
2502 s := g_Player_Get(NetClients[a].Player).Name;
2503 g_Net_Host_Kick(NetClients[a].ID, NET_DISC_FULL);
2504 g_Console_Add(Format(_lc[I_PLAYER_KICK], [s]));
2505 MH_SEND_GameEvent(NET_EV_PLAYER_KICK, 0, s);
2506 end;
2507 end;
2508 end;
2509 g_Net_Slist_ServerRenamed();
2510 end;
2511 end;
2512 g_Console_Add(cmd + ' ' + IntToStr(NetMaxClients));
2513 end;
2514 'sv_public':
2515 begin
2516 if (Length(P) > 1) then
2517 begin
2518 NetUseMaster := StrToIntDef(P[1], Byte(NetUseMaster)) <> 0;
2519 if NetUseMaster then g_Net_Slist_Public() else g_Net_Slist_Private();
2520 end;
2521 g_Console_Add(cmd + ' ' + IntToStr(Byte(NetUseMaster)));
2522 end;
2523 'sv_port':
2524 begin
2525 if (Length(P) > 1) then
2526 begin
2527 if not g_Game_IsNet then
2528 NetPort := nclamp(StrToIntDef(P[1], NetPort), 0, $FFFF)
2529 else
2530 g_Console_Add(_lc[I_MSG_NOT_NETGAME]);
2531 end;
2532 g_Console_Add(cmd + ' ' + IntToStr(Ord(NetPort)));
2533 end;
2534 end;
2535 end;
2537 initialization
2538 conRegVar('cl_downloadtimeout', @g_Net_DownloadTimeout, 0.0, 1000000.0, '', 'timeout in seconds, 0 to disable it');
2539 conRegVar('cl_predictself', @NetPredictSelf, '', 'predict local player');
2540 conRegVar('cl_forceplayerupdate', @NetForcePlayerUpdate, '', 'update net players on NET_MSG_PLRPOS');
2541 conRegVar('cl_interp', @NetInterpLevel, '', 'net player interpolation steps');
2542 conRegVar('cl_last_ip', @NetClientIP, '', 'address of the last server you have connected to');
2543 conRegVar('cl_last_port', @NetClientPort, '', 'port of the last server you have connected to');
2544 conRegVar('cl_deafen', @NetDeafLevel, '', 'filter server messages (0-3)');
2546 conRegVar('sv_forwardports', @NetForwardPorts, '', 'forward server port using miniupnpc (requires server restart)');
2547 conRegVar('sv_rcon', @NetAllowRCON, '', 'enable remote console');
2548 conRegVar('sv_rcon_password', @NetRCONPassword, '', 'remote console password');
2549 conRegVar('sv_update_interval', @NetUpdateRate, '', 'unreliable update interval');
2550 conRegVar('sv_reliable_interval', @NetRelupdRate, '', 'reliable update interval');
2551 conRegVar('sv_master_interval', @NetMasterRate, '', 'master server update interval');
2553 conRegVar('sv_autoban_threshold', @NetAutoBanLimit, '', 'max crimes before autoban (0 = no autoban)');
2554 conRegVar('sv_autoban_permanent', @NetAutoBanPerm, '', 'whether autobans are permanent');
2555 conRegVar('sv_autoban_warn', @NetAutoBanWarn, '', 'send warnings to the client when he triggers penalties');
2556 conRegVar('sv_autoban_packet_timeout', @NetAutoBanForTimeout, '', 'autoban for packet timeouts');
2558 conRegVar('sv_auth_timeout', @NetAuthTimeout, '', 'number of msec in which connecting clients must complete auth (0 = unlimited)');
2559 conRegVar('sv_packet_timeout', @NetPacketTimeout, '', 'number of msec the client must idle to be kicked (0 = unlimited)');
2561 conRegVar('net_master_list', @NetMasterList, '', 'list of master servers');
2563 SetLength(NetClients, 0);
2564 g_Net_DownloadTimeout := 60;
2565 NetIn.Alloc(NET_BUFSIZE);
2566 NetOut.Alloc(NET_BUFSIZE);
2567 NetBuf[NET_UNRELIABLE].Alloc(NET_BUFSIZE*2);
2568 NetBuf[NET_RELIABLE].Alloc(NET_BUFSIZE*2);
2569 trans_omsg.Alloc(NET_BUFSIZE);
2571 finalization
2572 NetIn.Free();
2573 NetOut.Free();
2574 NetBuf[NET_UNRELIABLE].Free();
2575 NetBuf[NET_RELIABLE].Free();
2576 trans_omsg.Free();
2578 end.