DEADSOFTWARE

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