DEADSOFTWARE

099ab76f4f248319c002751bd5d6f864a5c98c8b
[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, ENet, Classes, md5, MAPDEF{$IFDEF USE_MINIUPNPC}, miniupnpc;{$ELSE};{$ENDIF}
23 const
24 NET_PROTOCOL_VER = 182;
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 NetSlistIP: string = 'mpms.doom2d.org';
151 NetSlistPort: Word = 25665;
152 NetSlistList: string = 'deadsoftware.ru:25665';
154 NetClientIP: string = '127.0.0.1';
155 NetClientPort: Word = 25666;
157 NetIn, NetOut: TMsg;
158 NetBuf: array [0..1] of TMsg;
160 NetClients: array of TNetClient;
161 NetClientCount: Byte = 0;
162 NetMaxClients: Byte = 255;
163 NetBannedHosts: array of TBanRecord;
165 NetState: Integer = NET_STATE_NONE;
167 NetMyID: Integer = -1;
168 NetPlrUID1: Integer = -1;
169 NetPlrUID2: Integer = -1;
171 NetInterpLevel: Integer = 1;
172 NetUpdateRate: Cardinal = 0; // as soon as possible
173 NetRelupdRate: Cardinal = 18; // around two times a second
174 NetMasterRate: Cardinal = 60000;
176 NetForcePlayerUpdate: Boolean = False;
177 NetPredictSelf: Boolean = True;
178 NetForwardPorts: Boolean = False;
180 NetGotEverything: Boolean = False;
181 NetGotKeys: Boolean = False;
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; Chan: Byte = NET_CHAN_GAME);
205 function g_Net_Host_Update(): enet_size_t;
207 function g_Net_Connect(IP: string; Port: enet_uint16): Boolean;
208 procedure g_Net_Disconnect(Forced: Boolean = False);
209 procedure g_Net_Client_Send(Reliable: Boolean; Chan: Byte = NET_CHAN_GAME);
210 function g_Net_Client_Update(): enet_size_t;
211 function g_Net_Client_UpdateWhileLoading(): enet_size_t;
213 function g_Net_Client_ByName(Name: string): pTNetClient;
214 function g_Net_Client_ByPlayer(PID: Word): pTNetClient;
215 function g_Net_ClientName_ByID(ID: Integer): string;
217 procedure g_Net_SendData(Data: AByte; peer: pENetPeer; Reliable: Boolean; Chan: Byte = NET_CHAN_DOWNLOAD);
218 //function g_Net_Wait_Event(msgId: Word): TMemoryStream;
219 //function g_Net_Wait_FileInfo (var tf: TNetFileTransfer; asMap: Boolean; out resList: TStringList): Integer;
221 function IpToStr(IP: LongWord): string;
222 function StrToIp(IPstr: string; var IP: LongWord): Boolean;
224 function g_Net_IsHostBanned(IP: LongWord; Perm: Boolean = False): Boolean;
225 procedure g_Net_BanHost(IP: LongWord; Perm: Boolean = True); overload;
226 procedure g_Net_BanHost(IP: string; Perm: Boolean = True); overload;
227 function g_Net_UnbanHost(IP: string): Boolean; overload;
228 function g_Net_UnbanHost(IP: LongWord): Boolean; overload;
229 procedure g_Net_UnbanNonPermHosts();
230 procedure g_Net_SaveBanList();
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 ();
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, utils, ctypes, g_system,
264 g_map;
266 const
267 FILE_CHUNK_SIZE = 8192;
269 var
270 enet_init_success: Boolean = false;
271 g_Net_DownloadTimeout: Single;
272 trans_omsg: TMsg;
275 function g_Net_IsNetworkAvailable (): Boolean;
276 begin
277 result := enet_init_success;
278 end;
280 procedure g_Net_InitLowLevel ();
281 begin
282 if enet_init_success then raise Exception.Create('wuta?!');
283 enet_init_success := (enet_initialize() = 0);
284 end;
286 procedure g_Net_DeinitLowLevel ();
287 begin
288 if enet_init_success then
289 begin
290 enet_deinitialize();
291 enet_init_success := false;
292 end;
293 end;
296 //**************************************************************************
297 //
298 // SERVICE FUNCTIONS
299 //
300 //**************************************************************************
302 procedure clearNetClientTransfers (var nc: TNetClient);
303 begin
304 nc.Transfer.stream.Free;
305 nc.Transfer.diskName := ''; // just in case
306 if (nc.Transfer.diskBuffer <> nil) then FreeMem(nc.Transfer.diskBuffer);
307 nc.Transfer.stream := nil;
308 nc.Transfer.diskBuffer := nil;
309 end;
312 procedure clearNetClient (var nc: TNetClient);
313 begin
314 clearNetClientTransfers(nc);
315 end;
318 procedure clearNetClients (clearArray: Boolean);
319 var
320 f: Integer;
321 begin
322 for f := Low(NetClients) to High(NetClients) do clearNetClient(NetClients[f]);
323 if (clearArray) then SetLength(NetClients, 0);
324 end;
327 function g_Net_UserRequestExit (): Boolean;
328 begin
329 Result := {e_KeyPressed(IK_SPACE) or}
330 e_KeyPressed(IK_ESCAPE) or
331 e_KeyPressed(VK_ESCAPE) or
332 e_KeyPressed(JOY0_JUMP) or
333 e_KeyPressed(JOY1_JUMP) or
334 e_KeyPressed(JOY2_JUMP) or
335 e_KeyPressed(JOY3_JUMP)
336 end;
339 //**************************************************************************
340 //
341 // file transfer declaraions and host packet processor
342 //
343 //**************************************************************************
345 const
346 // server packet type
347 NTF_SERVER_DONE = 10; // done with this file
348 NTF_SERVER_FILE_INFO = 11; // sent after client request
349 NTF_SERVER_CHUNK = 12; // next chunk; chunk number follows
350 NTF_SERVER_ABORT = 13; // server abort
351 NTF_SERVER_MAP_INFO = 14;
353 // client packet type
354 NTF_CLIENT_MAP_REQUEST = 100; // map file request; also, returns list of additional wads to download
355 NTF_CLIENT_FILE_REQUEST = 101; // resource file request (by index)
356 NTF_CLIENT_ABORT = 102; // do not send requested file, or abort current transfer
357 NTF_CLIENT_START = 103; // start transfer; client may resume download by sending non-zero starting chunk
358 NTF_CLIENT_ACK = 104; // chunk ack; chunk number follows
361 // disconnect client due to some file transfer error
362 procedure killClientByFT (var nc: TNetClient);
363 begin
364 e_LogWritefln('disconnected client #%d due to file transfer error', [nc.ID], TMsgType.Warning);
365 enet_peer_disconnect(nc.Peer, NET_DISC_FILE_TIMEOUT);
366 clearNetClientTransfers(nc);
367 g_Net_Slist_ServerPlayerLeaves();
368 end;
371 // send file transfer message from server to client
372 function ftransSendServerMsg (var nc: TNetClient; var m: TMsg): Boolean;
373 var
374 pkt: PENetPacket;
375 begin
376 result := false;
377 if (m.CurSize < 1) then exit;
378 pkt := enet_packet_create(m.Data, m.CurSize, ENET_PACKET_FLAG_RELIABLE);
379 if not Assigned(pkt) then begin killClientByFT(nc); exit; end;
380 if (enet_peer_send(nc.Peer, NET_CHAN_DOWNLOAD_EX, pkt) <> 0) then begin killClientByFT(nc); exit; end;
381 result := true;
382 end;
385 // send file transfer message from client to server
386 function ftransSendClientMsg (var m: TMsg): Boolean;
387 var
388 pkt: PENetPacket;
389 begin
390 result := false;
391 if (m.CurSize < 1) then exit;
392 pkt := enet_packet_create(m.Data, m.CurSize, ENET_PACKET_FLAG_RELIABLE);
393 if not Assigned(pkt) then exit;
394 if (enet_peer_send(NetPeer, NET_CHAN_DOWNLOAD_EX, pkt) <> 0) then exit;
395 result := true;
396 end;
399 // file chunk sender
400 procedure ProcessChunkSend (var nc: TNetClient);
401 var
402 tf: ^TNetFileTransfer;
403 ct: Int64;
404 chunks: Integer;
405 rd: Integer;
406 begin
407 tf := @nc.Transfer;
408 if (tf.stream = nil) then exit;
409 ct := GetTimerMS();
410 // arbitrary timeout number
411 if (ct-tf.lastAckTime >= 5000) then
412 begin
413 killClientByFT(nc);
414 exit;
415 end;
416 // check if we need to send something
417 if (not tf.inProgress) then exit; // waiting for the initial ack
418 // ok, we're sending chunks
419 if (tf.lastAckChunk <> tf.lastSentChunk) then exit;
420 Inc(tf.lastSentChunk);
421 // do it one chunk at a time; client ack will advance our chunk counter
422 chunks := (tf.size+tf.chunkSize-1) div tf.chunkSize;
424 if (tf.lastSentChunk > chunks) then
425 begin
426 killClientByFT(nc);
427 exit;
428 end;
430 trans_omsg.Clear();
431 if (tf.lastSentChunk = chunks) then
432 begin
433 // we're done with this file
434 e_LogWritefln('download: client #%d, DONE sending chunks #%d/#%d', [nc.ID, tf.lastSentChunk, chunks]);
435 trans_omsg.Write(Byte(NTF_SERVER_DONE));
436 clearNetClientTransfers(nc);
437 end
438 else
439 begin
440 // packet type
441 trans_omsg.Write(Byte(NTF_SERVER_CHUNK));
442 trans_omsg.Write(LongInt(tf.lastSentChunk));
443 // read chunk
444 rd := tf.size-(tf.lastSentChunk*tf.chunkSize);
445 if (rd > tf.chunkSize) then rd := tf.chunkSize;
446 trans_omsg.Write(LongInt(rd));
447 //e_LogWritefln('download: client #%d, sending chunk #%d/#%d (%d bytes)', [nc.ID, tf.lastSentChunk, chunks, rd]);
448 //FIXME: check for errors here
449 try
450 tf.stream.Seek(tf.lastSentChunk*tf.chunkSize, soFromBeginning);
451 tf.stream.ReadBuffer(tf.diskBuffer^, rd);
452 trans_omsg.WriteData(tf.diskBuffer, rd);
453 except // sorry
454 killClientByFT(nc);
455 exit;
456 end;
457 end;
458 // send packet
459 ftransSendServerMsg(nc, trans_omsg);
460 end;
463 // server file transfer packet processor
464 // received packet is in `NetEvent`
465 procedure ProcessDownloadExPacket ();
466 var
467 f: Integer;
468 nc: ^TNetClient;
469 nid: Integer = -1;
470 msg: TMsg;
471 cmd: Byte;
472 tf: ^TNetFileTransfer;
473 fname: string;
474 chunk: Integer;
475 ridx: Integer;
476 dfn: AnsiString;
477 md5: TMD5Digest;
478 //st: TStream;
479 size: LongInt;
480 fi: TDiskFileInfo;
481 begin
482 // find client index by peer
483 for f := Low(NetClients) to High(NetClients) do
484 begin
485 if (not NetClients[f].Used) then continue;
486 if (NetClients[f].Peer = NetEvent.peer) then
487 begin
488 nid := f;
489 break;
490 end;
491 end;
492 //e_LogWritefln('RECEIVE: dlpacket; client=%d (datalen=%u)', [nid, NetEvent.packet^.dataLength]);
494 if (nid < 0) then exit; // wtf?!
495 nc := @NetClients[nid];
497 if (NetEvent.packet^.dataLength = 0) then
498 begin
499 killClientByFT(nc^);
500 exit;
501 end;
503 tf := @NetClients[nid].Transfer;
504 tf.lastAckTime := GetTimerMS();
506 cmd := Byte(NetEvent.packet^.data^);
507 //e_LogWritefln('RECEIVE: nid=%d; cmd=%u', [nid, cmd]);
508 case cmd of
509 NTF_CLIENT_FILE_REQUEST: // file request
510 begin
511 if (tf.stream <> nil) then
512 begin
513 killClientByFT(nc^);
514 exit;
515 end;
516 if (NetEvent.packet^.dataLength < 2) then
517 begin
518 killClientByFT(nc^);
519 exit;
520 end;
521 // new transfer request; build packet
522 if not msg.Init(NetEvent.packet^.data+1, NetEvent.packet^.dataLength-1, True) then
523 begin
524 killClientByFT(nc^);
525 exit;
526 end;
527 // get resource index
528 ridx := msg.ReadLongInt();
529 if (ridx < -1) or (ridx >= length(gExternalResources)) then
530 begin
531 e_LogWritefln('Invalid resource index %d', [ridx], TMsgType.Warning);
532 killClientByFT(nc^);
533 exit;
534 end;
535 if (ridx < 0) then fname := gGameSettings.WAD else fname := gExternalResources[ridx].diskName;
536 if (length(fname) = 0) then
537 begin
538 e_WriteLog('Invalid filename: '+fname, TMsgType.Warning);
539 killClientByFT(nc^);
540 exit;
541 end;
542 tf.diskName := findDiskWad(fname);
543 //if (length(tf.diskName) = 0) then tf.diskName := findDiskWad(GameDir+'/wads/'+fname);
544 if (length(tf.diskName) = 0) then
545 begin
546 e_LogWritefln('NETWORK: file "%s" not found!', [fname], TMsgType.Fatal);
547 killClientByFT(nc^);
548 exit;
549 end;
550 // calculate hash
551 //tf.hash := MD5File(tf.diskName);
552 if (ridx < 0) then tf.hash := gWADHash else tf.hash := gExternalResources[ridx].hash;
553 // create file stream
554 tf.diskName := findDiskWad(fname);
555 try
556 tf.stream := openDiskFileRO(tf.diskName);
557 except
558 tf.stream := nil;
559 end;
560 if (tf.stream = nil) then
561 begin
562 e_WriteLog(Format('NETWORK: file "%s" not found!', [fname]), TMsgType.Fatal);
563 killClientByFT(nc^);
564 exit;
565 end;
566 e_LogWritefln('client #%d requested resource #%d (file is `%s` : `%s`)', [nc.ID, ridx, fname, tf.diskName]);
567 tf.size := tf.stream.size;
568 tf.chunkSize := FILE_CHUNK_SIZE; // arbitrary
569 tf.lastSentChunk := -1;
570 tf.lastAckChunk := -1;
571 tf.lastAckTime := GetTimerMS();
572 tf.inProgress := False; // waiting for the first ACK or for the cancel
573 GetMem(tf.diskBuffer, tf.chunkSize);
574 // sent file info message
575 trans_omsg.Clear();
576 trans_omsg.Write(Byte(NTF_SERVER_FILE_INFO));
577 trans_omsg.Write(tf.hash);
578 trans_omsg.Write(tf.size);
579 trans_omsg.Write(tf.chunkSize);
580 trans_omsg.Write(ExtractFileName(fname));
581 if not ftransSendServerMsg(nc^, trans_omsg) then exit;
582 end;
583 NTF_CLIENT_ABORT: // do not send requested file, or abort current transfer
584 begin
585 e_LogWritefln('client #%d aborted file transfer', [nc.ID]);
586 clearNetClientTransfers(nc^);
587 end;
588 NTF_CLIENT_START: // start transfer; client may resume download by sending non-zero starting chunk
589 begin
590 if not Assigned(tf.stream) then
591 begin
592 killClientByFT(nc^);
593 exit;
594 end;
595 if (tf.lastSentChunk <> -1) or (tf.lastAckChunk <> -1) or (tf.inProgress) then
596 begin
597 // double ack, get lost
598 killClientByFT(nc^);
599 exit;
600 end;
601 if (NetEvent.packet^.dataLength < 2) then
602 begin
603 killClientByFT(nc^);
604 exit;
605 end;
606 // build packet
607 if not msg.Init(NetEvent.packet^.data+1, NetEvent.packet^.dataLength-1, True) then
608 begin
609 killClientByFT(nc^);
610 exit;
611 end;
612 chunk := msg.ReadLongInt();
613 if (chunk < 0) or (chunk > (tf.size+tf.chunkSize-1) div tf.chunkSize) then
614 begin
615 killClientByFT(nc^);
616 exit;
617 end;
618 e_LogWritefln('client #%d started file transfer from chunk %d', [nc.ID, chunk]);
619 // start sending chunks
620 tf.inProgress := True;
621 tf.lastSentChunk := chunk-1;
622 tf.lastAckChunk := chunk-1;
623 ProcessChunkSend(nc^);
624 end;
625 NTF_CLIENT_ACK: // chunk ack; chunk number follows
626 begin
627 if not Assigned(tf.stream) then
628 begin
629 killClientByFT(nc^);
630 exit;
631 end;
632 if (tf.lastSentChunk < 0) or (not tf.inProgress) then
633 begin
634 // double ack, get lost
635 killClientByFT(nc^);
636 exit;
637 end;
638 if (NetEvent.packet^.dataLength < 2) then
639 begin
640 killClientByFT(nc^);
641 exit;
642 end;
643 // build packet
644 if not msg.Init(NetEvent.packet^.data+1, NetEvent.packet^.dataLength-1, True) then
645 begin
646 killClientByFT(nc^);
647 exit;
648 end;
649 chunk := msg.ReadLongInt();
650 if (chunk < 0) or (chunk > (tf.size+tf.chunkSize-1) div tf.chunkSize) then
651 begin
652 killClientByFT(nc^);
653 exit;
654 end;
655 // do it this way, so client may seek, or request retransfers for some reason
656 tf.lastAckChunk := chunk;
657 tf.lastSentChunk := chunk;
658 //e_LogWritefln('client #%d acked file transfer chunk %d', [nc.ID, chunk]);
659 ProcessChunkSend(nc^);
660 end;
661 NTF_CLIENT_MAP_REQUEST:
662 begin
663 e_LogWritefln('client #%d requested map info', [nc.ID]);
664 trans_omsg.Clear();
665 dfn := findDiskWad(gGameSettings.WAD);
666 if (dfn = '') then dfn := '!wad_not_found!.wad'; //FIXME
667 //md5 := MD5File(dfn);
668 md5 := gWADHash;
669 if (not GetDiskFileInfo(dfn, fi)) then
670 begin
671 e_LogWritefln('client #%d requested map info, but i cannot get file info', [nc.ID]);
672 killClientByFT(nc^);
673 exit;
674 end;
675 size := fi.size;
677 st := openDiskFileRO(dfn);
678 if not assigned(st) then exit; //wtf?!
679 size := st.size;
680 st.Free;
682 // packet type
683 trans_omsg.Write(Byte(NTF_SERVER_MAP_INFO));
684 // map wad name
685 trans_omsg.Write(gGameSettings.WAD);
686 // map wad md5
687 trans_omsg.Write(md5);
688 // map wad size
689 trans_omsg.Write(size);
690 // number of external resources for map
691 trans_omsg.Write(LongInt(length(gExternalResources)));
692 // external resource names
693 for f := 0 to High(gExternalResources) do
694 begin
695 // old style packet
696 //trans_omsg.Write(ExtractFileName(gExternalResources[f])); // GameDir+'/wads/'+ResList.Strings[i]
697 // new style packet
698 trans_omsg.Write('!');
699 trans_omsg.Write(LongInt(gExternalResources[f].size));
700 trans_omsg.Write(gExternalResources[f].hash);
701 trans_omsg.Write(ExtractFileName(gExternalResources[f].diskName));
702 end;
703 // send packet
704 if not ftransSendServerMsg(nc^, trans_omsg) then exit;
705 end;
706 else
707 begin
708 killClientByFT(nc^);
709 exit;
710 end;
711 end;
712 end;
715 //**************************************************************************
716 //
717 // file transfer crap (both client and server)
718 //
719 //**************************************************************************
721 function getNewTimeoutEnd (): Int64;
722 begin
723 result := GetTimerMS();
724 if (g_Net_DownloadTimeout <= 0) then
725 begin
726 result := result+1000*60*3; // 3 minutes
727 end
728 else
729 begin
730 result := result+trunc(g_Net_DownloadTimeout*1000);
731 end;
732 end;
735 // send map request to server, and wait for "map info" server reply
736 //
737 // returns `false` on error or user abort
738 // fills:
739 // diskName: map wad file name (without a path)
740 // hash: map wad hash
741 // size: map wad size
742 // chunkSize: set too
743 // resList: list of resource wads
744 // returns:
745 // <0 on error
746 // 0 on success
747 // 1 on user abort
748 // 2 on server abort
749 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
750 function g_Net_Wait_MapInfo (var tf: TNetFileTransfer; var resList: TNetMapResourceInfoArray): Integer;
751 var
752 ev: ENetEvent;
753 rMsgId: Byte;
754 Ptr: Pointer;
755 msg: TMsg;
756 freePacket: Boolean = false;
757 ct, ett: Int64;
758 status: cint;
759 s: AnsiString;
760 rc, f: LongInt;
761 ri: ^TNetMapResourceInfo;
762 begin
763 SetLength(resList, 0);
765 // send request
766 trans_omsg.Clear();
767 trans_omsg.Write(Byte(NTF_CLIENT_MAP_REQUEST));
768 if not ftransSendClientMsg(trans_omsg) then begin result := -1; exit; end;
770 FillChar(ev, SizeOf(ev), 0);
771 Result := -1;
772 try
773 ett := getNewTimeoutEnd();
774 repeat
775 status := enet_host_service(NetHost, @ev, 300);
777 if (status < 0) then
778 begin
779 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True);
780 Result := -1;
781 exit;
782 end;
784 if (status <= 0) then
785 begin
786 // check for timeout
787 ct := GetTimerMS();
788 if (ct >= ett) then
789 begin
790 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' timeout reached', True);
791 Result := -1;
792 exit;
793 end;
794 end
795 else
796 begin
797 // some event
798 case ev.kind of
799 ENET_EVENT_TYPE_RECEIVE:
800 begin
801 freePacket := true;
802 if (ev.channelID <> NET_CHAN_DOWNLOAD_EX) then
803 begin
804 //e_LogWritefln('g_Net_Wait_MapInfo: skip message from non-transfer channel', []);
805 freePacket := false;
806 g_Net_Client_HandlePacket(ev.packet, g_Net_ClientLightMsgHandler);
807 if (g_Res_received_map_start < 0) then begin result := -666; exit; end;
808 end
809 else
810 begin
811 ett := getNewTimeoutEnd();
812 if (ev.packet.dataLength < 1) then
813 begin
814 e_LogWritefln('g_Net_Wait_MapInfo: invalid server packet (no data)', []);
815 Result := -1;
816 exit;
817 end;
818 Ptr := ev.packet^.data;
819 rMsgId := Byte(Ptr^);
820 e_LogWritefln('g_Net_Wait_MapInfo: got message %u from server (dataLength=%u)', [rMsgId, ev.packet^.dataLength]);
821 if (rMsgId = NTF_SERVER_FILE_INFO) then
822 begin
823 e_LogWritefln('g_Net_Wait_MapInfo: waiting for map info reply, but got file info reply', []);
824 Result := -1;
825 exit;
826 end
827 else if (rMsgId = NTF_SERVER_ABORT) then
828 begin
829 e_LogWritefln('g_Net_Wait_MapInfo: server aborted transfer', []);
830 Result := 2;
831 exit;
832 end
833 else if (rMsgId = NTF_SERVER_MAP_INFO) then
834 begin
835 e_LogWritefln('g_Net_Wait_MapInfo: creating map info packet...', []);
836 if not msg.Init(ev.packet^.data+1, ev.packet^.dataLength-1, True) then exit;
837 e_LogWritefln('g_Net_Wait_MapInfo: parsing map info packet (rd=%d; max=%d)...', [msg.ReadCount, msg.MaxSize]);
838 SetLength(resList, 0); // just in case
839 // map wad name
840 tf.diskName := msg.ReadString();
841 e_LogWritefln('g_Net_Wait_MapInfo: map wad is `%s`', [tf.diskName]);
842 // map wad md5
843 tf.hash := msg.ReadMD5();
844 // map wad size
845 tf.size := msg.ReadLongInt();
846 e_LogWritefln('g_Net_Wait_MapInfo: map wad size is %d', [tf.size]);
847 // number of external resources for map
848 rc := msg.ReadLongInt();
849 if (rc < 0) or (rc > 1024) then
850 begin
851 e_LogWritefln('g_Net_Wait_Event: invalid number of map external resources (%d)', [rc]);
852 Result := -1;
853 exit;
854 end;
855 e_LogWritefln('g_Net_Wait_MapInfo: map external resource count is %d', [rc]);
856 SetLength(resList, rc);
857 // external resource names
858 for f := 0 to rc-1 do
859 begin
860 ri := @resList[f];
861 s := msg.ReadString();
862 if (length(s) = 0) then begin result := -1; exit; end;
863 if (s = '!') then
864 begin
865 // extended packet
866 ri.size := msg.ReadLongInt();
867 ri.hash := msg.ReadMD5();
868 ri.wadName := ExtractFileName(msg.ReadString());
869 if (length(ri.wadName) = 0) or (ri.size < 0) then begin result := -1; exit; end;
870 end
871 else
872 begin
873 // old-style packet, only name
874 ri.wadName := ExtractFileName(s);
875 if (length(ri.wadName) = 0) then begin result := -1; exit; end;
876 ri.size := -1; // unknown
877 end;
878 end;
879 e_LogWritefln('g_Net_Wait_MapInfo: got map info', []);
880 Result := 0; // success
881 exit;
882 end
883 else
884 begin
885 e_LogWritefln('g_Net_Wait_Event: invalid server packet type', []);
886 Result := -1;
887 exit;
888 end;
889 end;
890 end;
891 ENET_EVENT_TYPE_DISCONNECT:
892 begin
893 if (ev.data <= NET_DISC_MAX) then
894 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' ' + _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + ev.data)], True);
895 Result := -1;
896 exit;
897 end;
898 else
899 begin
900 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' unknown ENet event ' + IntToStr(Ord(ev.kind)), True);
901 result := -1;
902 exit;
903 end;
904 end;
905 if (freePacket) then begin freePacket := false; enet_packet_destroy(ev.packet); end;
906 end;
907 ProcessLoading();
908 if g_Net_UserRequestExit() then
909 begin
910 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' user abort', True);
911 Result := 1;
912 exit;
913 end;
914 until false;
915 finally
916 if (freePacket) then enet_packet_destroy(ev.packet);
917 end;
918 end;
921 // send file request to server, and wait for server reply
922 //
923 // returns `false` on error or user abort
924 // fills:
925 // diskName (actually, base name)
926 // hash
927 // size
928 // chunkSize
929 // returns:
930 // <0 on error
931 // 0 on success
932 // 1 on user abort
933 // 2 on server abort
934 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
935 function g_Net_RequestResFileInfo (resIndex: LongInt; out tf: TNetFileTransfer): Integer;
936 var
937 ev: ENetEvent;
938 rMsgId: Byte;
939 Ptr: Pointer;
940 msg: TMsg;
941 freePacket: Boolean = false;
942 ct, ett: Int64;
943 status: cint;
944 begin
945 // send request
946 trans_omsg.Clear();
947 trans_omsg.Write(Byte(NTF_CLIENT_FILE_REQUEST));
948 trans_omsg.Write(resIndex);
949 if not ftransSendClientMsg(trans_omsg) then begin result := -1; exit; end;
951 FillChar(ev, SizeOf(ev), 0);
952 Result := -1;
953 try
954 ett := getNewTimeoutEnd();
955 repeat
956 status := enet_host_service(NetHost, @ev, 300);
958 if (status < 0) then
959 begin
960 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True);
961 Result := -1;
962 exit;
963 end;
965 if (status <= 0) then
966 begin
967 // check for timeout
968 ct := GetTimerMS();
969 if (ct >= ett) then
970 begin
971 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' timeout reached', True);
972 Result := -1;
973 exit;
974 end;
975 end
976 else
977 begin
978 // some event
979 case ev.kind of
980 ENET_EVENT_TYPE_RECEIVE:
981 begin
982 freePacket := true;
983 if (ev.channelID <> NET_CHAN_DOWNLOAD_EX) then
984 begin
985 //e_LogWriteln('g_Net_Wait_Event: skip message from non-transfer channel');
986 freePacket := false;
987 g_Net_Client_HandlePacket(ev.packet, g_Net_ClientLightMsgHandler);
988 if (g_Res_received_map_start < 0) then begin result := -666; exit; end;
989 end
990 else
991 begin
992 ett := getNewTimeoutEnd();
993 if (ev.packet.dataLength < 1) then
994 begin
995 e_LogWriteln('g_Net_Wait_Event: invalid server packet (no data)');
996 Result := -1;
997 exit;
998 end;
999 Ptr := ev.packet^.data;
1000 rMsgId := Byte(Ptr^);
1001 e_LogWritefln('received transfer packet with id %d (%u bytes)', [rMsgId, ev.packet^.dataLength]);
1002 if (rMsgId = NTF_SERVER_FILE_INFO) then
1003 begin
1004 if not msg.Init(ev.packet^.data+1, ev.packet^.dataLength-1, True) then exit;
1005 tf.hash := msg.ReadMD5();
1006 tf.size := msg.ReadLongInt();
1007 tf.chunkSize := msg.ReadLongInt();
1008 tf.diskName := ExtractFileName(msg.readString());
1009 if (tf.size < 0) or (tf.chunkSize <> FILE_CHUNK_SIZE) or (length(tf.diskName) = 0) then
1010 begin
1011 e_LogWritefln('g_Net_RequestResFileInfo: invalid file info packet', []);
1012 Result := -1;
1013 exit;
1014 end;
1015 e_LogWritefln('got file info for resource #%d: size=%d; name=%s', [resIndex, tf.size, tf.diskName]);
1016 Result := 0; // success
1017 exit;
1018 end
1019 else if (rMsgId = NTF_SERVER_ABORT) then
1020 begin
1021 e_LogWriteln('g_Net_RequestResFileInfo: server aborted transfer');
1022 Result := 2;
1023 exit;
1024 end
1025 else if (rMsgId = NTF_SERVER_MAP_INFO) then
1026 begin
1027 e_LogWriteln('g_Net_RequestResFileInfo: waiting for map info reply, but got file info reply');
1028 Result := -1;
1029 exit;
1030 end
1031 else
1032 begin
1033 e_LogWriteln('g_Net_RequestResFileInfo: invalid server packet type');
1034 Result := -1;
1035 exit;
1036 end;
1037 end;
1038 end;
1039 ENET_EVENT_TYPE_DISCONNECT:
1040 begin
1041 if (ev.data <= NET_DISC_MAX) then
1042 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' ' + _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + ev.data)], True);
1043 Result := -1;
1044 exit;
1045 end;
1046 else
1047 begin
1048 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' unknown ENet event ' + IntToStr(Ord(ev.kind)), True);
1049 result := -1;
1050 exit;
1051 end;
1052 end;
1053 if (freePacket) then begin freePacket := false; enet_packet_destroy(ev.packet); end;
1054 end;
1055 ProcessLoading();
1056 if g_Net_UserRequestExit() then
1057 begin
1058 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' user abort', True);
1059 Result := 1;
1060 exit;
1061 end;
1062 until false;
1063 finally
1064 if (freePacket) then enet_packet_destroy(ev.packet);
1065 end;
1066 end;
1069 // call this to cancel file transfer requested by `g_Net_RequestResFileInfo()`
1070 function g_Net_AbortResTransfer (var tf: TNetFileTransfer): Boolean;
1071 begin
1072 result := false;
1073 e_LogWritefln('aborting file transfer...', []);
1074 // send request
1075 trans_omsg.Clear();
1076 trans_omsg.Write(Byte(NTF_CLIENT_ABORT));
1077 result := ftransSendClientMsg(trans_omsg);
1078 if result then enet_host_flush(NetHost);
1079 end;
1082 // call this to start file transfer requested by `g_Net_RequestResFileInfo()`
1083 //
1084 // returns `false` on error or user abort
1085 // fills:
1086 // hash
1087 // size
1088 // chunkSize
1089 // returns:
1090 // <0 on error
1091 // 0 on success
1092 // 1 on user abort
1093 // 2 on server abort
1094 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
1095 function g_Net_ReceiveResourceFile (resIndex: LongInt; var tf: TNetFileTransfer; strm: TStream): Integer;
1096 var
1097 ev: ENetEvent;
1098 rMsgId: Byte;
1099 Ptr: Pointer;
1100 msg: TMsg;
1101 freePacket: Boolean = false;
1102 ct, ett: Int64;
1103 status: cint;
1104 nextChunk: Integer = 0;
1105 chunkTotal: Integer;
1106 chunk: Integer;
1107 csize: Integer;
1108 buf: PChar = nil;
1109 resumed: Boolean;
1110 //stx: Int64;
1111 begin
1112 tf.resumed := false;
1113 e_LogWritefln('file `%s`, size=%d (%d)', [tf.diskName, Integer(strm.size), tf.size], TMsgType.Notify);
1114 // check if we should resume downloading
1115 resumed := (strm.size > tf.chunkSize) and (strm.size < tf.size);
1116 // send request
1117 trans_omsg.Clear();
1118 trans_omsg.Write(Byte(NTF_CLIENT_START));
1119 if resumed then chunk := strm.size div tf.chunkSize else chunk := 0;
1120 trans_omsg.Write(LongInt(chunk));
1121 if not ftransSendClientMsg(trans_omsg) then begin result := -1; exit; end;
1123 strm.Seek(chunk*tf.chunkSize, soFromBeginning);
1124 chunkTotal := (tf.size+tf.chunkSize-1) div tf.chunkSize;
1125 e_LogWritefln('receiving file `%s` (%d chunks)', [tf.diskName, chunkTotal], TMsgType.Notify);
1126 g_Game_SetLoadingText('downloading "'+ExtractFileName(tf.diskName)+'"', chunkTotal, False);
1127 tf.resumed := resumed;
1129 if (chunk > 0) then g_Game_StepLoading(chunk);
1130 nextChunk := chunk;
1132 // wait for reply data
1133 FillChar(ev, SizeOf(ev), 0);
1134 Result := -1;
1135 GetMem(buf, tf.chunkSize);
1136 try
1137 ett := getNewTimeoutEnd();
1138 repeat
1139 //stx := -GetTimerMS();
1140 status := enet_host_service(NetHost, @ev, 300);
1142 if (status < 0) then
1143 begin
1144 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True);
1145 Result := -1;
1146 exit;
1147 end;
1149 if (status <= 0) then
1150 begin
1151 // check for timeout
1152 ct := GetTimerMS();
1153 if (ct >= ett) then
1154 begin
1155 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' timeout reached', True);
1156 Result := -1;
1157 exit;
1158 end;
1159 end
1160 else
1161 begin
1162 // some event
1163 case ev.kind of
1164 ENET_EVENT_TYPE_RECEIVE:
1165 begin
1166 freePacket := true;
1167 if (ev.channelID <> NET_CHAN_DOWNLOAD_EX) then
1168 begin
1169 //e_LogWritefln('g_Net_Wait_Event: skip message from non-transfer channel', []);
1170 freePacket := false;
1171 g_Net_Client_HandlePacket(ev.packet, g_Net_ClientLightMsgHandler);
1172 if (g_Res_received_map_start < 0) then begin result := -666; exit; end;
1173 end
1174 else
1175 begin
1176 //stx := stx+GetTimerMS();
1177 //e_LogWritefln('g_Net_ReceiveResourceFile: stx=%d', [Integer(stx)]);
1178 //stx := -GetTimerMS();
1179 ett := getNewTimeoutEnd();
1180 if (ev.packet.dataLength < 1) then
1181 begin
1182 e_LogWritefln('g_Net_ReceiveResourceFile: invalid server packet (no data)', []);
1183 Result := -1;
1184 exit;
1185 end;
1186 Ptr := ev.packet^.data;
1187 rMsgId := Byte(Ptr^);
1188 if (rMsgId = NTF_SERVER_DONE) then
1189 begin
1190 e_LogWritefln('file transfer complete.', []);
1191 result := 0;
1192 exit;
1193 end
1194 else if (rMsgId = NTF_SERVER_CHUNK) then
1195 begin
1196 if not msg.Init(ev.packet^.data+1, ev.packet^.dataLength-1, True) then exit;
1197 chunk := msg.ReadLongInt();
1198 csize := msg.ReadLongInt();
1199 if (chunk <> nextChunk) then
1200 begin
1201 e_LogWritefln('received chunk %d, but expected chunk %d', [chunk, nextChunk]);
1202 Result := -1;
1203 exit;
1204 end;
1205 if (csize < 0) or (csize > tf.chunkSize) then
1206 begin
1207 e_LogWritefln('received chunk with size %d, but expected chunk size is %d', [csize, tf.chunkSize]);
1208 Result := -1;
1209 exit;
1210 end;
1211 //e_LogWritefln('got chunk #%d of #%d (csize=%d)', [chunk, (tf.size+tf.chunkSize-1) div tf.chunkSize, csize]);
1212 msg.ReadData(buf, csize);
1213 strm.WriteBuffer(buf^, csize);
1214 nextChunk := chunk+1;
1215 g_Game_StepLoading();
1216 // send ack
1217 trans_omsg.Clear();
1218 trans_omsg.Write(Byte(NTF_CLIENT_ACK));
1219 trans_omsg.Write(LongInt(chunk));
1220 if not ftransSendClientMsg(trans_omsg) then begin result := -1; exit; end;
1221 end
1222 else if (rMsgId = NTF_SERVER_ABORT) then
1223 begin
1224 e_LogWritefln('g_Net_ReceiveResourceFile: server aborted transfer', []);
1225 Result := 2;
1226 exit;
1227 end
1228 else
1229 begin
1230 e_LogWritefln('g_Net_ReceiveResourceFile: invalid server packet type', []);
1231 Result := -1;
1232 exit;
1233 end;
1234 //stx := stx+GetTimerMS();
1235 //e_LogWritefln('g_Net_ReceiveResourceFile: process stx=%d', [Integer(stx)]);
1236 end;
1237 end;
1238 ENET_EVENT_TYPE_DISCONNECT:
1239 begin
1240 if (ev.data <= NET_DISC_MAX) then
1241 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' ' + _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + ev.data)], True);
1242 Result := -1;
1243 exit;
1244 end;
1245 else
1246 begin
1247 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' unknown ENet event ' + IntToStr(Ord(ev.kind)), True);
1248 result := -1;
1249 exit;
1250 end;
1251 end;
1252 if (freePacket) then begin freePacket := false; enet_packet_destroy(ev.packet); end;
1253 end;
1254 ProcessLoading();
1255 if g_Net_UserRequestExit() then
1256 begin
1257 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' user abort', True);
1258 Result := 1;
1259 exit;
1260 end;
1261 until false;
1262 finally
1263 FreeMem(buf);
1264 if (freePacket) then enet_packet_destroy(ev.packet);
1265 end;
1266 end;
1269 //**************************************************************************
1270 //
1271 // common functions
1272 //
1273 //**************************************************************************
1275 function g_Net_FindSlot(): Integer;
1276 var
1277 I: Integer;
1278 F: Boolean;
1279 N, C: Integer;
1280 begin
1281 N := -1;
1282 F := False;
1283 C := 0;
1284 for I := Low(NetClients) to High(NetClients) do
1285 begin
1286 if NetClients[I].Used then
1287 Inc(C)
1288 else
1289 if not F then
1290 begin
1291 F := True;
1292 N := I;
1293 end;
1294 end;
1295 if C >= NetMaxClients then
1296 begin
1297 Result := -1;
1298 Exit;
1299 end;
1301 if not F then
1302 begin
1303 if (Length(NetClients) >= NetMaxClients) then
1304 N := -1
1305 else
1306 begin
1307 SetLength(NetClients, Length(NetClients) + 1);
1308 N := High(NetClients);
1309 end;
1310 end;
1312 if N >= 0 then
1313 begin
1314 NetClients[N].Used := True;
1315 NetClients[N].ID := N;
1316 NetClients[N].RequestedFullUpdate := False;
1317 NetClients[N].WaitForFirstSpawn := False;
1318 NetClients[N].RCONAuth := False;
1319 NetClients[N].Voted := False;
1320 NetClients[N].Player := 0;
1321 clearNetClientTransfers(NetClients[N]); // just in case
1322 end;
1324 Result := N;
1325 end;
1328 function g_Net_Init(): Boolean;
1329 var
1330 F: TextFile;
1331 IPstr: string;
1332 IP: LongWord;
1333 path: AnsiString;
1334 begin
1335 NetIn.Clear();
1336 NetOut.Clear();
1337 NetBuf[NET_UNRELIABLE].Clear();
1338 NetBuf[NET_RELIABLE].Clear();
1339 //SetLength(NetClients, 0);
1340 clearNetClients(true); // clear array
1341 NetPeer := nil;
1342 NetHost := nil;
1343 NetMyID := -1;
1344 NetPlrUID1 := -1;
1345 NetPlrUID2 := -1;
1346 NetAddr.port := 25666;
1347 SetLength(NetBannedHosts, 0);
1348 path := BANLIST_FILENAME;
1349 if e_FindResource(DataDirs, path) = true then
1350 begin
1351 Assign(F, path);
1352 Reset(F);
1353 while not EOF(F) do
1354 begin
1355 Readln(F, IPstr);
1356 if StrToIp(IPstr, IP) then
1357 g_Net_BanHost(IP);
1358 end;
1359 CloseFile(F);
1360 g_Net_SaveBanList();
1361 end;
1363 //Result := (enet_initialize() = 0);
1364 Result := enet_init_success;
1365 end;
1367 procedure g_Net_Flush();
1368 var
1369 T: Integer;
1370 P: pENetPacket;
1371 F, Chan: enet_uint32;
1372 I: Integer;
1373 begin
1374 F := 0;
1375 Chan := NET_CHAN_GAME;
1377 if NetMode = NET_SERVER then
1378 for T := NET_UNRELIABLE to NET_RELIABLE do
1379 begin
1380 if NetBuf[T].CurSize > 0 then
1381 begin
1382 P := enet_packet_create(NetBuf[T].Data, NetBuf[T].CurSize, F);
1383 if not Assigned(P) then continue;
1384 enet_host_broadcast(NetHost, Chan, P);
1385 NetBuf[T].Clear();
1386 end;
1388 for I := Low(NetClients) to High(NetClients) do
1389 begin
1390 if not NetClients[I].Used then continue;
1391 if NetClients[I].NetOut[T].CurSize <= 0 then continue;
1392 P := enet_packet_create(NetClients[I].NetOut[T].Data, NetClients[I].NetOut[T].CurSize, F);
1393 if not Assigned(P) then continue;
1394 enet_peer_send(NetClients[I].Peer, Chan, P);
1395 NetClients[I].NetOut[T].Clear();
1396 end;
1398 // next and last iteration is always RELIABLE
1399 F := LongWord(ENET_PACKET_FLAG_RELIABLE);
1400 Chan := NET_CHAN_IMPORTANT;
1401 end
1402 else if NetMode = NET_CLIENT then
1403 for T := NET_UNRELIABLE to NET_RELIABLE do
1404 begin
1405 if NetBuf[T].CurSize > 0 then
1406 begin
1407 P := enet_packet_create(NetBuf[T].Data, NetBuf[T].CurSize, F);
1408 if not Assigned(P) then continue;
1409 enet_peer_send(NetPeer, Chan, P);
1410 NetBuf[T].Clear();
1411 end;
1412 // next and last iteration is always RELIABLE
1413 F := LongWord(ENET_PACKET_FLAG_RELIABLE);
1414 Chan := NET_CHAN_IMPORTANT;
1415 end;
1416 end;
1418 procedure g_Net_Cleanup();
1419 begin
1420 NetIn.Clear();
1421 NetOut.Clear();
1422 NetBuf[NET_UNRELIABLE].Clear();
1423 NetBuf[NET_RELIABLE].Clear();
1425 //SetLength(NetClients, 0);
1426 clearNetClients(true); // clear array
1427 NetClientCount := 0;
1429 NetPeer := nil;
1430 NetHost := nil;
1431 g_Net_Slist_ServerClosed();
1432 NetMyID := -1;
1433 NetPlrUID1 := -1;
1434 NetPlrUID2 := -1;
1435 NetState := NET_STATE_NONE;
1437 NetPongSock := ENET_SOCKET_NULL;
1439 NetTimeToMaster := 0;
1440 NetTimeToUpdate := 0;
1441 NetTimeToReliable := 0;
1443 NetMode := NET_NONE;
1445 if NetPortThread <> NilThreadId then
1446 WaitForThreadTerminate(NetPortThread, 66666);
1448 NetPortThread := NilThreadId;
1449 g_Net_UnforwardPorts();
1451 if NetDump then
1452 g_Net_DumpEnd();
1453 end;
1455 procedure g_Net_Free();
1456 begin
1457 g_Net_Cleanup();
1459 //enet_deinitialize();
1460 NetInitDone := False;
1461 end;
1464 //**************************************************************************
1465 //
1466 // SERVER FUNCTIONS
1467 //
1468 //**************************************************************************
1470 function ForwardThread(Param: Pointer): PtrInt;
1471 begin
1472 Result := 0;
1473 if not g_Net_ForwardPorts() then Result := -1;
1474 end;
1476 function g_Net_Host(IPAddr: LongWord; Port: enet_uint16; MaxClients: Cardinal = 16): Boolean;
1477 begin
1478 if NetMode <> NET_NONE then
1479 begin
1480 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_INGAME]);
1481 Result := False;
1482 Exit;
1483 end;
1485 Result := True;
1487 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_HOST], [Port]));
1488 if not NetInitDone then
1489 begin
1490 if (not g_Net_Init()) then
1491 begin
1492 g_Console_Add(_lc[I_NET_MSG_FERROR] + _lc[I_NET_ERR_ENET]);
1493 Result := False;
1494 Exit;
1495 end
1496 else
1497 NetInitDone := True;
1498 end;
1500 NetAddr.host := IPAddr;
1501 NetAddr.port := Port;
1503 if NetForwardPorts then NetPortThread := BeginThread(ForwardThread);
1505 NetHost := enet_host_create(@NetAddr, NET_MAXCLIENTS, NET_CHANS, 0, 0);
1507 if (NetHost = nil) then
1508 begin
1509 g_Console_Add(_lc[I_NET_MSG_ERROR] + Format(_lc[I_NET_ERR_HOST], [Port]));
1510 Result := False;
1511 g_Net_Cleanup;
1512 Exit;
1513 end;
1515 NetPongSock := enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
1516 if NetPongSock <> ENET_SOCKET_NULL then
1517 begin
1518 NetPongAddr.host := IPAddr;
1519 NetPongAddr.port := NET_PING_PORT;
1520 if enet_socket_bind(NetPongSock, @NetPongAddr) < 0 then
1521 begin
1522 enet_socket_destroy(NetPongSock);
1523 NetPongSock := ENET_SOCKET_NULL;
1524 end
1525 else
1526 enet_socket_set_option(NetPongSock, ENET_SOCKOPT_NONBLOCK, 1);
1527 end;
1529 NetMode := NET_SERVER;
1530 NetOut.Clear();
1531 NetBuf[NET_UNRELIABLE].Clear();
1532 NetBuf[NET_RELIABLE].Clear();
1534 if NetDump then
1535 g_Net_DumpStart();
1536 end;
1538 procedure g_Net_Host_Die();
1539 var
1540 I: Integer;
1541 begin
1542 if NetMode <> NET_SERVER then Exit;
1544 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_DISCALL]);
1545 for I := 0 to High(NetClients) do
1546 if NetClients[I].Used then
1547 enet_peer_disconnect(NetClients[I].Peer, NET_DISC_DOWN);
1549 while enet_host_service(NetHost, @NetEvent, 1000) > 0 do
1550 if NetEvent.kind = ENET_EVENT_TYPE_RECEIVE then
1551 enet_packet_destroy(NetEvent.packet);
1553 for I := 0 to High(NetClients) do
1554 if NetClients[I].Used then
1555 begin
1556 FreeMemory(NetClients[I].Peer^.data);
1557 NetClients[I].Peer^.data := nil;
1558 enet_peer_reset(NetClients[I].Peer);
1559 NetClients[I].Peer := nil;
1560 NetClients[I].Used := False;
1561 NetClients[I].NetOut[NET_UNRELIABLE].Free();
1562 NetClients[I].NetOut[NET_RELIABLE].Free();
1563 end;
1565 clearNetClients(false); // don't clear array
1566 g_Net_Slist_ServerClosed();
1567 if NetPongSock <> ENET_SOCKET_NULL then
1568 enet_socket_destroy(NetPongSock);
1570 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_DIE]);
1571 enet_host_destroy(NetHost);
1573 NetMode := NET_NONE;
1575 g_Net_Cleanup;
1576 e_WriteLog('NET: Server stopped', TMsgType.Notify);
1577 end;
1580 procedure g_Net_Host_Send(ID: Integer; Reliable: Boolean; Chan: Byte = NET_CHAN_GAME);
1581 var
1582 T: Integer;
1583 begin
1584 if (Reliable) then
1585 T := NET_RELIABLE
1586 else
1587 T := NET_UNRELIABLE;
1589 if (ID >= 0) then
1590 begin
1591 if ID > High(NetClients) then Exit;
1592 if NetClients[ID].Peer = nil then Exit;
1593 // write size first
1594 NetClients[ID].NetOut[T].Write(Integer(NetOut.CurSize));
1595 NetClients[ID].NetOut[T].Write(NetOut);
1596 end
1597 else
1598 begin
1599 // write size first
1600 NetBuf[T].Write(Integer(NetOut.CurSize));
1601 NetBuf[T].Write(NetOut);
1602 end;
1604 if NetDump then g_Net_DumpSendBuffer();
1605 NetOut.Clear();
1606 end;
1608 procedure g_Net_Host_CheckPings();
1609 var
1610 ClAddr: ENetAddress;
1611 Buf: ENetBuffer;
1612 Len: Integer;
1613 ClTime: Int64;
1614 Ping: array [0..9] of Byte;
1615 NPl: Byte;
1616 begin
1617 if NetPongSock = ENET_SOCKET_NULL then Exit;
1619 Buf.data := Addr(Ping[0]);
1620 Buf.dataLength := 2+8;
1622 Ping[0] := 0;
1624 Len := enet_socket_receive(NetPongSock, @ClAddr, @Buf, 1);
1625 if Len < 0 then Exit;
1627 if (Ping[0] = Ord('D')) and (Ping[1] = Ord('F')) then
1628 begin
1629 ClTime := Int64(Addr(Ping[2])^);
1631 NetOut.Clear();
1632 NetOut.Write(Byte(Ord('D')));
1633 NetOut.Write(Byte(Ord('F')));
1634 NetOut.Write(NetPort);
1635 NetOut.Write(ClTime);
1636 TMasterHost.writeInfo(NetOut);
1637 NPl := 0;
1638 if gPlayer1 <> nil then Inc(NPl);
1639 if gPlayer2 <> nil then Inc(NPl);
1640 NetOut.Write(NPl);
1641 NetOut.Write(gNumBots);
1643 Buf.data := NetOut.Data;
1644 Buf.dataLength := NetOut.CurSize;
1645 enet_socket_send(NetPongSock, @ClAddr, @Buf, 1);
1647 NetOut.Clear();
1648 end;
1649 end;
1652 function g_Net_Host_Update(): enet_size_t;
1653 var
1654 IP: string;
1655 Port: Word;
1656 ID: Integer;
1657 TC: pTNetClient;
1658 TP: TPlayer;
1659 begin
1660 IP := '';
1661 Result := 0;
1663 if NetUseMaster then g_Net_Slist_Pulse();
1664 g_Net_Host_CheckPings();
1666 while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
1667 begin
1668 case (NetEvent.kind) of
1669 ENET_EVENT_TYPE_CONNECT:
1670 begin
1671 IP := IpToStr(NetEvent.Peer^.address.host);
1672 Port := NetEvent.Peer^.address.port;
1673 g_Console_Add(_lc[I_NET_MSG] +
1674 Format(_lc[I_NET_MSG_HOST_CONN], [IP, Port]));
1676 if (NetEvent.data <> NET_PROTOCOL_VER) then
1677 begin
1678 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_REJECT] +
1679 _lc[I_NET_DISC_PROTOCOL]);
1680 NetEvent.peer^.data := GetMemory(SizeOf(Byte));
1681 Byte(NetEvent.peer^.data^) := 255;
1682 enet_peer_disconnect(NetEvent.peer, NET_DISC_PROTOCOL);
1683 enet_host_flush(NetHost);
1684 Exit;
1685 end;
1687 ID := g_Net_FindSlot();
1689 if ID < 0 then
1690 begin
1691 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_REJECT] +
1692 _lc[I_NET_DISC_FULL]);
1693 NetEvent.Peer^.data := GetMemory(SizeOf(Byte));
1694 Byte(NetEvent.peer^.data^) := 255;
1695 enet_peer_disconnect(NetEvent.peer, NET_DISC_FULL);
1696 enet_host_flush(NetHost);
1697 Exit;
1698 end;
1700 NetClients[ID].Peer := NetEvent.peer;
1701 NetClients[ID].Peer^.data := GetMemory(SizeOf(Byte));
1702 Byte(NetClients[ID].Peer^.data^) := ID;
1703 NetClients[ID].State := NET_STATE_AUTH;
1704 NetClients[ID].RCONAuth := False;
1705 NetClients[ID].NetOut[NET_UNRELIABLE].Alloc(NET_BUFSIZE*2);
1706 NetClients[ID].NetOut[NET_RELIABLE].Alloc(NET_BUFSIZE*2);
1707 clearNetClientTransfers(NetClients[ID]); // just in case
1709 enet_peer_timeout(NetEvent.peer, ENET_PEER_TIMEOUT_LIMIT * 2, ENET_PEER_TIMEOUT_MINIMUM * 2, ENET_PEER_TIMEOUT_MAXIMUM * 2);
1711 Inc(NetClientCount);
1712 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_HOST_ADD], [ID]));
1713 end;
1715 ENET_EVENT_TYPE_RECEIVE:
1716 begin
1717 //e_LogWritefln('RECEIVE: chan=%u', [NetEvent.channelID]);
1718 if (NetEvent.channelID = NET_CHAN_DOWNLOAD_EX) then
1719 begin
1720 ProcessDownloadExPacket();
1721 end
1722 else
1723 begin
1724 ID := Byte(NetEvent.peer^.data^);
1725 if ID > High(NetClients) then Exit;
1726 TC := @NetClients[ID];
1728 if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
1729 g_Net_Host_HandlePacket(TC, NetEvent.packet, g_Net_HostMsgHandler);
1730 end;
1731 end;
1733 ENET_EVENT_TYPE_DISCONNECT:
1734 begin
1735 ID := Byte(NetEvent.peer^.data^);
1736 if ID > High(NetClients) then Exit;
1737 clearNetClient(NetClients[ID]);
1738 TC := @NetClients[ID];
1739 if TC = nil then Exit;
1741 if not (TC^.Used) then Exit;
1743 TP := g_Player_Get(TC^.Player);
1745 if TP <> nil then
1746 begin
1747 TP.Lives := 0;
1748 TP.Kill(K_SIMPLEKILL, 0, HIT_DISCON);
1749 g_Console_Add(Format(_lc[I_PLAYER_LEAVE], [TP.Name]), True);
1750 e_WriteLog('NET: Client ' + TP.Name + ' [' + IntToStr(ID) + '] disconnected.', TMsgType.Notify);
1751 g_Player_Remove(TP.UID);
1752 end;
1754 TC^.Used := False;
1755 TC^.State := NET_STATE_NONE;
1756 TC^.Peer := nil;
1757 TC^.Player := 0;
1758 TC^.RequestedFullUpdate := False;
1759 TC^.WaitForFirstSpawn := False;
1760 TC^.NetOut[NET_UNRELIABLE].Free();
1761 TC^.NetOut[NET_RELIABLE].Free();
1763 FreeMemory(NetEvent.peer^.data);
1764 NetEvent.peer^.data := nil;
1765 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_HOST_DISC], [ID]));
1766 Dec(NetClientCount);
1768 if NetUseMaster then g_Net_Slist_ServerPlayerLeaves();
1769 end;
1770 end;
1771 end;
1772 end;
1775 //**************************************************************************
1776 //
1777 // CLIENT FUNCTIONS
1778 //
1779 //**************************************************************************
1781 procedure g_Net_Disconnect(Forced: Boolean = False);
1782 begin
1783 if NetMode <> NET_CLIENT then Exit;
1784 if (NetHost = nil) or (NetPeer = nil) then Exit;
1786 if not Forced then
1787 begin
1788 enet_peer_disconnect(NetPeer, NET_DISC_NONE);
1790 while (enet_host_service(NetHost, @NetEvent, 1500) > 0) do
1791 begin
1792 if (NetEvent.kind = ENET_EVENT_TYPE_DISCONNECT) then
1793 begin
1794 NetPeer := nil;
1795 break;
1796 end;
1798 if (NetEvent.kind = ENET_EVENT_TYPE_RECEIVE) then
1799 enet_packet_destroy(NetEvent.packet);
1800 end;
1802 if NetPeer <> nil then
1803 begin
1804 enet_peer_reset(NetPeer);
1805 NetPeer := nil;
1806 end;
1807 end
1808 else
1809 begin
1810 e_WriteLog('NET: Kicked from server: ' + IntToStr(NetEvent.data), TMsgType.Notify);
1811 if (NetEvent.data <= NET_DISC_MAX) then
1812 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_KICK] +
1813 _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + NetEvent.data)], True);
1814 end;
1816 if NetHost <> nil then
1817 begin
1818 enet_host_destroy(NetHost);
1819 NetHost := nil;
1820 end;
1821 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_CLIENT_DISC]);
1823 g_Net_Cleanup;
1824 e_WriteLog('NET: Disconnected', TMsgType.Notify);
1825 end;
1827 procedure g_Net_Client_Send(Reliable: Boolean; Chan: Byte = NET_CHAN_GAME);
1828 var
1829 T: Integer;
1830 begin
1831 if (Reliable) then
1832 T := NET_RELIABLE
1833 else
1834 T := NET_UNRELIABLE;
1836 // write size first
1837 NetBuf[T].Write(Integer(NetOut.CurSize));
1838 NetBuf[T].Write(NetOut);
1840 if NetDump then g_Net_DumpSendBuffer();
1841 NetOut.Clear();
1842 g_Net_Flush(); // FIXME: for now, send immediately
1843 end;
1845 function g_Net_Client_Update(): enet_size_t;
1846 begin
1847 Result := 0;
1848 while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
1849 begin
1850 case NetEvent.kind of
1851 ENET_EVENT_TYPE_RECEIVE:
1852 begin
1853 if (NetEvent.channelID = NET_CHAN_DOWNLOAD_EX) then continue; // ignore all download packets, they're processed by separate code
1854 if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
1855 g_Net_Client_HandlePacket(NetEvent.packet, g_Net_ClientMsgHandler);
1856 end;
1858 ENET_EVENT_TYPE_DISCONNECT:
1859 begin
1860 g_Net_Disconnect(True);
1861 Result := 1;
1862 Exit;
1863 end;
1864 end;
1865 end
1866 end;
1868 function g_Net_Client_UpdateWhileLoading(): enet_size_t;
1869 begin
1870 Result := 0;
1871 while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
1872 begin
1873 case NetEvent.kind of
1874 ENET_EVENT_TYPE_RECEIVE:
1875 begin
1876 if (NetEvent.channelID = NET_CHAN_DOWNLOAD_EX) then continue; // ignore all download packets, they're processed by separate code
1877 if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
1878 g_Net_Client_HandlePacket(NetEvent.packet, g_Net_ClientLightMsgHandler);
1879 end;
1881 ENET_EVENT_TYPE_DISCONNECT:
1882 begin
1883 g_Net_Disconnect(True);
1884 Result := 1;
1885 Exit;
1886 end;
1887 end;
1888 end;
1889 g_Net_Flush();
1890 end;
1892 function g_Net_Connect(IP: string; Port: enet_uint16): Boolean;
1893 var
1894 OuterLoop: Boolean;
1895 TimeoutTime, T: Int64;
1896 begin
1897 if NetMode <> NET_NONE then
1898 begin
1899 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_ERR_INGAME], True);
1900 Result := False;
1901 Exit;
1902 end;
1904 Result := True;
1906 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_CLIENT_CONN],
1907 [IP, Port]));
1908 if not NetInitDone then
1909 begin
1910 if (not g_Net_Init()) then
1911 begin
1912 g_Console_Add(_lc[I_NET_MSG_FERROR] + _lc[I_NET_ERR_ENET], True);
1913 Result := False;
1914 Exit;
1915 end
1916 else
1917 NetInitDone := True;
1918 end;
1920 NetHost := enet_host_create(nil, 1, NET_CHANS, 0, 0);
1922 if (NetHost = nil) then
1923 begin
1924 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CLIENT], True);
1925 g_Net_Cleanup;
1926 Result := False;
1927 Exit;
1928 end;
1930 enet_address_set_host(@NetAddr, PChar(Addr(IP[1])));
1931 NetAddr.port := Port;
1933 NetPeer := enet_host_connect(NetHost, @NetAddr, NET_CHANS, NET_PROTOCOL_VER);
1935 if (NetPeer = nil) then
1936 begin
1937 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CLIENT], True);
1938 enet_host_destroy(NetHost);
1939 g_Net_Cleanup;
1940 Result := False;
1941 Exit;
1942 end;
1944 // предупредить что ждем слишком долго через N секунд
1945 TimeoutTime := sys_GetTicks() + NET_CONNECT_TIMEOUT;
1947 OuterLoop := True;
1948 while OuterLoop do
1949 begin
1950 while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
1951 begin
1952 if (NetEvent.kind = ENET_EVENT_TYPE_CONNECT) then
1953 begin
1954 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_CLIENT_DONE]);
1955 NetMode := NET_CLIENT;
1956 NetOut.Clear();
1957 enet_peer_timeout(NetPeer, ENET_PEER_TIMEOUT_LIMIT * 2, ENET_PEER_TIMEOUT_MINIMUM * 2, ENET_PEER_TIMEOUT_MAXIMUM * 2);
1958 NetClientIP := IP;
1959 NetClientPort := Port;
1960 if NetDump then
1961 g_Net_DumpStart();
1962 Exit;
1963 end;
1964 end;
1966 T := sys_GetTicks();
1967 if T > TimeoutTime then
1968 begin
1969 TimeoutTime := T + NET_CONNECT_TIMEOUT * 100; // одного предупреждения хватит
1970 g_Console_Add(_lc[I_NET_MSG_TIMEOUT_WARN], True);
1971 g_Console_Add(Format(_lc[I_NET_MSG_PORTS], [Integer(Port), Integer(NET_PING_PORT)]), True);
1972 end;
1974 ProcessLoading(true);
1976 if e_KeyPressed(IK_SPACE) or e_KeyPressed(IK_ESCAPE) or e_KeyPressed(VK_ESCAPE) or
1977 e_KeyPressed(JOY0_JUMP) or e_KeyPressed(JOY1_JUMP) or e_KeyPressed(JOY2_JUMP) or e_KeyPressed(JOY3_JUMP) then
1978 OuterLoop := False;
1979 end;
1981 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_TIMEOUT], True);
1982 g_Console_Add(Format(_lc[I_NET_MSG_PORTS], [Integer(Port), Integer(NET_PING_PORT)]), True);
1983 if NetPeer <> nil then enet_peer_reset(NetPeer);
1984 if NetHost <> nil then
1985 begin
1986 enet_host_destroy(NetHost);
1987 NetHost := nil;
1988 end;
1989 g_Net_Cleanup();
1990 Result := False;
1991 end;
1993 function IpToStr(IP: LongWord): string;
1994 var
1995 Ptr: Pointer;
1996 begin
1997 Ptr := Addr(IP);
1998 Result := IntToStr(PByte(Ptr + 0)^) + '.';
1999 Result := Result + IntToStr(PByte(Ptr + 1)^) + '.';
2000 Result := Result + IntToStr(PByte(Ptr + 2)^) + '.';
2001 Result := Result + IntToStr(PByte(Ptr + 3)^);
2002 end;
2004 function StrToIp(IPstr: string; var IP: LongWord): Boolean;
2005 var
2006 EAddr: ENetAddress;
2007 begin
2008 Result := enet_address_set_host(@EAddr, PChar(@IPstr[1])) = 0;
2009 IP := EAddr.host;
2010 end;
2012 function g_Net_Client_ByName(Name: string): pTNetClient;
2013 var
2014 a: Integer;
2015 pl: TPlayer;
2016 begin
2017 Result := nil;
2018 for a := Low(NetClients) to High(NetClients) do
2019 if (NetClients[a].Used) and (NetClients[a].State = NET_STATE_GAME) then
2020 begin
2021 pl := g_Player_Get(NetClients[a].Player);
2022 if pl = nil then continue;
2023 if Copy(LowerCase(pl.Name), 1, Length(Name)) <> LowerCase(Name) then continue;
2024 if NetClients[a].Peer <> nil then
2025 begin
2026 Result := @NetClients[a];
2027 Exit;
2028 end;
2029 end;
2030 end;
2032 function g_Net_Client_ByPlayer(PID: Word): pTNetClient;
2033 var
2034 a: Integer;
2035 begin
2036 Result := nil;
2037 for a := Low(NetClients) to High(NetClients) do
2038 if (NetClients[a].Used) and (NetClients[a].State = NET_STATE_GAME) then
2039 if NetClients[a].Player = PID then
2040 begin
2041 Result := @NetClients[a];
2042 Exit;
2043 end;
2044 end;
2046 function g_Net_ClientName_ByID(ID: Integer): string;
2047 var
2048 a: Integer;
2049 pl: TPlayer;
2050 begin
2051 Result := '';
2052 if ID = NET_EVERYONE then
2053 Exit;
2054 for a := Low(NetClients) to High(NetClients) do
2055 if (NetClients[a].ID = ID) and (NetClients[a].Used) and (NetClients[a].State = NET_STATE_GAME) then
2056 begin
2057 pl := g_Player_Get(NetClients[a].Player);
2058 if pl = nil then Exit;
2059 Result := pl.Name;
2060 end;
2061 end;
2063 procedure g_Net_SendData(Data: AByte; peer: pENetPeer; Reliable: Boolean; Chan: Byte = NET_CHAN_DOWNLOAD);
2064 var
2065 P: pENetPacket;
2066 F: enet_uint32;
2067 dataLength: Cardinal;
2068 begin
2069 dataLength := Length(Data);
2071 if (Reliable) then
2072 F := LongWord(ENET_PACKET_FLAG_RELIABLE)
2073 else
2074 F := 0;
2076 if (peer <> nil) then
2077 begin
2078 P := enet_packet_create(@Data[0], dataLength, F);
2079 if not Assigned(P) then Exit;
2080 enet_peer_send(peer, Chan, P);
2081 end
2082 else
2083 begin
2084 P := enet_packet_create(@Data[0], dataLength, F);
2085 if not Assigned(P) then Exit;
2086 enet_host_broadcast(NetHost, Chan, P);
2087 end;
2089 enet_host_flush(NetHost);
2090 end;
2092 function g_Net_IsHostBanned(IP: LongWord; Perm: Boolean = False): Boolean;
2093 var
2094 I: Integer;
2095 begin
2096 Result := False;
2097 if NetBannedHosts = nil then
2098 Exit;
2099 for I := 0 to High(NetBannedHosts) do
2100 if (NetBannedHosts[I].IP = IP) and ((not Perm) or (NetBannedHosts[I].Perm)) then
2101 begin
2102 Result := True;
2103 break;
2104 end;
2105 end;
2107 procedure g_Net_BanHost(IP: LongWord; Perm: Boolean = True); overload;
2108 var
2109 I, P: Integer;
2110 begin
2111 if IP = 0 then
2112 Exit;
2113 if g_Net_IsHostBanned(IP, Perm) then
2114 Exit;
2116 P := -1;
2117 for I := Low(NetBannedHosts) to High(NetBannedHosts) do
2118 if NetBannedHosts[I].IP = 0 then
2119 begin
2120 P := I;
2121 break;
2122 end;
2124 if P < 0 then
2125 begin
2126 SetLength(NetBannedHosts, Length(NetBannedHosts) + 1);
2127 P := High(NetBannedHosts);
2128 end;
2130 NetBannedHosts[P].IP := IP;
2131 NetBannedHosts[P].Perm := Perm;
2132 end;
2134 procedure g_Net_BanHost(IP: string; Perm: Boolean = True); overload;
2135 var
2136 a: LongWord;
2137 b: Boolean;
2138 begin
2139 b := StrToIp(IP, a);
2140 if b then
2141 g_Net_BanHost(a, Perm);
2142 end;
2144 procedure g_Net_UnbanNonPermHosts();
2145 var
2146 I: Integer;
2147 begin
2148 if NetBannedHosts = nil then
2149 Exit;
2150 for I := Low(NetBannedHosts) to High(NetBannedHosts) do
2151 if (NetBannedHosts[I].IP > 0) and not NetBannedHosts[I].Perm then
2152 begin
2153 NetBannedHosts[I].IP := 0;
2154 NetBannedHosts[I].Perm := True;
2155 end;
2156 end;
2158 function g_Net_UnbanHost(IP: string): Boolean; overload;
2159 var
2160 a: LongWord;
2161 begin
2162 Result := StrToIp(IP, a);
2163 if Result then
2164 Result := g_Net_UnbanHost(a);
2165 end;
2167 function g_Net_UnbanHost(IP: LongWord): Boolean; overload;
2168 var
2169 I: Integer;
2170 begin
2171 Result := False;
2172 if IP = 0 then
2173 Exit;
2174 if NetBannedHosts = nil then
2175 Exit;
2176 for I := 0 to High(NetBannedHosts) do
2177 if NetBannedHosts[I].IP = IP then
2178 begin
2179 NetBannedHosts[I].IP := 0;
2180 NetBannedHosts[I].Perm := True;
2181 Result := True;
2182 // no break here to clear all bans of this host, perm and non-perm
2183 end;
2184 end;
2186 procedure g_Net_SaveBanList();
2187 var
2188 F: TextFile;
2189 I: Integer;
2190 path: AnsiString;
2191 begin
2192 path := e_GetWriteableDir(DataDirs);
2193 if path <> '' then
2194 begin
2195 path := e_CatPath(path, BANLIST_FILENAME);
2196 Assign(F, path);
2197 Rewrite(F);
2198 if NetBannedHosts <> nil then
2199 for I := 0 to High(NetBannedHosts) do
2200 if NetBannedHosts[I].Perm and (NetBannedHosts[I].IP > 0) then
2201 Writeln(F, IpToStr(NetBannedHosts[I].IP));
2202 CloseFile(F)
2203 end
2204 end;
2206 procedure g_Net_DumpStart();
2207 begin
2208 if NetMode = NET_SERVER then
2209 NetDumpFile := e_CreateResource(LogDirs, NETDUMP_FILENAME + '_server')
2210 else
2211 NetDumpFile := e_CreateResource(LogDirs, NETDUMP_FILENAME + '_client');
2212 end;
2214 procedure g_Net_DumpSendBuffer();
2215 begin
2216 writeInt(NetDumpFile, gTime);
2217 writeInt(NetDumpFile, LongWord(NetOut.CurSize));
2218 writeInt(NetDumpFile, Byte(1));
2219 NetDumpFile.WriteBuffer(NetOut.Data^, NetOut.CurSize);
2220 end;
2222 procedure g_Net_DumpRecvBuffer(Buf: penet_uint8; Len: LongWord);
2223 begin
2224 if (Buf = nil) or (Len = 0) then Exit;
2225 writeInt(NetDumpFile, gTime);
2226 writeInt(NetDumpFile, Len);
2227 writeInt(NetDumpFile, Byte(0));
2228 NetDumpFile.WriteBuffer(Buf^, Len);
2229 end;
2231 procedure g_Net_DumpEnd();
2232 begin
2233 NetDumpFile.Free();
2234 NetDumpFile := nil;
2235 end;
2237 function g_Net_ForwardPorts(ForwardPongPort: Boolean = True): Boolean;
2238 {$IFDEF USE_MINIUPNPC}
2239 var
2240 DevList: PUPNPDev;
2241 Urls: TUPNPUrls;
2242 Data: TIGDDatas;
2243 LanAddr: array [0..255] of Char;
2244 StrPort: AnsiString;
2245 Err, I: Integer;
2246 begin
2247 Result := False;
2249 if NetPortForwarded = NetPort then
2250 begin
2251 Result := True;
2252 exit;
2253 end;
2255 NetPongForwarded := False;
2256 NetPortForwarded := 0;
2258 DevList := upnpDiscover(1000, nil, nil, 0, 0, 2, Addr(Err));
2259 if DevList = nil then
2260 begin
2261 conwritefln('port forwarding failed: upnpDiscover() failed: %d', [Err]);
2262 exit;
2263 end;
2265 I := UPNP_GetValidIGD(DevList, @Urls, @Data, Addr(LanAddr[0]), 256);
2267 if I = 0 then
2268 begin
2269 conwriteln('port forwarding failed: could not find an IGD device on this LAN');
2270 FreeUPNPDevList(DevList);
2271 FreeUPNPUrls(@Urls);
2272 exit;
2273 end;
2275 StrPort := IntToStr(NetPort);
2276 I := UPNP_AddPortMapping(
2277 Urls.controlURL, Addr(data.first.servicetype[1]),
2278 PChar(StrPort), PChar(StrPort), Addr(LanAddr[0]), PChar('D2DF'),
2279 PChar('UDP'), nil, PChar('0')
2280 );
2282 if I <> 0 then
2283 begin
2284 conwritefln('forwarding port %d failed: error %d', [NetPort, I]);
2285 FreeUPNPDevList(DevList);
2286 FreeUPNPUrls(@Urls);
2287 exit;
2288 end;
2290 if ForwardPongPort then
2291 begin
2292 StrPort := IntToStr(NET_PING_PORT);
2293 I := UPNP_AddPortMapping(
2294 Urls.controlURL, Addr(data.first.servicetype[1]),
2295 PChar(StrPort), PChar(StrPort), Addr(LanAddr[0]), PChar('D2DF'),
2296 PChar('UDP'), nil, PChar('0')
2297 );
2299 if I <> 0 then
2300 begin
2301 conwritefln('forwarding port %d failed: error %d', [NetPort + 1, I]);
2302 NetPongForwarded := False;
2303 end
2304 else
2305 begin
2306 conwritefln('forwarded port %d successfully', [NetPort + 1]);
2307 NetPongForwarded := True;
2308 end;
2309 end;
2311 conwritefln('forwarded port %d successfully', [NetPort]);
2312 NetIGDControl := AnsiString(Urls.controlURL);
2313 NetIGDService := data.first.servicetype;
2314 NetPortForwarded := NetPort;
2316 FreeUPNPDevList(DevList);
2317 FreeUPNPUrls(@Urls);
2318 Result := True;
2319 end;
2320 {$ELSE}
2321 begin
2322 Result := False;
2323 end;
2324 {$ENDIF}
2326 procedure g_Net_UnforwardPorts();
2327 {$IFDEF USE_MINIUPNPC}
2328 var
2329 I: Integer;
2330 StrPort: AnsiString;
2331 begin
2332 if NetPortForwarded = 0 then Exit;
2334 conwriteln('unforwarding ports...');
2336 StrPort := IntToStr(NetPortForwarded);
2337 I := UPNP_DeletePortMapping(
2338 PChar(NetIGDControl), Addr(NetIGDService[1]),
2339 PChar(StrPort), PChar('UDP'), nil
2340 );
2341 conwritefln(' port %d: %d', [NetPortForwarded, I]);
2343 if NetPongForwarded then
2344 begin
2345 NetPongForwarded := False;
2346 StrPort := IntToStr(NetPortForwarded + 1);
2347 I := UPNP_DeletePortMapping(
2348 PChar(NetIGDControl), Addr(NetIGDService[1]),
2349 PChar(StrPort), PChar('UDP'), nil
2350 );
2351 conwritefln(' port %d: %d', [NetPortForwarded + 1, I]);
2352 end;
2354 NetPortForwarded := 0;
2355 end;
2356 {$ELSE}
2357 begin
2358 end;
2359 {$ENDIF}
2362 initialization
2363 conRegVar('cl_downloadtimeout', @g_Net_DownloadTimeout, 0.0, 1000000.0, '', 'timeout in seconds, 0 to disable it');
2364 SetLength(NetClients, 0);
2365 g_Net_DownloadTimeout := 60;
2366 NetIn.Alloc(NET_BUFSIZE);
2367 NetOut.Alloc(NET_BUFSIZE);
2368 NetBuf[NET_UNRELIABLE].Alloc(NET_BUFSIZE*2);
2369 NetBuf[NET_RELIABLE].Alloc(NET_BUFSIZE*2);
2370 trans_omsg.Alloc(NET_BUFSIZE);
2371 finalization
2372 NetIn.Free();
2373 NetOut.Free();
2374 NetBuf[NET_UNRELIABLE].Free();
2375 NetBuf[NET_RELIABLE].Free();
2376 end.