DEADSOFTWARE

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