DEADSOFTWARE

fix (?) survival spectator; add space after comma in masterlist
[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 = 185;
26 NET_MAXCLIENTS = 24;
27 NET_CHANS = 12;
29 NET_CHAN_SERVICE = 0;
30 NET_CHAN_IMPORTANT = 1;
31 NET_CHAN_GAME = 2;
32 NET_CHAN_PLAYER = 3;
33 NET_CHAN_PLAYERPOS = 4;
34 NET_CHAN_MONSTER = 5;
35 NET_CHAN_MONSTERPOS = 6;
36 NET_CHAN_LARGEDATA = 7;
37 NET_CHAN_CHAT = 8;
38 NET_CHAN_DOWNLOAD = 9;
39 NET_CHAN_SHOTS = 10;
40 NET_CHAN_DOWNLOAD_EX = 11;
42 NET_NONE = 0;
43 NET_SERVER = 1;
44 NET_CLIENT = 2;
46 NET_BUFSIZE = $FFFF;
47 NET_PING_PORT = $DF2D;
49 NET_EVERYONE = -1;
51 NET_UNRELIABLE = 0;
52 NET_RELIABLE = 1;
54 NET_DISC_NONE: enet_uint32 = 0;
55 NET_DISC_PROTOCOL: enet_uint32 = 1;
56 NET_DISC_VERSION: enet_uint32 = 2;
57 NET_DISC_FULL: enet_uint32 = 3;
58 NET_DISC_KICK: enet_uint32 = 4;
59 NET_DISC_DOWN: enet_uint32 = 5;
60 NET_DISC_PASSWORD: enet_uint32 = 6;
61 NET_DISC_TEMPBAN: enet_uint32 = 7;
62 NET_DISC_BAN: enet_uint32 = 8;
63 NET_DISC_MAX: enet_uint32 = 8;
64 NET_DISC_FILE_TIMEOUT: enet_uint32 = 13;
66 NET_STATE_NONE = 0;
67 NET_STATE_AUTH = 1;
68 NET_STATE_GAME = 2;
70 NET_CONNECT_TIMEOUT = 1000 * 10;
72 BANLIST_FILENAME = 'banlist.txt';
73 NETDUMP_FILENAME = 'netdump';
75 {$IF DEFINED(FREEBSD) OR DEFINED(DARWIN)}
76 NilThreadId = nil;
77 {$ELSE}
78 NilThreadId = 0;
79 {$ENDIF}
81 type
82 TNetMapResourceInfo = record
83 wadName: AnsiString; // wad file name, without a path
84 size: Integer; // wad file size (-1: size and hash are not known)
85 hash: TMD5Digest; // wad hash
86 end;
88 TNetMapResourceInfoArray = array of TNetMapResourceInfo;
90 TNetFileTransfer = record
91 diskName: string;
92 hash: TMD5Digest;
93 stream: TStream;
94 size: Integer; // file size in bytes
95 chunkSize: Integer;
96 lastSentChunk: Integer;
97 lastAckChunk: Integer;
98 lastAckTime: Int64; // msecs; if not "in progress", we're waiting for the first ack
99 inProgress: Boolean;
100 diskBuffer: PChar; // of `chunkSize` bytes
101 resumed: Boolean;
102 end;
104 TNetClient = record
105 ID: Byte;
106 Used: Boolean;
107 State: Byte;
108 Peer: pENetPeer;
109 Player: Word;
110 RequestedFullUpdate: Boolean;
111 WaitForFirstSpawn: Boolean; // set to `true` in server, used to spawn a player on first full state request
112 RCONAuth: Boolean;
113 Voted: Boolean;
114 Transfer: TNetFileTransfer; // only one transfer may be active
115 NetOut: array [0..1] of TMsg;
116 end;
117 TBanRecord = record
118 IP: LongWord;
119 Perm: Boolean;
120 end;
121 pTNetClient = ^TNetClient;
123 AByte = array of Byte;
125 var
126 NetInitDone: Boolean = False;
127 NetMode: Byte = NET_NONE;
128 NetDump: Boolean = False;
130 NetServerName: string = 'Unnamed Server';
131 NetPassword: string = '';
132 NetPort: Word = 25666;
134 NetAllowRCON: Boolean = False;
135 NetRCONPassword: string = '';
137 NetTimeToUpdate: Cardinal = 0;
138 NetTimeToReliable: Cardinal = 0;
139 NetTimeToMaster: Cardinal = 0;
141 NetHost: pENetHost = nil;
142 NetPeer: pENetPeer = nil;
143 NetEvent: ENetEvent;
144 NetAddr: ENetAddress;
146 NetPongAddr: ENetAddress;
147 NetPongSock: ENetSocket = ENET_SOCKET_NULL;
149 NetUseMaster: Boolean = True;
150 NetMasterList: string = 'mpms.doom2d.org:25665, deadsoftware.ru:25665';
152 NetClientIP: string = '127.0.0.1';
153 NetClientPort: Word = 25666;
155 NetIn, NetOut: TMsg;
156 NetBuf: array [0..1] of TMsg;
158 NetClients: array of TNetClient;
159 NetClientCount: Byte = 0;
160 NetMaxClients: Byte = 255;
161 NetBannedHosts: array of TBanRecord;
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 {$IFDEF USE_MINIUPNPC}
182 NetPortForwarded: Word = 0;
183 NetPongForwarded: Boolean = False;
184 NetIGDControl: AnsiString;
185 NetIGDService: TURLStr;
186 {$ENDIF}
188 NetPortThread: TThreadID = NilThreadId;
190 NetDumpFile: TStream;
192 g_Res_received_map_start: Integer = 0; // set if we received "map change" event
195 function g_Net_Init(): Boolean;
196 procedure g_Net_Cleanup();
197 procedure g_Net_Free();
198 procedure g_Net_Flush();
200 function g_Net_Host(IPAddr: LongWord; Port: enet_uint16; MaxClients: Cardinal = 16): Boolean;
201 procedure g_Net_Host_Die();
202 procedure g_Net_Host_Send(ID: Integer; Reliable: Boolean; Chan: Byte = NET_CHAN_GAME);
203 function g_Net_Host_Update(): enet_size_t;
205 function g_Net_Connect(IP: string; Port: enet_uint16): Boolean;
206 procedure g_Net_Disconnect(Forced: Boolean = False);
207 procedure g_Net_Client_Send(Reliable: Boolean; Chan: Byte = NET_CHAN_GAME);
208 function g_Net_Client_Update(): enet_size_t;
209 function g_Net_Client_UpdateWhileLoading(): enet_size_t;
211 function g_Net_Client_ByName(Name: string): pTNetClient;
212 function g_Net_Client_ByPlayer(PID: Word): pTNetClient;
213 function g_Net_ClientName_ByID(ID: Integer): string;
215 procedure g_Net_SendData(Data: AByte; peer: pENetPeer; Reliable: Boolean; Chan: Byte = NET_CHAN_DOWNLOAD);
216 //function g_Net_Wait_Event(msgId: Word): TMemoryStream;
217 //function g_Net_Wait_FileInfo (var tf: TNetFileTransfer; asMap: Boolean; out resList: TStringList): Integer;
219 function IpToStr(IP: LongWord): string;
220 function StrToIp(IPstr: string; var IP: LongWord): Boolean;
222 function g_Net_IsHostBanned(IP: LongWord; Perm: Boolean = False): Boolean;
223 procedure g_Net_BanHost(IP: LongWord; Perm: Boolean = True); overload;
224 procedure g_Net_BanHost(IP: string; Perm: Boolean = True); overload;
225 function g_Net_UnbanHost(IP: string): Boolean; overload;
226 function g_Net_UnbanHost(IP: LongWord): Boolean; overload;
227 procedure g_Net_UnbanNonPermHosts();
228 procedure g_Net_SaveBanList();
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 begin
281 if enet_init_success then raise Exception.Create('wuta?!');
282 enet_init_success := (enet_initialize() = 0);
283 end;
285 procedure g_Net_DeinitLowLevel ();
286 begin
287 if enet_init_success then
288 begin
289 enet_deinitialize();
290 enet_init_success := false;
291 end;
292 end;
295 //**************************************************************************
296 //
297 // SERVICE FUNCTIONS
298 //
299 //**************************************************************************
301 procedure clearNetClientTransfers (var nc: TNetClient);
302 begin
303 nc.Transfer.stream.Free;
304 nc.Transfer.diskName := ''; // just in case
305 if (nc.Transfer.diskBuffer <> nil) then FreeMem(nc.Transfer.diskBuffer);
306 nc.Transfer.stream := nil;
307 nc.Transfer.diskBuffer := nil;
308 end;
311 procedure clearNetClient (var nc: TNetClient);
312 begin
313 clearNetClientTransfers(nc);
314 end;
317 procedure clearNetClients (clearArray: Boolean);
318 var
319 f: Integer;
320 begin
321 for f := Low(NetClients) to High(NetClients) do clearNetClient(NetClients[f]);
322 if (clearArray) then SetLength(NetClients, 0);
323 end;
326 function g_Net_UserRequestExit (): Boolean;
327 begin
328 Result := {e_KeyPressed(IK_SPACE) or}
329 e_KeyPressed(IK_ESCAPE) or
330 e_KeyPressed(VK_ESCAPE) or
331 e_KeyPressed(JOY0_JUMP) or
332 e_KeyPressed(JOY1_JUMP) or
333 e_KeyPressed(JOY2_JUMP) or
334 e_KeyPressed(JOY3_JUMP)
335 end;
338 //**************************************************************************
339 //
340 // file transfer declaraions and host packet processor
341 //
342 //**************************************************************************
344 const
345 // server packet type
346 NTF_SERVER_DONE = 10; // done with this file
347 NTF_SERVER_FILE_INFO = 11; // sent after client request
348 NTF_SERVER_CHUNK = 12; // next chunk; chunk number follows
349 NTF_SERVER_ABORT = 13; // server abort
350 NTF_SERVER_MAP_INFO = 14;
352 // client packet type
353 NTF_CLIENT_MAP_REQUEST = 100; // map file request; also, returns list of additional wads to download
354 NTF_CLIENT_FILE_REQUEST = 101; // resource file request (by index)
355 NTF_CLIENT_ABORT = 102; // do not send requested file, or abort current transfer
356 NTF_CLIENT_START = 103; // start transfer; client may resume download by sending non-zero starting chunk
357 NTF_CLIENT_ACK = 104; // chunk ack; chunk number follows
360 // disconnect client due to some file transfer error
361 procedure killClientByFT (var nc: TNetClient);
362 begin
363 e_LogWritefln('disconnected client #%d due to file transfer error', [nc.ID], TMsgType.Warning);
364 enet_peer_disconnect(nc.Peer, NET_DISC_FILE_TIMEOUT);
365 clearNetClientTransfers(nc);
366 g_Net_Slist_ServerPlayerLeaves();
367 end;
370 // send file transfer message from server to client
371 function ftransSendServerMsg (var nc: TNetClient; var m: TMsg): Boolean;
372 var
373 pkt: PENetPacket;
374 begin
375 result := false;
376 if (m.CurSize < 1) then exit;
377 pkt := enet_packet_create(m.Data, m.CurSize, ENET_PACKET_FLAG_RELIABLE);
378 if not Assigned(pkt) then begin killClientByFT(nc); exit; end;
379 if (enet_peer_send(nc.Peer, NET_CHAN_DOWNLOAD_EX, pkt) <> 0) then begin killClientByFT(nc); exit; end;
380 result := true;
381 end;
384 // send file transfer message from client to server
385 function ftransSendClientMsg (var m: TMsg): Boolean;
386 var
387 pkt: PENetPacket;
388 begin
389 result := false;
390 if (m.CurSize < 1) then exit;
391 pkt := enet_packet_create(m.Data, m.CurSize, ENET_PACKET_FLAG_RELIABLE);
392 if not Assigned(pkt) then exit;
393 if (enet_peer_send(NetPeer, NET_CHAN_DOWNLOAD_EX, pkt) <> 0) then exit;
394 result := true;
395 end;
398 // file chunk sender
399 procedure ProcessChunkSend (var nc: TNetClient);
400 var
401 tf: ^TNetFileTransfer;
402 ct: Int64;
403 chunks: Integer;
404 rd: Integer;
405 begin
406 tf := @nc.Transfer;
407 if (tf.stream = nil) then exit;
408 ct := GetTimerMS();
409 // arbitrary timeout number
410 if (ct-tf.lastAckTime >= 5000) then
411 begin
412 killClientByFT(nc);
413 exit;
414 end;
415 // check if we need to send something
416 if (not tf.inProgress) then exit; // waiting for the initial ack
417 // ok, we're sending chunks
418 if (tf.lastAckChunk <> tf.lastSentChunk) then exit;
419 Inc(tf.lastSentChunk);
420 // do it one chunk at a time; client ack will advance our chunk counter
421 chunks := (tf.size+tf.chunkSize-1) div tf.chunkSize;
423 if (tf.lastSentChunk > chunks) then
424 begin
425 killClientByFT(nc);
426 exit;
427 end;
429 trans_omsg.Clear();
430 if (tf.lastSentChunk = chunks) then
431 begin
432 // we're done with this file
433 e_LogWritefln('download: client #%d, DONE sending chunks #%d/#%d', [nc.ID, tf.lastSentChunk, chunks]);
434 trans_omsg.Write(Byte(NTF_SERVER_DONE));
435 clearNetClientTransfers(nc);
436 end
437 else
438 begin
439 // packet type
440 trans_omsg.Write(Byte(NTF_SERVER_CHUNK));
441 trans_omsg.Write(LongInt(tf.lastSentChunk));
442 // read chunk
443 rd := tf.size-(tf.lastSentChunk*tf.chunkSize);
444 if (rd > tf.chunkSize) then rd := tf.chunkSize;
445 trans_omsg.Write(LongInt(rd));
446 //e_LogWritefln('download: client #%d, sending chunk #%d/#%d (%d bytes)', [nc.ID, tf.lastSentChunk, chunks, rd]);
447 //FIXME: check for errors here
448 try
449 tf.stream.Seek(tf.lastSentChunk*tf.chunkSize, soFromBeginning);
450 tf.stream.ReadBuffer(tf.diskBuffer^, rd);
451 trans_omsg.WriteData(tf.diskBuffer, rd);
452 except // sorry
453 killClientByFT(nc);
454 exit;
455 end;
456 end;
457 // send packet
458 ftransSendServerMsg(nc, trans_omsg);
459 end;
462 // server file transfer packet processor
463 // received packet is in `NetEvent`
464 procedure ProcessDownloadExPacket ();
465 var
466 f: Integer;
467 nc: ^TNetClient;
468 nid: Integer = -1;
469 msg: TMsg;
470 cmd: Byte;
471 tf: ^TNetFileTransfer;
472 fname: string;
473 chunk: Integer;
474 ridx: Integer;
475 dfn: AnsiString;
476 md5: TMD5Digest;
477 //st: TStream;
478 size: LongInt;
479 fi: TDiskFileInfo;
480 begin
481 // find client index by peer
482 for f := Low(NetClients) to High(NetClients) do
483 begin
484 if (not NetClients[f].Used) then continue;
485 if (NetClients[f].Peer = NetEvent.peer) then
486 begin
487 nid := f;
488 break;
489 end;
490 end;
491 //e_LogWritefln('RECEIVE: dlpacket; client=%d (datalen=%u)', [nid, NetEvent.packet^.dataLength]);
493 if (nid < 0) then exit; // wtf?!
494 nc := @NetClients[nid];
496 if (NetEvent.packet^.dataLength = 0) then
497 begin
498 killClientByFT(nc^);
499 exit;
500 end;
502 tf := @NetClients[nid].Transfer;
503 tf.lastAckTime := GetTimerMS();
505 cmd := Byte(NetEvent.packet^.data^);
506 //e_LogWritefln('RECEIVE: nid=%d; cmd=%u', [nid, cmd]);
507 case cmd of
508 NTF_CLIENT_FILE_REQUEST: // file request
509 begin
510 if (tf.stream <> nil) then
511 begin
512 killClientByFT(nc^);
513 exit;
514 end;
515 if (NetEvent.packet^.dataLength < 2) then
516 begin
517 killClientByFT(nc^);
518 exit;
519 end;
520 // new transfer request; build packet
521 if not msg.Init(NetEvent.packet^.data+1, NetEvent.packet^.dataLength-1, True) then
522 begin
523 killClientByFT(nc^);
524 exit;
525 end;
526 // get resource index
527 ridx := msg.ReadLongInt();
528 if (ridx < -1) or (ridx >= length(gExternalResources)) then
529 begin
530 e_LogWritefln('Invalid resource index %d', [ridx], TMsgType.Warning);
531 killClientByFT(nc^);
532 exit;
533 end;
534 if (ridx < 0) then fname := gGameSettings.WAD else fname := gExternalResources[ridx].diskName;
535 if (length(fname) = 0) then
536 begin
537 e_WriteLog('Invalid filename: '+fname, TMsgType.Warning);
538 killClientByFT(nc^);
539 exit;
540 end;
541 tf.diskName := findDiskWad(fname);
542 if (length(tf.diskName) = 0) then
543 begin
544 e_LogWritefln('NETWORK: file "%s" not found!', [fname], TMsgType.Fatal);
545 killClientByFT(nc^);
546 exit;
547 end;
548 // calculate hash
549 //tf.hash := MD5File(tf.diskName);
550 if (ridx < 0) then tf.hash := gWADHash else tf.hash := gExternalResources[ridx].hash;
551 // create file stream
552 tf.diskName := findDiskWad(fname);
553 try
554 tf.stream := openDiskFileRO(tf.diskName);
555 except
556 tf.stream := nil;
557 end;
558 if (tf.stream = nil) then
559 begin
560 e_WriteLog(Format('NETWORK: file "%s" not found!', [fname]), TMsgType.Fatal);
561 killClientByFT(nc^);
562 exit;
563 end;
564 e_LogWritefln('client #%d requested resource #%d (file is `%s` : `%s`)', [nc.ID, ridx, fname, tf.diskName]);
565 tf.size := tf.stream.size;
566 tf.chunkSize := FILE_CHUNK_SIZE; // arbitrary
567 tf.lastSentChunk := -1;
568 tf.lastAckChunk := -1;
569 tf.lastAckTime := GetTimerMS();
570 tf.inProgress := False; // waiting for the first ACK or for the cancel
571 GetMem(tf.diskBuffer, tf.chunkSize);
572 // sent file info message
573 trans_omsg.Clear();
574 trans_omsg.Write(Byte(NTF_SERVER_FILE_INFO));
575 trans_omsg.Write(tf.hash);
576 trans_omsg.Write(tf.size);
577 trans_omsg.Write(tf.chunkSize);
578 trans_omsg.Write(ExtractFileName(fname));
579 if not ftransSendServerMsg(nc^, trans_omsg) then exit;
580 end;
581 NTF_CLIENT_ABORT: // do not send requested file, or abort current transfer
582 begin
583 e_LogWritefln('client #%d aborted file transfer', [nc.ID]);
584 clearNetClientTransfers(nc^);
585 end;
586 NTF_CLIENT_START: // start transfer; client may resume download by sending non-zero starting chunk
587 begin
588 if not Assigned(tf.stream) then
589 begin
590 killClientByFT(nc^);
591 exit;
592 end;
593 if (tf.lastSentChunk <> -1) or (tf.lastAckChunk <> -1) or (tf.inProgress) then
594 begin
595 // double ack, get lost
596 killClientByFT(nc^);
597 exit;
598 end;
599 if (NetEvent.packet^.dataLength < 2) then
600 begin
601 killClientByFT(nc^);
602 exit;
603 end;
604 // build packet
605 if not msg.Init(NetEvent.packet^.data+1, NetEvent.packet^.dataLength-1, True) then
606 begin
607 killClientByFT(nc^);
608 exit;
609 end;
610 chunk := msg.ReadLongInt();
611 if (chunk < 0) or (chunk > (tf.size+tf.chunkSize-1) div tf.chunkSize) then
612 begin
613 killClientByFT(nc^);
614 exit;
615 end;
616 e_LogWritefln('client #%d started file transfer from chunk %d', [nc.ID, chunk]);
617 // start sending chunks
618 tf.inProgress := True;
619 tf.lastSentChunk := chunk-1;
620 tf.lastAckChunk := chunk-1;
621 ProcessChunkSend(nc^);
622 end;
623 NTF_CLIENT_ACK: // chunk ack; chunk number follows
624 begin
625 if not Assigned(tf.stream) then
626 begin
627 killClientByFT(nc^);
628 exit;
629 end;
630 if (tf.lastSentChunk < 0) or (not tf.inProgress) then
631 begin
632 // double ack, get lost
633 killClientByFT(nc^);
634 exit;
635 end;
636 if (NetEvent.packet^.dataLength < 2) then
637 begin
638 killClientByFT(nc^);
639 exit;
640 end;
641 // build packet
642 if not msg.Init(NetEvent.packet^.data+1, NetEvent.packet^.dataLength-1, True) then
643 begin
644 killClientByFT(nc^);
645 exit;
646 end;
647 chunk := msg.ReadLongInt();
648 if (chunk < 0) or (chunk > (tf.size+tf.chunkSize-1) div tf.chunkSize) then
649 begin
650 killClientByFT(nc^);
651 exit;
652 end;
653 // do it this way, so client may seek, or request retransfers for some reason
654 tf.lastAckChunk := chunk;
655 tf.lastSentChunk := chunk;
656 //e_LogWritefln('client #%d acked file transfer chunk %d', [nc.ID, chunk]);
657 ProcessChunkSend(nc^);
658 end;
659 NTF_CLIENT_MAP_REQUEST:
660 begin
661 e_LogWritefln('client #%d requested map info', [nc.ID]);
662 trans_omsg.Clear();
663 dfn := findDiskWad(gGameSettings.WAD);
664 if (dfn = '') then dfn := '!wad_not_found!.wad'; //FIXME
665 //md5 := MD5File(dfn);
666 md5 := gWADHash;
667 if (not GetDiskFileInfo(dfn, fi)) then
668 begin
669 e_LogWritefln('client #%d requested map info, but i cannot get file info', [nc.ID]);
670 killClientByFT(nc^);
671 exit;
672 end;
673 size := fi.size;
675 st := openDiskFileRO(dfn);
676 if not assigned(st) then exit; //wtf?!
677 size := st.size;
678 st.Free;
680 // packet type
681 trans_omsg.Write(Byte(NTF_SERVER_MAP_INFO));
682 // map wad name
683 trans_omsg.Write(ExtractFileName(gGameSettings.WAD));
684 // map wad md5
685 trans_omsg.Write(md5);
686 // map wad size
687 trans_omsg.Write(size);
688 // number of external resources for map
689 trans_omsg.Write(LongInt(length(gExternalResources)));
690 // external resource names
691 for f := 0 to High(gExternalResources) do
692 begin
693 // old style packet
694 //trans_omsg.Write(ExtractFileName(gExternalResources[f])); // GameDir+'/wads/'+ResList.Strings[i]
695 // new style packet
696 trans_omsg.Write('!');
697 trans_omsg.Write(LongInt(gExternalResources[f].size));
698 trans_omsg.Write(gExternalResources[f].hash);
699 trans_omsg.Write(ExtractFileName(gExternalResources[f].diskName));
700 end;
701 // send packet
702 if not ftransSendServerMsg(nc^, trans_omsg) then exit;
703 end;
704 else
705 begin
706 killClientByFT(nc^);
707 exit;
708 end;
709 end;
710 end;
713 //**************************************************************************
714 //
715 // file transfer crap (both client and server)
716 //
717 //**************************************************************************
719 function getNewTimeoutEnd (): Int64;
720 begin
721 result := GetTimerMS();
722 if (g_Net_DownloadTimeout <= 0) then
723 begin
724 result := result+1000*60*3; // 3 minutes
725 end
726 else
727 begin
728 result := result+trunc(g_Net_DownloadTimeout*1000);
729 end;
730 end;
733 // send map request to server, and wait for "map info" server reply
734 //
735 // returns `false` on error or user abort
736 // fills:
737 // diskName: map wad file name (without a path)
738 // hash: map wad hash
739 // size: map wad size
740 // chunkSize: set too
741 // resList: list of resource wads
742 // returns:
743 // <0 on error
744 // 0 on success
745 // 1 on user abort
746 // 2 on server abort
747 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
748 function g_Net_Wait_MapInfo (var tf: TNetFileTransfer; var resList: TNetMapResourceInfoArray): Integer;
749 var
750 ev: ENetEvent;
751 rMsgId: Byte;
752 Ptr: Pointer;
753 msg: TMsg;
754 freePacket: Boolean = false;
755 ct, ett: Int64;
756 status: cint;
757 s: AnsiString;
758 rc, f: LongInt;
759 ri: ^TNetMapResourceInfo;
760 begin
761 SetLength(resList, 0);
763 // send request
764 trans_omsg.Clear();
765 trans_omsg.Write(Byte(NTF_CLIENT_MAP_REQUEST));
766 if not ftransSendClientMsg(trans_omsg) then begin result := -1; exit; end;
768 FillChar(ev, SizeOf(ev), 0);
769 Result := -1;
770 try
771 ett := getNewTimeoutEnd();
772 repeat
773 status := enet_host_service(NetHost, @ev, 300);
775 if (status < 0) then
776 begin
777 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True);
778 Result := -1;
779 exit;
780 end;
782 if (status <= 0) then
783 begin
784 // check for timeout
785 ct := GetTimerMS();
786 if (ct >= ett) then
787 begin
788 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' timeout reached', True);
789 Result := -1;
790 exit;
791 end;
792 end
793 else
794 begin
795 // some event
796 case ev.kind of
797 ENET_EVENT_TYPE_RECEIVE:
798 begin
799 freePacket := true;
800 if (ev.channelID <> NET_CHAN_DOWNLOAD_EX) then
801 begin
802 //e_LogWritefln('g_Net_Wait_MapInfo: skip message from non-transfer channel', []);
803 freePacket := false;
804 g_Net_Client_HandlePacket(ev.packet, g_Net_ClientLightMsgHandler);
805 if (g_Res_received_map_start < 0) then begin result := -666; exit; end;
806 end
807 else
808 begin
809 ett := getNewTimeoutEnd();
810 if (ev.packet.dataLength < 1) then
811 begin
812 e_LogWritefln('g_Net_Wait_MapInfo: invalid server packet (no data)', []);
813 Result := -1;
814 exit;
815 end;
816 Ptr := ev.packet^.data;
817 rMsgId := Byte(Ptr^);
818 e_LogWritefln('g_Net_Wait_MapInfo: got message %u from server (dataLength=%u)', [rMsgId, ev.packet^.dataLength]);
819 if (rMsgId = NTF_SERVER_FILE_INFO) then
820 begin
821 e_LogWritefln('g_Net_Wait_MapInfo: waiting for map info reply, but got file info reply', []);
822 Result := -1;
823 exit;
824 end
825 else if (rMsgId = NTF_SERVER_ABORT) then
826 begin
827 e_LogWritefln('g_Net_Wait_MapInfo: server aborted transfer', []);
828 Result := 2;
829 exit;
830 end
831 else if (rMsgId = NTF_SERVER_MAP_INFO) then
832 begin
833 e_LogWritefln('g_Net_Wait_MapInfo: creating map info packet...', []);
834 if not msg.Init(ev.packet^.data+1, ev.packet^.dataLength-1, True) then exit;
835 e_LogWritefln('g_Net_Wait_MapInfo: parsing map info packet (rd=%d; max=%d)...', [msg.ReadCount, msg.MaxSize]);
836 SetLength(resList, 0); // just in case
837 // map wad name
838 tf.diskName := msg.ReadString();
839 e_LogWritefln('g_Net_Wait_MapInfo: map wad is `%s`', [tf.diskName]);
840 // map wad md5
841 tf.hash := msg.ReadMD5();
842 // map wad size
843 tf.size := msg.ReadLongInt();
844 e_LogWritefln('g_Net_Wait_MapInfo: map wad size is %d', [tf.size]);
845 // number of external resources for map
846 rc := msg.ReadLongInt();
847 if (rc < 0) or (rc > 1024) then
848 begin
849 e_LogWritefln('g_Net_Wait_Event: invalid number of map external resources (%d)', [rc]);
850 Result := -1;
851 exit;
852 end;
853 e_LogWritefln('g_Net_Wait_MapInfo: map external resource count is %d', [rc]);
854 SetLength(resList, rc);
855 // external resource names
856 for f := 0 to rc-1 do
857 begin
858 ri := @resList[f];
859 s := msg.ReadString();
860 if (length(s) = 0) then begin result := -1; exit; end;
861 if (s = '!') then
862 begin
863 // extended packet
864 ri.size := msg.ReadLongInt();
865 ri.hash := msg.ReadMD5();
866 ri.wadName := ExtractFileName(msg.ReadString());
867 if (length(ri.wadName) = 0) or (ri.size < 0) then begin result := -1; exit; end;
868 end
869 else
870 begin
871 // old-style packet, only name
872 ri.wadName := ExtractFileName(s);
873 if (length(ri.wadName) = 0) then begin result := -1; exit; end;
874 ri.size := -1; // unknown
875 end;
876 end;
877 e_LogWritefln('g_Net_Wait_MapInfo: got map info', []);
878 Result := 0; // success
879 exit;
880 end
881 else
882 begin
883 e_LogWritefln('g_Net_Wait_Event: invalid server packet type', []);
884 Result := -1;
885 exit;
886 end;
887 end;
888 end;
889 ENET_EVENT_TYPE_DISCONNECT:
890 begin
891 if (ev.data <= NET_DISC_MAX) then
892 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' ' + _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + ev.data)], True);
893 Result := -1;
894 exit;
895 end;
896 else
897 begin
898 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' unknown ENet event ' + IntToStr(Ord(ev.kind)), True);
899 result := -1;
900 exit;
901 end;
902 end;
903 if (freePacket) then begin freePacket := false; enet_packet_destroy(ev.packet); end;
904 end;
905 ProcessLoading();
906 if g_Net_UserRequestExit() then
907 begin
908 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' user abort', True);
909 Result := 1;
910 exit;
911 end;
912 until false;
913 finally
914 if (freePacket) then enet_packet_destroy(ev.packet);
915 end;
916 end;
919 // send file request to server, and wait for server reply
920 //
921 // returns `false` on error or user abort
922 // fills:
923 // diskName (actually, base name)
924 // hash
925 // size
926 // chunkSize
927 // returns:
928 // <0 on error
929 // 0 on success
930 // 1 on user abort
931 // 2 on server abort
932 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
933 function g_Net_RequestResFileInfo (resIndex: LongInt; out tf: TNetFileTransfer): Integer;
934 var
935 ev: ENetEvent;
936 rMsgId: Byte;
937 Ptr: Pointer;
938 msg: TMsg;
939 freePacket: Boolean = false;
940 ct, ett: Int64;
941 status: cint;
942 begin
943 // send request
944 trans_omsg.Clear();
945 trans_omsg.Write(Byte(NTF_CLIENT_FILE_REQUEST));
946 trans_omsg.Write(resIndex);
947 if not ftransSendClientMsg(trans_omsg) then begin result := -1; exit; end;
949 FillChar(ev, SizeOf(ev), 0);
950 Result := -1;
951 try
952 ett := getNewTimeoutEnd();
953 repeat
954 status := enet_host_service(NetHost, @ev, 300);
956 if (status < 0) then
957 begin
958 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True);
959 Result := -1;
960 exit;
961 end;
963 if (status <= 0) then
964 begin
965 // check for timeout
966 ct := GetTimerMS();
967 if (ct >= ett) then
968 begin
969 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' timeout reached', True);
970 Result := -1;
971 exit;
972 end;
973 end
974 else
975 begin
976 // some event
977 case ev.kind of
978 ENET_EVENT_TYPE_RECEIVE:
979 begin
980 freePacket := true;
981 if (ev.channelID <> NET_CHAN_DOWNLOAD_EX) then
982 begin
983 //e_LogWriteln('g_Net_Wait_Event: skip message from non-transfer channel');
984 freePacket := false;
985 g_Net_Client_HandlePacket(ev.packet, g_Net_ClientLightMsgHandler);
986 if (g_Res_received_map_start < 0) then begin result := -666; exit; end;
987 end
988 else
989 begin
990 ett := getNewTimeoutEnd();
991 if (ev.packet.dataLength < 1) then
992 begin
993 e_LogWriteln('g_Net_Wait_Event: invalid server packet (no data)');
994 Result := -1;
995 exit;
996 end;
997 Ptr := ev.packet^.data;
998 rMsgId := Byte(Ptr^);
999 e_LogWritefln('received transfer packet with id %d (%u bytes)', [rMsgId, ev.packet^.dataLength]);
1000 if (rMsgId = NTF_SERVER_FILE_INFO) then
1001 begin
1002 if not msg.Init(ev.packet^.data+1, ev.packet^.dataLength-1, True) then exit;
1003 tf.hash := msg.ReadMD5();
1004 tf.size := msg.ReadLongInt();
1005 tf.chunkSize := msg.ReadLongInt();
1006 tf.diskName := ExtractFileName(msg.readString());
1007 if (tf.size < 0) or (tf.chunkSize <> FILE_CHUNK_SIZE) or (length(tf.diskName) = 0) then
1008 begin
1009 e_LogWritefln('g_Net_RequestResFileInfo: invalid file info packet', []);
1010 Result := -1;
1011 exit;
1012 end;
1013 e_LogWritefln('got file info for resource #%d: size=%d; name=%s', [resIndex, tf.size, tf.diskName]);
1014 Result := 0; // success
1015 exit;
1016 end
1017 else if (rMsgId = NTF_SERVER_ABORT) then
1018 begin
1019 e_LogWriteln('g_Net_RequestResFileInfo: server aborted transfer');
1020 Result := 2;
1021 exit;
1022 end
1023 else if (rMsgId = NTF_SERVER_MAP_INFO) then
1024 begin
1025 e_LogWriteln('g_Net_RequestResFileInfo: waiting for map info reply, but got file info reply');
1026 Result := -1;
1027 exit;
1028 end
1029 else
1030 begin
1031 e_LogWriteln('g_Net_RequestResFileInfo: invalid server packet type');
1032 Result := -1;
1033 exit;
1034 end;
1035 end;
1036 end;
1037 ENET_EVENT_TYPE_DISCONNECT:
1038 begin
1039 if (ev.data <= NET_DISC_MAX) then
1040 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' ' + _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + ev.data)], True);
1041 Result := -1;
1042 exit;
1043 end;
1044 else
1045 begin
1046 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' unknown ENet event ' + IntToStr(Ord(ev.kind)), True);
1047 result := -1;
1048 exit;
1049 end;
1050 end;
1051 if (freePacket) then begin freePacket := false; enet_packet_destroy(ev.packet); end;
1052 end;
1053 ProcessLoading();
1054 if g_Net_UserRequestExit() then
1055 begin
1056 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' user abort', True);
1057 Result := 1;
1058 exit;
1059 end;
1060 until false;
1061 finally
1062 if (freePacket) then enet_packet_destroy(ev.packet);
1063 end;
1064 end;
1067 // call this to cancel file transfer requested by `g_Net_RequestResFileInfo()`
1068 function g_Net_AbortResTransfer (var tf: TNetFileTransfer): Boolean;
1069 begin
1070 result := false;
1071 e_LogWritefln('aborting file transfer...', []);
1072 // send request
1073 trans_omsg.Clear();
1074 trans_omsg.Write(Byte(NTF_CLIENT_ABORT));
1075 result := ftransSendClientMsg(trans_omsg);
1076 if result then enet_host_flush(NetHost);
1077 end;
1080 // call this to start file transfer requested by `g_Net_RequestResFileInfo()`
1081 //
1082 // returns `false` on error or user abort
1083 // fills:
1084 // hash
1085 // size
1086 // chunkSize
1087 // returns:
1088 // <0 on error
1089 // 0 on success
1090 // 1 on user abort
1091 // 2 on server abort
1092 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
1093 function g_Net_ReceiveResourceFile (resIndex: LongInt; var tf: TNetFileTransfer; strm: TStream): Integer;
1094 var
1095 ev: ENetEvent;
1096 rMsgId: Byte;
1097 Ptr: Pointer;
1098 msg: TMsg;
1099 freePacket: Boolean = false;
1100 ct, ett: Int64;
1101 status: cint;
1102 nextChunk: Integer = 0;
1103 chunkTotal: Integer;
1104 chunk: Integer;
1105 csize: Integer;
1106 buf: PChar = nil;
1107 resumed: Boolean;
1108 //stx: Int64;
1109 begin
1110 tf.resumed := false;
1111 e_LogWritefln('file `%s`, size=%d (%d)', [tf.diskName, Integer(strm.size), tf.size], TMsgType.Notify);
1112 // check if we should resume downloading
1113 resumed := (strm.size > tf.chunkSize) and (strm.size < tf.size);
1114 // send request
1115 trans_omsg.Clear();
1116 trans_omsg.Write(Byte(NTF_CLIENT_START));
1117 if resumed then chunk := strm.size div tf.chunkSize else chunk := 0;
1118 trans_omsg.Write(LongInt(chunk));
1119 if not ftransSendClientMsg(trans_omsg) then begin result := -1; exit; end;
1121 strm.Seek(chunk*tf.chunkSize, soFromBeginning);
1122 chunkTotal := (tf.size+tf.chunkSize-1) div tf.chunkSize;
1123 e_LogWritefln('receiving file `%s` (%d chunks)', [tf.diskName, chunkTotal], TMsgType.Notify);
1124 g_Game_SetLoadingText('downloading "'+ExtractFileName(tf.diskName)+'"', chunkTotal, False);
1125 tf.resumed := resumed;
1127 if (chunk > 0) then g_Game_StepLoading(chunk);
1128 nextChunk := chunk;
1130 // wait for reply data
1131 FillChar(ev, SizeOf(ev), 0);
1132 Result := -1;
1133 GetMem(buf, tf.chunkSize);
1134 try
1135 ett := getNewTimeoutEnd();
1136 repeat
1137 //stx := -GetTimerMS();
1138 status := enet_host_service(NetHost, @ev, 300);
1140 if (status < 0) then
1141 begin
1142 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True);
1143 Result := -1;
1144 exit;
1145 end;
1147 if (status <= 0) then
1148 begin
1149 // check for timeout
1150 ct := GetTimerMS();
1151 if (ct >= ett) then
1152 begin
1153 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' timeout reached', True);
1154 Result := -1;
1155 exit;
1156 end;
1157 end
1158 else
1159 begin
1160 // some event
1161 case ev.kind of
1162 ENET_EVENT_TYPE_RECEIVE:
1163 begin
1164 freePacket := true;
1165 if (ev.channelID <> NET_CHAN_DOWNLOAD_EX) then
1166 begin
1167 //e_LogWritefln('g_Net_Wait_Event: skip message from non-transfer channel', []);
1168 freePacket := false;
1169 g_Net_Client_HandlePacket(ev.packet, g_Net_ClientLightMsgHandler);
1170 if (g_Res_received_map_start < 0) then begin result := -666; exit; end;
1171 end
1172 else
1173 begin
1174 //stx := stx+GetTimerMS();
1175 //e_LogWritefln('g_Net_ReceiveResourceFile: stx=%d', [Integer(stx)]);
1176 //stx := -GetTimerMS();
1177 ett := getNewTimeoutEnd();
1178 if (ev.packet.dataLength < 1) then
1179 begin
1180 e_LogWritefln('g_Net_ReceiveResourceFile: invalid server packet (no data)', []);
1181 Result := -1;
1182 exit;
1183 end;
1184 Ptr := ev.packet^.data;
1185 rMsgId := Byte(Ptr^);
1186 if (rMsgId = NTF_SERVER_DONE) then
1187 begin
1188 e_LogWritefln('file transfer complete.', []);
1189 result := 0;
1190 exit;
1191 end
1192 else if (rMsgId = NTF_SERVER_CHUNK) then
1193 begin
1194 if not msg.Init(ev.packet^.data+1, ev.packet^.dataLength-1, True) then exit;
1195 chunk := msg.ReadLongInt();
1196 csize := msg.ReadLongInt();
1197 if (chunk <> nextChunk) then
1198 begin
1199 e_LogWritefln('received chunk %d, but expected chunk %d', [chunk, nextChunk]);
1200 Result := -1;
1201 exit;
1202 end;
1203 if (csize < 0) or (csize > tf.chunkSize) then
1204 begin
1205 e_LogWritefln('received chunk with size %d, but expected chunk size is %d', [csize, tf.chunkSize]);
1206 Result := -1;
1207 exit;
1208 end;
1209 //e_LogWritefln('got chunk #%d of #%d (csize=%d)', [chunk, (tf.size+tf.chunkSize-1) div tf.chunkSize, csize]);
1210 msg.ReadData(buf, csize);
1211 strm.WriteBuffer(buf^, csize);
1212 nextChunk := chunk+1;
1213 g_Game_StepLoading();
1214 // send ack
1215 trans_omsg.Clear();
1216 trans_omsg.Write(Byte(NTF_CLIENT_ACK));
1217 trans_omsg.Write(LongInt(chunk));
1218 if not ftransSendClientMsg(trans_omsg) then begin result := -1; exit; end;
1219 end
1220 else if (rMsgId = NTF_SERVER_ABORT) then
1221 begin
1222 e_LogWritefln('g_Net_ReceiveResourceFile: server aborted transfer', []);
1223 Result := 2;
1224 exit;
1225 end
1226 else
1227 begin
1228 e_LogWritefln('g_Net_ReceiveResourceFile: invalid server packet type', []);
1229 Result := -1;
1230 exit;
1231 end;
1232 //stx := stx+GetTimerMS();
1233 //e_LogWritefln('g_Net_ReceiveResourceFile: process stx=%d', [Integer(stx)]);
1234 end;
1235 end;
1236 ENET_EVENT_TYPE_DISCONNECT:
1237 begin
1238 if (ev.data <= NET_DISC_MAX) then
1239 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' ' + _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + ev.data)], True);
1240 Result := -1;
1241 exit;
1242 end;
1243 else
1244 begin
1245 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' unknown ENet event ' + IntToStr(Ord(ev.kind)), True);
1246 result := -1;
1247 exit;
1248 end;
1249 end;
1250 if (freePacket) then begin freePacket := false; enet_packet_destroy(ev.packet); end;
1251 end;
1252 ProcessLoading();
1253 if g_Net_UserRequestExit() then
1254 begin
1255 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' user abort', True);
1256 Result := 1;
1257 exit;
1258 end;
1259 until false;
1260 finally
1261 FreeMem(buf);
1262 if (freePacket) then enet_packet_destroy(ev.packet);
1263 end;
1264 end;
1267 //**************************************************************************
1268 //
1269 // common functions
1270 //
1271 //**************************************************************************
1273 function g_Net_FindSlot(): Integer;
1274 var
1275 I: Integer;
1276 F: Boolean;
1277 N, C: Integer;
1278 begin
1279 N := -1;
1280 F := False;
1281 C := 0;
1282 for I := Low(NetClients) to High(NetClients) do
1283 begin
1284 if NetClients[I].Used then
1285 Inc(C)
1286 else
1287 if not F then
1288 begin
1289 F := True;
1290 N := I;
1291 end;
1292 end;
1293 if C >= NetMaxClients then
1294 begin
1295 Result := -1;
1296 Exit;
1297 end;
1299 if not F then
1300 begin
1301 if (Length(NetClients) >= NetMaxClients) then
1302 N := -1
1303 else
1304 begin
1305 SetLength(NetClients, Length(NetClients) + 1);
1306 N := High(NetClients);
1307 end;
1308 end;
1310 if N >= 0 then
1311 begin
1312 NetClients[N].Used := True;
1313 NetClients[N].ID := N;
1314 NetClients[N].RequestedFullUpdate := False;
1315 NetClients[N].WaitForFirstSpawn := False;
1316 NetClients[N].RCONAuth := False;
1317 NetClients[N].Voted := False;
1318 NetClients[N].Player := 0;
1319 clearNetClientTransfers(NetClients[N]); // just in case
1320 end;
1322 Result := N;
1323 end;
1326 function g_Net_Init(): Boolean;
1327 var
1328 F: TextFile;
1329 IPstr: string;
1330 IP: LongWord;
1331 path: AnsiString;
1332 begin
1333 NetIn.Clear();
1334 NetOut.Clear();
1335 NetBuf[NET_UNRELIABLE].Clear();
1336 NetBuf[NET_RELIABLE].Clear();
1337 //SetLength(NetClients, 0);
1338 clearNetClients(true); // clear array
1339 NetPeer := nil;
1340 NetHost := nil;
1341 NetMyID := -1;
1342 NetPlrUID1 := -1;
1343 NetPlrUID2 := -1;
1344 NetAddr.port := 25666;
1345 SetLength(NetBannedHosts, 0);
1346 path := BANLIST_FILENAME;
1347 if e_FindResource(DataDirs, path) = true then
1348 begin
1349 Assign(F, path);
1350 Reset(F);
1351 while not EOF(F) do
1352 begin
1353 Readln(F, IPstr);
1354 if StrToIp(IPstr, IP) then
1355 g_Net_BanHost(IP);
1356 end;
1357 CloseFile(F);
1358 g_Net_SaveBanList();
1359 end;
1361 //Result := (enet_initialize() = 0);
1362 Result := enet_init_success;
1363 end;
1365 procedure g_Net_Flush();
1366 var
1367 T: Integer;
1368 P: pENetPacket;
1369 F, Chan: enet_uint32;
1370 I: Integer;
1371 begin
1372 F := 0;
1373 Chan := NET_CHAN_GAME;
1375 if NetMode = NET_SERVER then
1376 for T := NET_UNRELIABLE to NET_RELIABLE do
1377 begin
1378 if NetBuf[T].CurSize > 0 then
1379 begin
1380 P := enet_packet_create(NetBuf[T].Data, NetBuf[T].CurSize, F);
1381 if not Assigned(P) then continue;
1382 enet_host_broadcast(NetHost, Chan, P);
1383 NetBuf[T].Clear();
1384 end;
1386 for I := Low(NetClients) to High(NetClients) do
1387 begin
1388 if not NetClients[I].Used then continue;
1389 if NetClients[I].NetOut[T].CurSize <= 0 then continue;
1390 P := enet_packet_create(NetClients[I].NetOut[T].Data, NetClients[I].NetOut[T].CurSize, F);
1391 if not Assigned(P) then continue;
1392 enet_peer_send(NetClients[I].Peer, Chan, P);
1393 NetClients[I].NetOut[T].Clear();
1394 end;
1396 // next and last iteration is always RELIABLE
1397 F := LongWord(ENET_PACKET_FLAG_RELIABLE);
1398 Chan := NET_CHAN_IMPORTANT;
1399 end
1400 else if NetMode = NET_CLIENT then
1401 for T := NET_UNRELIABLE to NET_RELIABLE do
1402 begin
1403 if NetBuf[T].CurSize > 0 then
1404 begin
1405 P := enet_packet_create(NetBuf[T].Data, NetBuf[T].CurSize, F);
1406 if not Assigned(P) then continue;
1407 enet_peer_send(NetPeer, Chan, P);
1408 NetBuf[T].Clear();
1409 end;
1410 // next and last iteration is always RELIABLE
1411 F := LongWord(ENET_PACKET_FLAG_RELIABLE);
1412 Chan := NET_CHAN_IMPORTANT;
1413 end;
1414 end;
1416 procedure g_Net_Cleanup();
1417 begin
1418 NetIn.Clear();
1419 NetOut.Clear();
1420 NetBuf[NET_UNRELIABLE].Clear();
1421 NetBuf[NET_RELIABLE].Clear();
1423 //SetLength(NetClients, 0);
1424 clearNetClients(true); // clear array
1425 NetClientCount := 0;
1427 NetPeer := nil;
1428 NetHost := nil;
1429 g_Net_Slist_ServerClosed();
1430 NetMyID := -1;
1431 NetPlrUID1 := -1;
1432 NetPlrUID2 := -1;
1433 NetState := NET_STATE_NONE;
1435 NetPongSock := ENET_SOCKET_NULL;
1437 NetTimeToMaster := 0;
1438 NetTimeToUpdate := 0;
1439 NetTimeToReliable := 0;
1441 NetMode := NET_NONE;
1443 if NetPortThread <> NilThreadId then
1444 WaitForThreadTerminate(NetPortThread, 66666);
1446 NetPortThread := NilThreadId;
1447 g_Net_UnforwardPorts();
1449 if NetDump then
1450 g_Net_DumpEnd();
1451 end;
1453 procedure g_Net_Free();
1454 begin
1455 g_Net_Cleanup();
1457 //enet_deinitialize();
1458 NetInitDone := False;
1459 end;
1462 //**************************************************************************
1463 //
1464 // SERVER FUNCTIONS
1465 //
1466 //**************************************************************************
1468 function ForwardThread(Param: Pointer): PtrInt;
1469 begin
1470 Result := 0;
1471 if not g_Net_ForwardPorts() then Result := -1;
1472 end;
1474 function g_Net_Host(IPAddr: LongWord; Port: enet_uint16; MaxClients: Cardinal = 16): Boolean;
1475 begin
1476 if NetMode <> NET_NONE then
1477 begin
1478 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_INGAME]);
1479 Result := False;
1480 Exit;
1481 end;
1483 Result := True;
1485 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_HOST], [Port]));
1486 if not NetInitDone then
1487 begin
1488 if (not g_Net_Init()) then
1489 begin
1490 g_Console_Add(_lc[I_NET_MSG_FERROR] + _lc[I_NET_ERR_ENET]);
1491 Result := False;
1492 Exit;
1493 end
1494 else
1495 NetInitDone := True;
1496 end;
1498 NetAddr.host := IPAddr;
1499 NetAddr.port := Port;
1501 if NetForwardPorts then NetPortThread := BeginThread(ForwardThread);
1503 NetHost := enet_host_create(@NetAddr, NET_MAXCLIENTS, NET_CHANS, 0, 0);
1505 if (NetHost = nil) then
1506 begin
1507 g_Console_Add(_lc[I_NET_MSG_ERROR] + Format(_lc[I_NET_ERR_HOST], [Port]));
1508 Result := False;
1509 g_Net_Cleanup;
1510 Exit;
1511 end;
1513 NetPongSock := enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
1514 if NetPongSock <> ENET_SOCKET_NULL then
1515 begin
1516 NetPongAddr.host := IPAddr;
1517 NetPongAddr.port := NET_PING_PORT;
1518 if enet_socket_bind(NetPongSock, @NetPongAddr) < 0 then
1519 begin
1520 enet_socket_destroy(NetPongSock);
1521 NetPongSock := ENET_SOCKET_NULL;
1522 end
1523 else
1524 enet_socket_set_option(NetPongSock, ENET_SOCKOPT_NONBLOCK, 1);
1525 end;
1527 NetMode := NET_SERVER;
1528 NetOut.Clear();
1529 NetBuf[NET_UNRELIABLE].Clear();
1530 NetBuf[NET_RELIABLE].Clear();
1532 if NetDump then
1533 g_Net_DumpStart();
1534 end;
1536 procedure g_Net_Host_Die();
1537 var
1538 I: Integer;
1539 begin
1540 if NetMode <> NET_SERVER then Exit;
1542 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_DISCALL]);
1543 for I := 0 to High(NetClients) do
1544 if NetClients[I].Used then
1545 enet_peer_disconnect(NetClients[I].Peer, NET_DISC_DOWN);
1547 while enet_host_service(NetHost, @NetEvent, 1000) > 0 do
1548 if NetEvent.kind = ENET_EVENT_TYPE_RECEIVE then
1549 enet_packet_destroy(NetEvent.packet);
1551 for I := 0 to High(NetClients) do
1552 if NetClients[I].Used then
1553 begin
1554 FreeMemory(NetClients[I].Peer^.data);
1555 NetClients[I].Peer^.data := nil;
1556 enet_peer_reset(NetClients[I].Peer);
1557 NetClients[I].Peer := nil;
1558 NetClients[I].Used := False;
1559 NetClients[I].NetOut[NET_UNRELIABLE].Free();
1560 NetClients[I].NetOut[NET_RELIABLE].Free();
1561 end;
1563 clearNetClients(false); // don't clear array
1564 g_Net_Slist_ServerClosed();
1565 if NetPongSock <> ENET_SOCKET_NULL then
1566 enet_socket_destroy(NetPongSock);
1568 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_DIE]);
1569 enet_host_destroy(NetHost);
1571 NetMode := NET_NONE;
1573 g_Net_Cleanup;
1574 e_WriteLog('NET: Server stopped', TMsgType.Notify);
1575 end;
1578 procedure g_Net_Host_Send(ID: Integer; Reliable: Boolean; Chan: Byte = NET_CHAN_GAME);
1579 var
1580 T: Integer;
1581 begin
1582 if (Reliable) then
1583 T := NET_RELIABLE
1584 else
1585 T := NET_UNRELIABLE;
1587 if (ID >= 0) then
1588 begin
1589 if ID > High(NetClients) then Exit;
1590 if NetClients[ID].Peer = nil then Exit;
1591 // write size first
1592 NetClients[ID].NetOut[T].Write(Integer(NetOut.CurSize));
1593 NetClients[ID].NetOut[T].Write(NetOut);
1594 end
1595 else
1596 begin
1597 // write size first
1598 NetBuf[T].Write(Integer(NetOut.CurSize));
1599 NetBuf[T].Write(NetOut);
1600 end;
1602 if NetDump then g_Net_DumpSendBuffer();
1603 NetOut.Clear();
1604 end;
1606 procedure g_Net_Host_CheckPings();
1607 var
1608 ClAddr: ENetAddress;
1609 Buf: ENetBuffer;
1610 Len: Integer;
1611 ClTime: Int64;
1612 Ping: array [0..9] of Byte;
1613 NPl: Byte;
1614 begin
1615 if NetPongSock = ENET_SOCKET_NULL then Exit;
1617 Buf.data := Addr(Ping[0]);
1618 Buf.dataLength := 2+8;
1620 Ping[0] := 0;
1622 Len := enet_socket_receive(NetPongSock, @ClAddr, @Buf, 1);
1623 if Len < 0 then Exit;
1625 if (Ping[0] = Ord('D')) and (Ping[1] = Ord('F')) then
1626 begin
1627 ClTime := Int64(Addr(Ping[2])^);
1629 NetOut.Clear();
1630 NetOut.Write(Byte(Ord('D')));
1631 NetOut.Write(Byte(Ord('F')));
1632 NetOut.Write(NetPort);
1633 NetOut.Write(ClTime);
1634 TMasterHost.writeInfo(NetOut);
1635 NPl := 0;
1636 if gPlayer1 <> nil then Inc(NPl);
1637 if gPlayer2 <> nil then Inc(NPl);
1638 NetOut.Write(NPl);
1639 NetOut.Write(gNumBots);
1641 Buf.data := NetOut.Data;
1642 Buf.dataLength := NetOut.CurSize;
1643 enet_socket_send(NetPongSock, @ClAddr, @Buf, 1);
1645 NetOut.Clear();
1646 end;
1647 end;
1650 function g_Net_Host_Update(): enet_size_t;
1651 var
1652 IP: string;
1653 Port: Word;
1654 ID: Integer;
1655 TC: pTNetClient;
1656 TP: TPlayer;
1657 begin
1658 IP := '';
1659 Result := 0;
1661 if NetUseMaster then g_Net_Slist_Pulse();
1662 g_Net_Host_CheckPings();
1664 while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
1665 begin
1666 case (NetEvent.kind) of
1667 ENET_EVENT_TYPE_CONNECT:
1668 begin
1669 IP := IpToStr(NetEvent.Peer^.address.host);
1670 Port := NetEvent.Peer^.address.port;
1671 g_Console_Add(_lc[I_NET_MSG] +
1672 Format(_lc[I_NET_MSG_HOST_CONN], [IP, Port]));
1674 if (NetEvent.data <> NET_PROTOCOL_VER) then
1675 begin
1676 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_REJECT] +
1677 _lc[I_NET_DISC_PROTOCOL]);
1678 NetEvent.peer^.data := GetMemory(SizeOf(Byte));
1679 Byte(NetEvent.peer^.data^) := 255;
1680 enet_peer_disconnect(NetEvent.peer, NET_DISC_PROTOCOL);
1681 enet_host_flush(NetHost);
1682 Exit;
1683 end;
1685 ID := g_Net_FindSlot();
1687 if ID < 0 then
1688 begin
1689 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_REJECT] +
1690 _lc[I_NET_DISC_FULL]);
1691 NetEvent.Peer^.data := GetMemory(SizeOf(Byte));
1692 Byte(NetEvent.peer^.data^) := 255;
1693 enet_peer_disconnect(NetEvent.peer, NET_DISC_FULL);
1694 enet_host_flush(NetHost);
1695 Exit;
1696 end;
1698 NetClients[ID].Peer := NetEvent.peer;
1699 NetClients[ID].Peer^.data := GetMemory(SizeOf(Byte));
1700 Byte(NetClients[ID].Peer^.data^) := ID;
1701 NetClients[ID].State := NET_STATE_AUTH;
1702 NetClients[ID].RCONAuth := False;
1703 NetClients[ID].NetOut[NET_UNRELIABLE].Alloc(NET_BUFSIZE*2);
1704 NetClients[ID].NetOut[NET_RELIABLE].Alloc(NET_BUFSIZE*2);
1705 clearNetClientTransfers(NetClients[ID]); // just in case
1707 enet_peer_timeout(NetEvent.peer, ENET_PEER_TIMEOUT_LIMIT * 2, ENET_PEER_TIMEOUT_MINIMUM * 2, ENET_PEER_TIMEOUT_MAXIMUM * 2);
1709 Inc(NetClientCount);
1710 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_HOST_ADD], [ID]));
1711 end;
1713 ENET_EVENT_TYPE_RECEIVE:
1714 begin
1715 //e_LogWritefln('RECEIVE: chan=%u', [NetEvent.channelID]);
1716 if (NetEvent.channelID = NET_CHAN_DOWNLOAD_EX) then
1717 begin
1718 ProcessDownloadExPacket();
1719 end
1720 else
1721 begin
1722 ID := Byte(NetEvent.peer^.data^);
1723 if ID > High(NetClients) then Exit;
1724 TC := @NetClients[ID];
1726 if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
1727 g_Net_Host_HandlePacket(TC, NetEvent.packet, g_Net_HostMsgHandler);
1728 end;
1729 end;
1731 ENET_EVENT_TYPE_DISCONNECT:
1732 begin
1733 ID := Byte(NetEvent.peer^.data^);
1734 if ID > High(NetClients) then Exit;
1735 clearNetClient(NetClients[ID]);
1736 TC := @NetClients[ID];
1737 if TC = nil then Exit;
1739 if not (TC^.Used) then Exit;
1741 TP := g_Player_Get(TC^.Player);
1743 if TP <> nil then
1744 begin
1745 TP.Lives := 0;
1746 TP.Kill(K_SIMPLEKILL, 0, HIT_DISCON);
1747 g_Console_Add(Format(_lc[I_PLAYER_LEAVE], [TP.Name]), True);
1748 e_WriteLog('NET: Client ' + TP.Name + ' [' + IntToStr(ID) + '] disconnected.', TMsgType.Notify);
1749 g_Player_Remove(TP.UID);
1750 end;
1752 TC^.Used := False;
1753 TC^.State := NET_STATE_NONE;
1754 TC^.Peer := nil;
1755 TC^.Player := 0;
1756 TC^.RequestedFullUpdate := False;
1757 TC^.WaitForFirstSpawn := False;
1758 TC^.NetOut[NET_UNRELIABLE].Free();
1759 TC^.NetOut[NET_RELIABLE].Free();
1761 FreeMemory(NetEvent.peer^.data);
1762 NetEvent.peer^.data := nil;
1763 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_HOST_DISC], [ID]));
1764 Dec(NetClientCount);
1766 if NetUseMaster then g_Net_Slist_ServerPlayerLeaves();
1767 end;
1768 end;
1769 end;
1770 end;
1773 //**************************************************************************
1774 //
1775 // CLIENT FUNCTIONS
1776 //
1777 //**************************************************************************
1779 procedure g_Net_Disconnect(Forced: Boolean = False);
1780 begin
1781 if NetMode <> NET_CLIENT then Exit;
1782 if (NetHost = nil) or (NetPeer = nil) then Exit;
1784 if not Forced then
1785 begin
1786 enet_peer_disconnect(NetPeer, NET_DISC_NONE);
1788 while (enet_host_service(NetHost, @NetEvent, 1500) > 0) do
1789 begin
1790 if (NetEvent.kind = ENET_EVENT_TYPE_DISCONNECT) then
1791 begin
1792 NetPeer := nil;
1793 break;
1794 end;
1796 if (NetEvent.kind = ENET_EVENT_TYPE_RECEIVE) then
1797 enet_packet_destroy(NetEvent.packet);
1798 end;
1800 if NetPeer <> nil then
1801 begin
1802 enet_peer_reset(NetPeer);
1803 NetPeer := nil;
1804 end;
1805 end
1806 else
1807 begin
1808 e_WriteLog('NET: Kicked from server: ' + IntToStr(NetEvent.data), TMsgType.Notify);
1809 if (NetEvent.data <= NET_DISC_MAX) then
1810 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_KICK] +
1811 _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + NetEvent.data)], True);
1812 end;
1814 if NetHost <> nil then
1815 begin
1816 enet_host_destroy(NetHost);
1817 NetHost := nil;
1818 end;
1819 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_CLIENT_DISC]);
1821 g_Net_Cleanup;
1822 e_WriteLog('NET: Disconnected', TMsgType.Notify);
1823 end;
1825 procedure g_Net_Client_Send(Reliable: Boolean; Chan: Byte = NET_CHAN_GAME);
1826 var
1827 T: Integer;
1828 begin
1829 if (Reliable) then
1830 T := NET_RELIABLE
1831 else
1832 T := NET_UNRELIABLE;
1834 // write size first
1835 NetBuf[T].Write(Integer(NetOut.CurSize));
1836 NetBuf[T].Write(NetOut);
1838 if NetDump then g_Net_DumpSendBuffer();
1839 NetOut.Clear();
1840 g_Net_Flush(); // FIXME: for now, send immediately
1841 end;
1843 function g_Net_Client_Update(): enet_size_t;
1844 begin
1845 Result := 0;
1846 while (NetHost <> nil) and (enet_host_service(NetHost, @NetEvent, 0) > 0) do
1847 begin
1848 case NetEvent.kind of
1849 ENET_EVENT_TYPE_RECEIVE:
1850 begin
1851 if (NetEvent.channelID = NET_CHAN_DOWNLOAD_EX) then continue; // ignore all download packets, they're processed by separate code
1852 if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
1853 g_Net_Client_HandlePacket(NetEvent.packet, g_Net_ClientMsgHandler);
1854 end;
1856 ENET_EVENT_TYPE_DISCONNECT:
1857 begin
1858 g_Net_Disconnect(True);
1859 Result := 1;
1860 Exit;
1861 end;
1862 end;
1863 end
1864 end;
1866 function g_Net_Client_UpdateWhileLoading(): enet_size_t;
1867 begin
1868 Result := 0;
1869 while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
1870 begin
1871 case NetEvent.kind of
1872 ENET_EVENT_TYPE_RECEIVE:
1873 begin
1874 if (NetEvent.channelID = NET_CHAN_DOWNLOAD_EX) then continue; // ignore all download packets, they're processed by separate code
1875 if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
1876 g_Net_Client_HandlePacket(NetEvent.packet, g_Net_ClientLightMsgHandler);
1877 end;
1879 ENET_EVENT_TYPE_DISCONNECT:
1880 begin
1881 g_Net_Disconnect(True);
1882 Result := 1;
1883 Exit;
1884 end;
1885 end;
1886 end;
1887 g_Net_Flush();
1888 end;
1890 function g_Net_Connect(IP: string; Port: enet_uint16): Boolean;
1891 var
1892 OuterLoop: Boolean;
1893 TimeoutTime, T: Int64;
1894 begin
1895 if NetMode <> NET_NONE then
1896 begin
1897 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_ERR_INGAME], True);
1898 Result := False;
1899 Exit;
1900 end;
1902 Result := True;
1904 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_CLIENT_CONN],
1905 [IP, Port]));
1906 if not NetInitDone then
1907 begin
1908 if (not g_Net_Init()) then
1909 begin
1910 g_Console_Add(_lc[I_NET_MSG_FERROR] + _lc[I_NET_ERR_ENET], True);
1911 Result := False;
1912 Exit;
1913 end
1914 else
1915 NetInitDone := True;
1916 end;
1918 NetHost := enet_host_create(nil, 1, NET_CHANS, 0, 0);
1920 if (NetHost = nil) then
1921 begin
1922 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CLIENT], True);
1923 g_Net_Cleanup;
1924 Result := False;
1925 Exit;
1926 end;
1928 enet_address_set_host(@NetAddr, PChar(Addr(IP[1])));
1929 NetAddr.port := Port;
1931 NetPeer := enet_host_connect(NetHost, @NetAddr, NET_CHANS, NET_PROTOCOL_VER);
1933 if (NetPeer = nil) then
1934 begin
1935 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CLIENT], True);
1936 enet_host_destroy(NetHost);
1937 g_Net_Cleanup;
1938 Result := False;
1939 Exit;
1940 end;
1942 // предупредить что ждем слишком долго через N секунд
1943 TimeoutTime := sys_GetTicks() + NET_CONNECT_TIMEOUT;
1945 OuterLoop := True;
1946 while OuterLoop do
1947 begin
1948 while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
1949 begin
1950 if (NetEvent.kind = ENET_EVENT_TYPE_CONNECT) then
1951 begin
1952 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_CLIENT_DONE]);
1953 NetMode := NET_CLIENT;
1954 NetOut.Clear();
1955 enet_peer_timeout(NetPeer, ENET_PEER_TIMEOUT_LIMIT * 2, ENET_PEER_TIMEOUT_MINIMUM * 2, ENET_PEER_TIMEOUT_MAXIMUM * 2);
1956 NetClientIP := IP;
1957 NetClientPort := Port;
1958 if NetDump then
1959 g_Net_DumpStart();
1960 Exit;
1961 end;
1962 end;
1964 T := sys_GetTicks();
1965 if T > TimeoutTime then
1966 begin
1967 TimeoutTime := T + NET_CONNECT_TIMEOUT * 100; // одного предупреждения хватит
1968 g_Console_Add(_lc[I_NET_MSG_TIMEOUT_WARN], True);
1969 g_Console_Add(Format(_lc[I_NET_MSG_PORTS], [Integer(Port), Integer(NET_PING_PORT)]), True);
1970 end;
1972 ProcessLoading(true);
1974 if e_KeyPressed(IK_SPACE) or e_KeyPressed(IK_ESCAPE) or e_KeyPressed(VK_ESCAPE) or
1975 e_KeyPressed(JOY0_JUMP) or e_KeyPressed(JOY1_JUMP) or e_KeyPressed(JOY2_JUMP) or e_KeyPressed(JOY3_JUMP) then
1976 OuterLoop := False;
1977 end;
1979 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_TIMEOUT], True);
1980 g_Console_Add(Format(_lc[I_NET_MSG_PORTS], [Integer(Port), Integer(NET_PING_PORT)]), True);
1981 if NetPeer <> nil then enet_peer_reset(NetPeer);
1982 if NetHost <> nil then
1983 begin
1984 enet_host_destroy(NetHost);
1985 NetHost := nil;
1986 end;
1987 g_Net_Cleanup();
1988 Result := False;
1989 end;
1991 function IpToStr(IP: LongWord): string;
1992 var
1993 Ptr: Pointer;
1994 begin
1995 Ptr := Addr(IP);
1996 Result := IntToStr(PByte(Ptr + 0)^) + '.';
1997 Result := Result + IntToStr(PByte(Ptr + 1)^) + '.';
1998 Result := Result + IntToStr(PByte(Ptr + 2)^) + '.';
1999 Result := Result + IntToStr(PByte(Ptr + 3)^);
2000 end;
2002 function StrToIp(IPstr: string; var IP: LongWord): Boolean;
2003 var
2004 EAddr: ENetAddress;
2005 begin
2006 Result := enet_address_set_host(@EAddr, PChar(@IPstr[1])) = 0;
2007 IP := EAddr.host;
2008 end;
2010 function g_Net_Client_ByName(Name: string): pTNetClient;
2011 var
2012 a: Integer;
2013 pl: TPlayer;
2014 begin
2015 Result := nil;
2016 for a := Low(NetClients) to High(NetClients) do
2017 if (NetClients[a].Used) and (NetClients[a].State = NET_STATE_GAME) then
2018 begin
2019 pl := g_Player_Get(NetClients[a].Player);
2020 if pl = nil then continue;
2021 if Copy(LowerCase(pl.Name), 1, Length(Name)) <> LowerCase(Name) then continue;
2022 if NetClients[a].Peer <> nil then
2023 begin
2024 Result := @NetClients[a];
2025 Exit;
2026 end;
2027 end;
2028 end;
2030 function g_Net_Client_ByPlayer(PID: Word): pTNetClient;
2031 var
2032 a: Integer;
2033 begin
2034 Result := nil;
2035 for a := Low(NetClients) to High(NetClients) do
2036 if (NetClients[a].Used) and (NetClients[a].State = NET_STATE_GAME) then
2037 if NetClients[a].Player = PID then
2038 begin
2039 Result := @NetClients[a];
2040 Exit;
2041 end;
2042 end;
2044 function g_Net_ClientName_ByID(ID: Integer): string;
2045 var
2046 a: Integer;
2047 pl: TPlayer;
2048 begin
2049 Result := '';
2050 if ID = NET_EVERYONE then
2051 Exit;
2052 for a := Low(NetClients) to High(NetClients) do
2053 if (NetClients[a].ID = ID) and (NetClients[a].Used) and (NetClients[a].State = NET_STATE_GAME) then
2054 begin
2055 pl := g_Player_Get(NetClients[a].Player);
2056 if pl = nil then Exit;
2057 Result := pl.Name;
2058 end;
2059 end;
2061 procedure g_Net_SendData(Data: AByte; peer: pENetPeer; Reliable: Boolean; Chan: Byte = NET_CHAN_DOWNLOAD);
2062 var
2063 P: pENetPacket;
2064 F: enet_uint32;
2065 dataLength: Cardinal;
2066 begin
2067 dataLength := Length(Data);
2069 if (Reliable) then
2070 F := LongWord(ENET_PACKET_FLAG_RELIABLE)
2071 else
2072 F := 0;
2074 if (peer <> nil) then
2075 begin
2076 P := enet_packet_create(@Data[0], dataLength, F);
2077 if not Assigned(P) then Exit;
2078 enet_peer_send(peer, Chan, P);
2079 end
2080 else
2081 begin
2082 P := enet_packet_create(@Data[0], dataLength, F);
2083 if not Assigned(P) then Exit;
2084 enet_host_broadcast(NetHost, Chan, P);
2085 end;
2087 enet_host_flush(NetHost);
2088 end;
2090 function g_Net_IsHostBanned(IP: LongWord; Perm: Boolean = False): Boolean;
2091 var
2092 I: Integer;
2093 begin
2094 Result := False;
2095 if NetBannedHosts = nil then
2096 Exit;
2097 for I := 0 to High(NetBannedHosts) do
2098 if (NetBannedHosts[I].IP = IP) and ((not Perm) or (NetBannedHosts[I].Perm)) then
2099 begin
2100 Result := True;
2101 break;
2102 end;
2103 end;
2105 procedure g_Net_BanHost(IP: LongWord; Perm: Boolean = True); overload;
2106 var
2107 I, P: Integer;
2108 begin
2109 if IP = 0 then
2110 Exit;
2111 if g_Net_IsHostBanned(IP, Perm) then
2112 Exit;
2114 P := -1;
2115 for I := Low(NetBannedHosts) to High(NetBannedHosts) do
2116 if NetBannedHosts[I].IP = 0 then
2117 begin
2118 P := I;
2119 break;
2120 end;
2122 if P < 0 then
2123 begin
2124 SetLength(NetBannedHosts, Length(NetBannedHosts) + 1);
2125 P := High(NetBannedHosts);
2126 end;
2128 NetBannedHosts[P].IP := IP;
2129 NetBannedHosts[P].Perm := Perm;
2130 end;
2132 procedure g_Net_BanHost(IP: string; Perm: Boolean = True); overload;
2133 var
2134 a: LongWord;
2135 b: Boolean;
2136 begin
2137 b := StrToIp(IP, a);
2138 if b then
2139 g_Net_BanHost(a, Perm);
2140 end;
2142 procedure g_Net_UnbanNonPermHosts();
2143 var
2144 I: Integer;
2145 begin
2146 if NetBannedHosts = nil then
2147 Exit;
2148 for I := Low(NetBannedHosts) to High(NetBannedHosts) do
2149 if (NetBannedHosts[I].IP > 0) and not NetBannedHosts[I].Perm then
2150 begin
2151 NetBannedHosts[I].IP := 0;
2152 NetBannedHosts[I].Perm := True;
2153 end;
2154 end;
2156 function g_Net_UnbanHost(IP: string): Boolean; overload;
2157 var
2158 a: LongWord;
2159 begin
2160 Result := StrToIp(IP, a);
2161 if Result then
2162 Result := g_Net_UnbanHost(a);
2163 end;
2165 function g_Net_UnbanHost(IP: LongWord): Boolean; overload;
2166 var
2167 I: Integer;
2168 begin
2169 Result := False;
2170 if IP = 0 then
2171 Exit;
2172 if NetBannedHosts = nil then
2173 Exit;
2174 for I := 0 to High(NetBannedHosts) do
2175 if NetBannedHosts[I].IP = IP then
2176 begin
2177 NetBannedHosts[I].IP := 0;
2178 NetBannedHosts[I].Perm := True;
2179 Result := True;
2180 // no break here to clear all bans of this host, perm and non-perm
2181 end;
2182 end;
2184 procedure g_Net_SaveBanList();
2185 var
2186 F: TextFile;
2187 I: Integer;
2188 path: AnsiString;
2189 begin
2190 path := e_GetWriteableDir(DataDirs);
2191 if path <> '' then
2192 begin
2193 path := e_CatPath(path, BANLIST_FILENAME);
2194 Assign(F, path);
2195 Rewrite(F);
2196 if NetBannedHosts <> nil then
2197 for I := 0 to High(NetBannedHosts) do
2198 if NetBannedHosts[I].Perm and (NetBannedHosts[I].IP > 0) then
2199 Writeln(F, IpToStr(NetBannedHosts[I].IP));
2200 CloseFile(F)
2201 end
2202 end;
2204 procedure g_Net_DumpStart();
2205 begin
2206 if NetMode = NET_SERVER then
2207 NetDumpFile := e_CreateResource(LogDirs, NETDUMP_FILENAME + '_server')
2208 else
2209 NetDumpFile := e_CreateResource(LogDirs, NETDUMP_FILENAME + '_client');
2210 end;
2212 procedure g_Net_DumpSendBuffer();
2213 begin
2214 writeInt(NetDumpFile, gTime);
2215 writeInt(NetDumpFile, LongWord(NetOut.CurSize));
2216 writeInt(NetDumpFile, Byte(1));
2217 NetDumpFile.WriteBuffer(NetOut.Data^, NetOut.CurSize);
2218 end;
2220 procedure g_Net_DumpRecvBuffer(Buf: penet_uint8; Len: LongWord);
2221 begin
2222 if (Buf = nil) or (Len = 0) then Exit;
2223 writeInt(NetDumpFile, gTime);
2224 writeInt(NetDumpFile, Len);
2225 writeInt(NetDumpFile, Byte(0));
2226 NetDumpFile.WriteBuffer(Buf^, Len);
2227 end;
2229 procedure g_Net_DumpEnd();
2230 begin
2231 NetDumpFile.Free();
2232 NetDumpFile := nil;
2233 end;
2235 function g_Net_ForwardPorts(ForwardPongPort: Boolean = True): Boolean;
2236 {$IFDEF USE_MINIUPNPC}
2237 var
2238 DevList: PUPNPDev;
2239 Urls: TUPNPUrls;
2240 Data: TIGDDatas;
2241 LanAddr: array [0..255] of Char;
2242 StrPort: AnsiString;
2243 Err, I: Integer;
2244 begin
2245 Result := False;
2247 if NetPortForwarded = NetPort then
2248 begin
2249 Result := True;
2250 exit;
2251 end;
2253 NetPongForwarded := False;
2254 NetPortForwarded := 0;
2256 DevList := upnpDiscover(1000, nil, nil, 0, 0, 2, Addr(Err));
2257 if DevList = nil then
2258 begin
2259 conwritefln('port forwarding failed: upnpDiscover() failed: %d', [Err]);
2260 exit;
2261 end;
2263 I := UPNP_GetValidIGD(DevList, @Urls, @Data, Addr(LanAddr[0]), 256);
2265 if I = 0 then
2266 begin
2267 conwriteln('port forwarding failed: could not find an IGD device on this LAN');
2268 FreeUPNPDevList(DevList);
2269 FreeUPNPUrls(@Urls);
2270 exit;
2271 end;
2273 StrPort := IntToStr(NetPort);
2274 I := UPNP_AddPortMapping(
2275 Urls.controlURL, Addr(data.first.servicetype[1]),
2276 PChar(StrPort), PChar(StrPort), Addr(LanAddr[0]), PChar('D2DF'),
2277 PChar('UDP'), nil, PChar('0')
2278 );
2280 if I <> 0 then
2281 begin
2282 conwritefln('forwarding port %d failed: error %d', [NetPort, I]);
2283 FreeUPNPDevList(DevList);
2284 FreeUPNPUrls(@Urls);
2285 exit;
2286 end;
2288 if ForwardPongPort then
2289 begin
2290 StrPort := IntToStr(NET_PING_PORT);
2291 I := UPNP_AddPortMapping(
2292 Urls.controlURL, Addr(data.first.servicetype[1]),
2293 PChar(StrPort), PChar(StrPort), Addr(LanAddr[0]), PChar('D2DF'),
2294 PChar('UDP'), nil, PChar('0')
2295 );
2297 if I <> 0 then
2298 begin
2299 conwritefln('forwarding port %d failed: error %d', [NET_PING_PORT, I]);
2300 NetPongForwarded := False;
2301 end
2302 else
2303 begin
2304 conwritefln('forwarded port %d successfully', [NET_PING_PORT]);
2305 NetPongForwarded := True;
2306 end;
2307 end;
2309 conwritefln('forwarded port %d successfully', [NetPort]);
2310 NetIGDControl := AnsiString(Urls.controlURL);
2311 NetIGDService := data.first.servicetype;
2312 NetPortForwarded := NetPort;
2314 FreeUPNPDevList(DevList);
2315 FreeUPNPUrls(@Urls);
2316 Result := True;
2317 end;
2318 {$ELSE}
2319 begin
2320 Result := False;
2321 end;
2322 {$ENDIF}
2324 procedure g_Net_UnforwardPorts();
2325 {$IFDEF USE_MINIUPNPC}
2326 var
2327 I: Integer;
2328 StrPort: AnsiString;
2329 begin
2330 if NetPortForwarded = 0 then Exit;
2332 conwriteln('unforwarding ports...');
2334 StrPort := IntToStr(NetPortForwarded);
2335 I := UPNP_DeletePortMapping(
2336 PChar(NetIGDControl), Addr(NetIGDService[1]),
2337 PChar(StrPort), PChar('UDP'), nil
2338 );
2339 conwritefln(' port %d: %d', [NetPortForwarded, I]);
2341 if NetPongForwarded then
2342 begin
2343 NetPongForwarded := False;
2344 StrPort := IntToStr(NET_PING_PORT);
2345 I := UPNP_DeletePortMapping(
2346 PChar(NetIGDControl), Addr(NetIGDService[1]),
2347 PChar(StrPort), PChar('UDP'), nil
2348 );
2349 conwritefln(' port %d: %d', [NET_PING_PORT, I]);
2350 end;
2352 NetPortForwarded := 0;
2353 end;
2354 {$ELSE}
2355 begin
2356 end;
2357 {$ENDIF}
2359 procedure NetServerCVars(P: SSArray);
2360 var
2361 cmd, s: string;
2362 a, b: Integer;
2363 begin
2364 cmd := LowerCase(P[0]);
2365 case cmd of
2366 'sv_name':
2367 begin
2368 if (Length(P) > 1) and (Length(P[1]) > 0) then
2369 begin
2370 NetServerName := P[1];
2371 if Length(NetServerName) > 64 then
2372 SetLength(NetServerName, 64);
2373 g_Net_Slist_ServerRenamed();
2374 end;
2375 g_Console_Add(cmd + ' "' + NetServerName + '"');
2376 end;
2377 'sv_passwd':
2378 begin
2379 if (Length(P) > 1) and (Length(P[1]) > 0) then
2380 begin
2381 NetPassword := P[1];
2382 if Length(NetPassword) > 24 then
2383 SetLength(NetPassword, 24);
2384 g_Net_Slist_ServerRenamed();
2385 end;
2386 g_Console_Add(cmd + ' "' + AnsiLowerCase(NetPassword) + '"');
2387 end;
2388 'sv_maxplrs':
2389 begin
2390 if (Length(P) > 1) then
2391 begin
2392 NetMaxClients := nclamp(StrToIntDef(P[1], NetMaxClients), 1, NET_MAXCLIENTS);
2393 if g_Game_IsServer and g_Game_IsNet then
2394 begin
2395 b := 0;
2396 for a := 0 to High(NetClients) do
2397 begin
2398 if NetClients[a].Used then
2399 begin
2400 Inc(b);
2401 if b > NetMaxClients then
2402 begin
2403 s := g_Player_Get(NetClients[a].Player).Name;
2404 enet_peer_disconnect(NetClients[a].Peer, NET_DISC_FULL);
2405 g_Console_Add(Format(_lc[I_PLAYER_KICK], [s]));
2406 MH_SEND_GameEvent(NET_EV_PLAYER_KICK, 0, s);
2407 end;
2408 end;
2409 end;
2410 g_Net_Slist_ServerRenamed();
2411 end;
2412 end;
2413 g_Console_Add(cmd + ' ' + IntToStr(NetMaxClients));
2414 end;
2415 'sv_public':
2416 begin
2417 if (Length(P) > 1) then
2418 begin
2419 NetUseMaster := StrToIntDef(P[1], Byte(NetUseMaster)) <> 0;
2420 if NetUseMaster then g_Net_Slist_Public() else g_Net_Slist_Private();
2421 end;
2422 g_Console_Add(cmd + ' ' + IntToStr(Byte(NetUseMaster)));
2423 end;
2424 'sv_port':
2425 begin
2426 if (Length(P) > 1) then
2427 begin
2428 if not g_Game_IsNet then
2429 NetPort := nclamp(StrToIntDef(P[1], NetPort), 0, $FFFF)
2430 else
2431 g_Console_Add(_lc[I_MSG_NOT_NETGAME]);
2432 end;
2433 g_Console_Add(cmd + ' ' + IntToStr(Ord(NetUseMaster)));
2434 end;
2435 end;
2436 end;
2438 initialization
2439 conRegVar('cl_downloadtimeout', @g_Net_DownloadTimeout, 0.0, 1000000.0, '', 'timeout in seconds, 0 to disable it');
2440 conRegVar('cl_predictself', @NetPredictSelf, '', 'predict local player');
2441 conRegVar('cl_forceplayerupdate', @NetForcePlayerUpdate, '', 'update net players on NET_MSG_PLRPOS');
2442 conRegVar('cl_interp', @NetInterpLevel, '', 'net player interpolation steps');
2443 conRegVar('cl_last_ip', @NetClientIP, '', 'address of the last you have connected to');
2444 conRegVar('cl_last_port', @NetClientPort, '', 'port of the last server you have connected to');
2446 conRegVar('sv_forwardports', @NetForwardPorts, '', 'forward server port using miniupnpc (requires server restart)');
2447 conRegVar('sv_rcon', @NetAllowRCON, '', 'enable remote console');
2448 conRegVar('sv_rcon_password', @NetRCONPassword, '', 'remote console password');
2449 conRegVar('sv_update_interval', @NetUpdateRate, '', 'unreliable update interval');
2450 conRegVar('sv_reliable_interval', @NetRelupdRate, '', 'reliable update interval');
2451 conRegVar('sv_master_interval', @NetMasterRate, '', 'master server update interval');
2453 conRegVar('net_master_list', @NetMasterList, '', 'list of master servers');
2455 SetLength(NetClients, 0);
2456 g_Net_DownloadTimeout := 60;
2457 NetIn.Alloc(NET_BUFSIZE);
2458 NetOut.Alloc(NET_BUFSIZE);
2459 NetBuf[NET_UNRELIABLE].Alloc(NET_BUFSIZE*2);
2460 NetBuf[NET_RELIABLE].Alloc(NET_BUFSIZE*2);
2461 trans_omsg.Alloc(NET_BUFSIZE);
2462 finalization
2463 NetIn.Free();
2464 NetOut.Free();
2465 NetBuf[NET_UNRELIABLE].Free();
2466 NetBuf[NET_RELIABLE].Free();
2467 end.