DEADSOFTWARE

net: game: other: hash database and resource downloader converted to new dirsys
[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
544 begin
545 e_LogWritefln('NETWORK: file "%s" not found!', [fname], TMsgType.Fatal);
546 killClientByFT(nc^);
547 exit;
548 end;
549 // calculate hash
550 //tf.hash := MD5File(tf.diskName);
551 if (ridx < 0) then tf.hash := gWADHash else tf.hash := gExternalResources[ridx].hash;
552 // create file stream
553 tf.diskName := findDiskWad(fname);
554 try
555 tf.stream := openDiskFileRO(tf.diskName);
556 except
557 tf.stream := nil;
558 end;
559 if (tf.stream = nil) then
560 begin
561 e_WriteLog(Format('NETWORK: file "%s" not found!', [fname]), TMsgType.Fatal);
562 killClientByFT(nc^);
563 exit;
564 end;
565 e_LogWritefln('client #%d requested resource #%d (file is `%s` : `%s`)', [nc.ID, ridx, fname, tf.diskName]);
566 tf.size := tf.stream.size;
567 tf.chunkSize := FILE_CHUNK_SIZE; // arbitrary
568 tf.lastSentChunk := -1;
569 tf.lastAckChunk := -1;
570 tf.lastAckTime := GetTimerMS();
571 tf.inProgress := False; // waiting for the first ACK or for the cancel
572 GetMem(tf.diskBuffer, tf.chunkSize);
573 // sent file info message
574 trans_omsg.Clear();
575 trans_omsg.Write(Byte(NTF_SERVER_FILE_INFO));
576 trans_omsg.Write(tf.hash);
577 trans_omsg.Write(tf.size);
578 trans_omsg.Write(tf.chunkSize);
579 trans_omsg.Write(ExtractFileName(fname));
580 if not ftransSendServerMsg(nc^, trans_omsg) then exit;
581 end;
582 NTF_CLIENT_ABORT: // do not send requested file, or abort current transfer
583 begin
584 e_LogWritefln('client #%d aborted file transfer', [nc.ID]);
585 clearNetClientTransfers(nc^);
586 end;
587 NTF_CLIENT_START: // start transfer; client may resume download by sending non-zero starting chunk
588 begin
589 if not Assigned(tf.stream) then
590 begin
591 killClientByFT(nc^);
592 exit;
593 end;
594 if (tf.lastSentChunk <> -1) or (tf.lastAckChunk <> -1) or (tf.inProgress) then
595 begin
596 // double ack, get lost
597 killClientByFT(nc^);
598 exit;
599 end;
600 if (NetEvent.packet^.dataLength < 2) then
601 begin
602 killClientByFT(nc^);
603 exit;
604 end;
605 // build packet
606 if not msg.Init(NetEvent.packet^.data+1, NetEvent.packet^.dataLength-1, True) then
607 begin
608 killClientByFT(nc^);
609 exit;
610 end;
611 chunk := msg.ReadLongInt();
612 if (chunk < 0) or (chunk > (tf.size+tf.chunkSize-1) div tf.chunkSize) then
613 begin
614 killClientByFT(nc^);
615 exit;
616 end;
617 e_LogWritefln('client #%d started file transfer from chunk %d', [nc.ID, chunk]);
618 // start sending chunks
619 tf.inProgress := True;
620 tf.lastSentChunk := chunk-1;
621 tf.lastAckChunk := chunk-1;
622 ProcessChunkSend(nc^);
623 end;
624 NTF_CLIENT_ACK: // chunk ack; chunk number follows
625 begin
626 if not Assigned(tf.stream) then
627 begin
628 killClientByFT(nc^);
629 exit;
630 end;
631 if (tf.lastSentChunk < 0) or (not tf.inProgress) then
632 begin
633 // double ack, get lost
634 killClientByFT(nc^);
635 exit;
636 end;
637 if (NetEvent.packet^.dataLength < 2) then
638 begin
639 killClientByFT(nc^);
640 exit;
641 end;
642 // build packet
643 if not msg.Init(NetEvent.packet^.data+1, NetEvent.packet^.dataLength-1, True) then
644 begin
645 killClientByFT(nc^);
646 exit;
647 end;
648 chunk := msg.ReadLongInt();
649 if (chunk < 0) or (chunk > (tf.size+tf.chunkSize-1) div tf.chunkSize) then
650 begin
651 killClientByFT(nc^);
652 exit;
653 end;
654 // do it this way, so client may seek, or request retransfers for some reason
655 tf.lastAckChunk := chunk;
656 tf.lastSentChunk := chunk;
657 //e_LogWritefln('client #%d acked file transfer chunk %d', [nc.ID, chunk]);
658 ProcessChunkSend(nc^);
659 end;
660 NTF_CLIENT_MAP_REQUEST:
661 begin
662 e_LogWritefln('client #%d requested map info', [nc.ID]);
663 trans_omsg.Clear();
664 dfn := findDiskWad(gGameSettings.WAD);
665 if (dfn = '') then dfn := '!wad_not_found!.wad'; //FIXME
666 //md5 := MD5File(dfn);
667 md5 := gWADHash;
668 if (not GetDiskFileInfo(dfn, fi)) then
669 begin
670 e_LogWritefln('client #%d requested map info, but i cannot get file info', [nc.ID]);
671 killClientByFT(nc^);
672 exit;
673 end;
674 size := fi.size;
676 st := openDiskFileRO(dfn);
677 if not assigned(st) then exit; //wtf?!
678 size := st.size;
679 st.Free;
681 // packet type
682 trans_omsg.Write(Byte(NTF_SERVER_MAP_INFO));
683 // map wad name
684 trans_omsg.Write(gGameSettings.WAD);
685 // map wad md5
686 trans_omsg.Write(md5);
687 // map wad size
688 trans_omsg.Write(size);
689 // number of external resources for map
690 trans_omsg.Write(LongInt(length(gExternalResources)));
691 // external resource names
692 for f := 0 to High(gExternalResources) do
693 begin
694 // old style packet
695 //trans_omsg.Write(ExtractFileName(gExternalResources[f])); // GameDir+'/wads/'+ResList.Strings[i]
696 // new style packet
697 trans_omsg.Write('!');
698 trans_omsg.Write(LongInt(gExternalResources[f].size));
699 trans_omsg.Write(gExternalResources[f].hash);
700 trans_omsg.Write(ExtractFileName(gExternalResources[f].diskName));
701 end;
702 // send packet
703 if not ftransSendServerMsg(nc^, trans_omsg) then exit;
704 end;
705 else
706 begin
707 killClientByFT(nc^);
708 exit;
709 end;
710 end;
711 end;
714 //**************************************************************************
715 //
716 // file transfer crap (both client and server)
717 //
718 //**************************************************************************
720 function getNewTimeoutEnd (): Int64;
721 begin
722 result := GetTimerMS();
723 if (g_Net_DownloadTimeout <= 0) then
724 begin
725 result := result+1000*60*3; // 3 minutes
726 end
727 else
728 begin
729 result := result+trunc(g_Net_DownloadTimeout*1000);
730 end;
731 end;
734 // send map request to server, and wait for "map info" server reply
735 //
736 // returns `false` on error or user abort
737 // fills:
738 // diskName: map wad file name (without a path)
739 // hash: map wad hash
740 // size: map wad size
741 // chunkSize: set too
742 // resList: list of resource wads
743 // returns:
744 // <0 on error
745 // 0 on success
746 // 1 on user abort
747 // 2 on server abort
748 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
749 function g_Net_Wait_MapInfo (var tf: TNetFileTransfer; var resList: TNetMapResourceInfoArray): Integer;
750 var
751 ev: ENetEvent;
752 rMsgId: Byte;
753 Ptr: Pointer;
754 msg: TMsg;
755 freePacket: Boolean = false;
756 ct, ett: Int64;
757 status: cint;
758 s: AnsiString;
759 rc, f: LongInt;
760 ri: ^TNetMapResourceInfo;
761 begin
762 SetLength(resList, 0);
764 // send request
765 trans_omsg.Clear();
766 trans_omsg.Write(Byte(NTF_CLIENT_MAP_REQUEST));
767 if not ftransSendClientMsg(trans_omsg) then begin result := -1; exit; end;
769 FillChar(ev, SizeOf(ev), 0);
770 Result := -1;
771 try
772 ett := getNewTimeoutEnd();
773 repeat
774 status := enet_host_service(NetHost, @ev, 300);
776 if (status < 0) then
777 begin
778 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True);
779 Result := -1;
780 exit;
781 end;
783 if (status <= 0) then
784 begin
785 // check for timeout
786 ct := GetTimerMS();
787 if (ct >= ett) then
788 begin
789 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' timeout reached', True);
790 Result := -1;
791 exit;
792 end;
793 end
794 else
795 begin
796 // some event
797 case ev.kind of
798 ENET_EVENT_TYPE_RECEIVE:
799 begin
800 freePacket := true;
801 if (ev.channelID <> NET_CHAN_DOWNLOAD_EX) then
802 begin
803 //e_LogWritefln('g_Net_Wait_MapInfo: skip message from non-transfer channel', []);
804 freePacket := false;
805 g_Net_Client_HandlePacket(ev.packet, g_Net_ClientLightMsgHandler);
806 if (g_Res_received_map_start < 0) then begin result := -666; exit; end;
807 end
808 else
809 begin
810 ett := getNewTimeoutEnd();
811 if (ev.packet.dataLength < 1) then
812 begin
813 e_LogWritefln('g_Net_Wait_MapInfo: invalid server packet (no data)', []);
814 Result := -1;
815 exit;
816 end;
817 Ptr := ev.packet^.data;
818 rMsgId := Byte(Ptr^);
819 e_LogWritefln('g_Net_Wait_MapInfo: got message %u from server (dataLength=%u)', [rMsgId, ev.packet^.dataLength]);
820 if (rMsgId = NTF_SERVER_FILE_INFO) then
821 begin
822 e_LogWritefln('g_Net_Wait_MapInfo: waiting for map info reply, but got file info reply', []);
823 Result := -1;
824 exit;
825 end
826 else if (rMsgId = NTF_SERVER_ABORT) then
827 begin
828 e_LogWritefln('g_Net_Wait_MapInfo: server aborted transfer', []);
829 Result := 2;
830 exit;
831 end
832 else if (rMsgId = NTF_SERVER_MAP_INFO) then
833 begin
834 e_LogWritefln('g_Net_Wait_MapInfo: creating map info packet...', []);
835 if not msg.Init(ev.packet^.data+1, ev.packet^.dataLength-1, True) then exit;
836 e_LogWritefln('g_Net_Wait_MapInfo: parsing map info packet (rd=%d; max=%d)...', [msg.ReadCount, msg.MaxSize]);
837 SetLength(resList, 0); // just in case
838 // map wad name
839 tf.diskName := msg.ReadString();
840 e_LogWritefln('g_Net_Wait_MapInfo: map wad is `%s`', [tf.diskName]);
841 // map wad md5
842 tf.hash := msg.ReadMD5();
843 // map wad size
844 tf.size := msg.ReadLongInt();
845 e_LogWritefln('g_Net_Wait_MapInfo: map wad size is %d', [tf.size]);
846 // number of external resources for map
847 rc := msg.ReadLongInt();
848 if (rc < 0) or (rc > 1024) then
849 begin
850 e_LogWritefln('g_Net_Wait_Event: invalid number of map external resources (%d)', [rc]);
851 Result := -1;
852 exit;
853 end;
854 e_LogWritefln('g_Net_Wait_MapInfo: map external resource count is %d', [rc]);
855 SetLength(resList, rc);
856 // external resource names
857 for f := 0 to rc-1 do
858 begin
859 ri := @resList[f];
860 s := msg.ReadString();
861 if (length(s) = 0) then begin result := -1; exit; end;
862 if (s = '!') then
863 begin
864 // extended packet
865 ri.size := msg.ReadLongInt();
866 ri.hash := msg.ReadMD5();
867 ri.wadName := ExtractFileName(msg.ReadString());
868 if (length(ri.wadName) = 0) or (ri.size < 0) then begin result := -1; exit; end;
869 end
870 else
871 begin
872 // old-style packet, only name
873 ri.wadName := ExtractFileName(s);
874 if (length(ri.wadName) = 0) then begin result := -1; exit; end;
875 ri.size := -1; // unknown
876 end;
877 end;
878 e_LogWritefln('g_Net_Wait_MapInfo: got map info', []);
879 Result := 0; // success
880 exit;
881 end
882 else
883 begin
884 e_LogWritefln('g_Net_Wait_Event: invalid server packet type', []);
885 Result := -1;
886 exit;
887 end;
888 end;
889 end;
890 ENET_EVENT_TYPE_DISCONNECT:
891 begin
892 if (ev.data <= NET_DISC_MAX) then
893 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' ' + _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + ev.data)], True);
894 Result := -1;
895 exit;
896 end;
897 else
898 begin
899 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' unknown ENet event ' + IntToStr(Ord(ev.kind)), True);
900 result := -1;
901 exit;
902 end;
903 end;
904 if (freePacket) then begin freePacket := false; enet_packet_destroy(ev.packet); end;
905 end;
906 ProcessLoading();
907 if g_Net_UserRequestExit() then
908 begin
909 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' user abort', True);
910 Result := 1;
911 exit;
912 end;
913 until false;
914 finally
915 if (freePacket) then enet_packet_destroy(ev.packet);
916 end;
917 end;
920 // send file request to server, and wait for server reply
921 //
922 // returns `false` on error or user abort
923 // fills:
924 // diskName (actually, base name)
925 // hash
926 // size
927 // chunkSize
928 // returns:
929 // <0 on error
930 // 0 on success
931 // 1 on user abort
932 // 2 on server abort
933 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
934 function g_Net_RequestResFileInfo (resIndex: LongInt; out tf: TNetFileTransfer): Integer;
935 var
936 ev: ENetEvent;
937 rMsgId: Byte;
938 Ptr: Pointer;
939 msg: TMsg;
940 freePacket: Boolean = false;
941 ct, ett: Int64;
942 status: cint;
943 begin
944 // send request
945 trans_omsg.Clear();
946 trans_omsg.Write(Byte(NTF_CLIENT_FILE_REQUEST));
947 trans_omsg.Write(resIndex);
948 if not ftransSendClientMsg(trans_omsg) then begin result := -1; exit; end;
950 FillChar(ev, SizeOf(ev), 0);
951 Result := -1;
952 try
953 ett := getNewTimeoutEnd();
954 repeat
955 status := enet_host_service(NetHost, @ev, 300);
957 if (status < 0) then
958 begin
959 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True);
960 Result := -1;
961 exit;
962 end;
964 if (status <= 0) then
965 begin
966 // check for timeout
967 ct := GetTimerMS();
968 if (ct >= ett) then
969 begin
970 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' timeout reached', True);
971 Result := -1;
972 exit;
973 end;
974 end
975 else
976 begin
977 // some event
978 case ev.kind of
979 ENET_EVENT_TYPE_RECEIVE:
980 begin
981 freePacket := true;
982 if (ev.channelID <> NET_CHAN_DOWNLOAD_EX) then
983 begin
984 //e_LogWriteln('g_Net_Wait_Event: skip message from non-transfer channel');
985 freePacket := false;
986 g_Net_Client_HandlePacket(ev.packet, g_Net_ClientLightMsgHandler);
987 if (g_Res_received_map_start < 0) then begin result := -666; exit; end;
988 end
989 else
990 begin
991 ett := getNewTimeoutEnd();
992 if (ev.packet.dataLength < 1) then
993 begin
994 e_LogWriteln('g_Net_Wait_Event: invalid server packet (no data)');
995 Result := -1;
996 exit;
997 end;
998 Ptr := ev.packet^.data;
999 rMsgId := Byte(Ptr^);
1000 e_LogWritefln('received transfer packet with id %d (%u bytes)', [rMsgId, ev.packet^.dataLength]);
1001 if (rMsgId = NTF_SERVER_FILE_INFO) then
1002 begin
1003 if not msg.Init(ev.packet^.data+1, ev.packet^.dataLength-1, True) then exit;
1004 tf.hash := msg.ReadMD5();
1005 tf.size := msg.ReadLongInt();
1006 tf.chunkSize := msg.ReadLongInt();
1007 tf.diskName := ExtractFileName(msg.readString());
1008 if (tf.size < 0) or (tf.chunkSize <> FILE_CHUNK_SIZE) or (length(tf.diskName) = 0) then
1009 begin
1010 e_LogWritefln('g_Net_RequestResFileInfo: invalid file info packet', []);
1011 Result := -1;
1012 exit;
1013 end;
1014 e_LogWritefln('got file info for resource #%d: size=%d; name=%s', [resIndex, tf.size, tf.diskName]);
1015 Result := 0; // success
1016 exit;
1017 end
1018 else if (rMsgId = NTF_SERVER_ABORT) then
1019 begin
1020 e_LogWriteln('g_Net_RequestResFileInfo: server aborted transfer');
1021 Result := 2;
1022 exit;
1023 end
1024 else if (rMsgId = NTF_SERVER_MAP_INFO) then
1025 begin
1026 e_LogWriteln('g_Net_RequestResFileInfo: waiting for map info reply, but got file info reply');
1027 Result := -1;
1028 exit;
1029 end
1030 else
1031 begin
1032 e_LogWriteln('g_Net_RequestResFileInfo: invalid server packet type');
1033 Result := -1;
1034 exit;
1035 end;
1036 end;
1037 end;
1038 ENET_EVENT_TYPE_DISCONNECT:
1039 begin
1040 if (ev.data <= NET_DISC_MAX) then
1041 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' ' + _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + ev.data)], True);
1042 Result := -1;
1043 exit;
1044 end;
1045 else
1046 begin
1047 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' unknown ENet event ' + IntToStr(Ord(ev.kind)), True);
1048 result := -1;
1049 exit;
1050 end;
1051 end;
1052 if (freePacket) then begin freePacket := false; enet_packet_destroy(ev.packet); end;
1053 end;
1054 ProcessLoading();
1055 if g_Net_UserRequestExit() then
1056 begin
1057 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' user abort', True);
1058 Result := 1;
1059 exit;
1060 end;
1061 until false;
1062 finally
1063 if (freePacket) then enet_packet_destroy(ev.packet);
1064 end;
1065 end;
1068 // call this to cancel file transfer requested by `g_Net_RequestResFileInfo()`
1069 function g_Net_AbortResTransfer (var tf: TNetFileTransfer): Boolean;
1070 begin
1071 result := false;
1072 e_LogWritefln('aborting file transfer...', []);
1073 // send request
1074 trans_omsg.Clear();
1075 trans_omsg.Write(Byte(NTF_CLIENT_ABORT));
1076 result := ftransSendClientMsg(trans_omsg);
1077 if result then enet_host_flush(NetHost);
1078 end;
1081 // call this to start file transfer requested by `g_Net_RequestResFileInfo()`
1082 //
1083 // returns `false` on error or user abort
1084 // fills:
1085 // hash
1086 // size
1087 // chunkSize
1088 // returns:
1089 // <0 on error
1090 // 0 on success
1091 // 1 on user abort
1092 // 2 on server abort
1093 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
1094 function g_Net_ReceiveResourceFile (resIndex: LongInt; var tf: TNetFileTransfer; strm: TStream): Integer;
1095 var
1096 ev: ENetEvent;
1097 rMsgId: Byte;
1098 Ptr: Pointer;
1099 msg: TMsg;
1100 freePacket: Boolean = false;
1101 ct, ett: Int64;
1102 status: cint;
1103 nextChunk: Integer = 0;
1104 chunkTotal: Integer;
1105 chunk: Integer;
1106 csize: Integer;
1107 buf: PChar = nil;
1108 resumed: Boolean;
1109 //stx: Int64;
1110 begin
1111 tf.resumed := false;
1112 e_LogWritefln('file `%s`, size=%d (%d)', [tf.diskName, Integer(strm.size), tf.size], TMsgType.Notify);
1113 // check if we should resume downloading
1114 resumed := (strm.size > tf.chunkSize) and (strm.size < tf.size);
1115 // send request
1116 trans_omsg.Clear();
1117 trans_omsg.Write(Byte(NTF_CLIENT_START));
1118 if resumed then chunk := strm.size div tf.chunkSize else chunk := 0;
1119 trans_omsg.Write(LongInt(chunk));
1120 if not ftransSendClientMsg(trans_omsg) then begin result := -1; exit; end;
1122 strm.Seek(chunk*tf.chunkSize, soFromBeginning);
1123 chunkTotal := (tf.size+tf.chunkSize-1) div tf.chunkSize;
1124 e_LogWritefln('receiving file `%s` (%d chunks)', [tf.diskName, chunkTotal], TMsgType.Notify);
1125 g_Game_SetLoadingText('downloading "'+ExtractFileName(tf.diskName)+'"', chunkTotal, False);
1126 tf.resumed := resumed;
1128 if (chunk > 0) then g_Game_StepLoading(chunk);
1129 nextChunk := chunk;
1131 // wait for reply data
1132 FillChar(ev, SizeOf(ev), 0);
1133 Result := -1;
1134 GetMem(buf, tf.chunkSize);
1135 try
1136 ett := getNewTimeoutEnd();
1137 repeat
1138 //stx := -GetTimerMS();
1139 status := enet_host_service(NetHost, @ev, 300);
1141 if (status < 0) then
1142 begin
1143 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True);
1144 Result := -1;
1145 exit;
1146 end;
1148 if (status <= 0) then
1149 begin
1150 // check for timeout
1151 ct := GetTimerMS();
1152 if (ct >= ett) then
1153 begin
1154 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' timeout reached', True);
1155 Result := -1;
1156 exit;
1157 end;
1158 end
1159 else
1160 begin
1161 // some event
1162 case ev.kind of
1163 ENET_EVENT_TYPE_RECEIVE:
1164 begin
1165 freePacket := true;
1166 if (ev.channelID <> NET_CHAN_DOWNLOAD_EX) then
1167 begin
1168 //e_LogWritefln('g_Net_Wait_Event: skip message from non-transfer channel', []);
1169 freePacket := false;
1170 g_Net_Client_HandlePacket(ev.packet, g_Net_ClientLightMsgHandler);
1171 if (g_Res_received_map_start < 0) then begin result := -666; exit; end;
1172 end
1173 else
1174 begin
1175 //stx := stx+GetTimerMS();
1176 //e_LogWritefln('g_Net_ReceiveResourceFile: stx=%d', [Integer(stx)]);
1177 //stx := -GetTimerMS();
1178 ett := getNewTimeoutEnd();
1179 if (ev.packet.dataLength < 1) then
1180 begin
1181 e_LogWritefln('g_Net_ReceiveResourceFile: invalid server packet (no data)', []);
1182 Result := -1;
1183 exit;
1184 end;
1185 Ptr := ev.packet^.data;
1186 rMsgId := Byte(Ptr^);
1187 if (rMsgId = NTF_SERVER_DONE) then
1188 begin
1189 e_LogWritefln('file transfer complete.', []);
1190 result := 0;
1191 exit;
1192 end
1193 else if (rMsgId = NTF_SERVER_CHUNK) then
1194 begin
1195 if not msg.Init(ev.packet^.data+1, ev.packet^.dataLength-1, True) then exit;
1196 chunk := msg.ReadLongInt();
1197 csize := msg.ReadLongInt();
1198 if (chunk <> nextChunk) then
1199 begin
1200 e_LogWritefln('received chunk %d, but expected chunk %d', [chunk, nextChunk]);
1201 Result := -1;
1202 exit;
1203 end;
1204 if (csize < 0) or (csize > tf.chunkSize) then
1205 begin
1206 e_LogWritefln('received chunk with size %d, but expected chunk size is %d', [csize, tf.chunkSize]);
1207 Result := -1;
1208 exit;
1209 end;
1210 //e_LogWritefln('got chunk #%d of #%d (csize=%d)', [chunk, (tf.size+tf.chunkSize-1) div tf.chunkSize, csize]);
1211 msg.ReadData(buf, csize);
1212 strm.WriteBuffer(buf^, csize);
1213 nextChunk := chunk+1;
1214 g_Game_StepLoading();
1215 // send ack
1216 trans_omsg.Clear();
1217 trans_omsg.Write(Byte(NTF_CLIENT_ACK));
1218 trans_omsg.Write(LongInt(chunk));
1219 if not ftransSendClientMsg(trans_omsg) then begin result := -1; exit; end;
1220 end
1221 else if (rMsgId = NTF_SERVER_ABORT) then
1222 begin
1223 e_LogWritefln('g_Net_ReceiveResourceFile: server aborted transfer', []);
1224 Result := 2;
1225 exit;
1226 end
1227 else
1228 begin
1229 e_LogWritefln('g_Net_ReceiveResourceFile: invalid server packet type', []);
1230 Result := -1;
1231 exit;
1232 end;
1233 //stx := stx+GetTimerMS();
1234 //e_LogWritefln('g_Net_ReceiveResourceFile: process stx=%d', [Integer(stx)]);
1235 end;
1236 end;
1237 ENET_EVENT_TYPE_DISCONNECT:
1238 begin
1239 if (ev.data <= NET_DISC_MAX) then
1240 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' ' + _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + ev.data)], True);
1241 Result := -1;
1242 exit;
1243 end;
1244 else
1245 begin
1246 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' unknown ENet event ' + IntToStr(Ord(ev.kind)), True);
1247 result := -1;
1248 exit;
1249 end;
1250 end;
1251 if (freePacket) then begin freePacket := false; enet_packet_destroy(ev.packet); end;
1252 end;
1253 ProcessLoading();
1254 if g_Net_UserRequestExit() then
1255 begin
1256 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' user abort', True);
1257 Result := 1;
1258 exit;
1259 end;
1260 until false;
1261 finally
1262 FreeMem(buf);
1263 if (freePacket) then enet_packet_destroy(ev.packet);
1264 end;
1265 end;
1268 //**************************************************************************
1269 //
1270 // common functions
1271 //
1272 //**************************************************************************
1274 function g_Net_FindSlot(): Integer;
1275 var
1276 I: Integer;
1277 F: Boolean;
1278 N, C: Integer;
1279 begin
1280 N := -1;
1281 F := False;
1282 C := 0;
1283 for I := Low(NetClients) to High(NetClients) do
1284 begin
1285 if NetClients[I].Used then
1286 Inc(C)
1287 else
1288 if not F then
1289 begin
1290 F := True;
1291 N := I;
1292 end;
1293 end;
1294 if C >= NetMaxClients then
1295 begin
1296 Result := -1;
1297 Exit;
1298 end;
1300 if not F then
1301 begin
1302 if (Length(NetClients) >= NetMaxClients) then
1303 N := -1
1304 else
1305 begin
1306 SetLength(NetClients, Length(NetClients) + 1);
1307 N := High(NetClients);
1308 end;
1309 end;
1311 if N >= 0 then
1312 begin
1313 NetClients[N].Used := True;
1314 NetClients[N].ID := N;
1315 NetClients[N].RequestedFullUpdate := False;
1316 NetClients[N].WaitForFirstSpawn := False;
1317 NetClients[N].RCONAuth := False;
1318 NetClients[N].Voted := False;
1319 NetClients[N].Player := 0;
1320 clearNetClientTransfers(NetClients[N]); // just in case
1321 end;
1323 Result := N;
1324 end;
1327 function g_Net_Init(): Boolean;
1328 var
1329 F: TextFile;
1330 IPstr: string;
1331 IP: LongWord;
1332 path: AnsiString;
1333 begin
1334 NetIn.Clear();
1335 NetOut.Clear();
1336 NetBuf[NET_UNRELIABLE].Clear();
1337 NetBuf[NET_RELIABLE].Clear();
1338 //SetLength(NetClients, 0);
1339 clearNetClients(true); // clear array
1340 NetPeer := nil;
1341 NetHost := nil;
1342 NetMyID := -1;
1343 NetPlrUID1 := -1;
1344 NetPlrUID2 := -1;
1345 NetAddr.port := 25666;
1346 SetLength(NetBannedHosts, 0);
1347 path := BANLIST_FILENAME;
1348 if e_FindResource(DataDirs, path) = true then
1349 begin
1350 Assign(F, path);
1351 Reset(F);
1352 while not EOF(F) do
1353 begin
1354 Readln(F, IPstr);
1355 if StrToIp(IPstr, IP) then
1356 g_Net_BanHost(IP);
1357 end;
1358 CloseFile(F);
1359 g_Net_SaveBanList();
1360 end;
1362 //Result := (enet_initialize() = 0);
1363 Result := enet_init_success;
1364 end;
1366 procedure g_Net_Flush();
1367 var
1368 T: Integer;
1369 P: pENetPacket;
1370 F, Chan: enet_uint32;
1371 I: Integer;
1372 begin
1373 F := 0;
1374 Chan := NET_CHAN_GAME;
1376 if NetMode = NET_SERVER then
1377 for T := NET_UNRELIABLE to NET_RELIABLE do
1378 begin
1379 if NetBuf[T].CurSize > 0 then
1380 begin
1381 P := enet_packet_create(NetBuf[T].Data, NetBuf[T].CurSize, F);
1382 if not Assigned(P) then continue;
1383 enet_host_broadcast(NetHost, Chan, P);
1384 NetBuf[T].Clear();
1385 end;
1387 for I := Low(NetClients) to High(NetClients) do
1388 begin
1389 if not NetClients[I].Used then continue;
1390 if NetClients[I].NetOut[T].CurSize <= 0 then continue;
1391 P := enet_packet_create(NetClients[I].NetOut[T].Data, NetClients[I].NetOut[T].CurSize, F);
1392 if not Assigned(P) then continue;
1393 enet_peer_send(NetClients[I].Peer, Chan, P);
1394 NetClients[I].NetOut[T].Clear();
1395 end;
1397 // next and last iteration is always RELIABLE
1398 F := LongWord(ENET_PACKET_FLAG_RELIABLE);
1399 Chan := NET_CHAN_IMPORTANT;
1400 end
1401 else if NetMode = NET_CLIENT then
1402 for T := NET_UNRELIABLE to NET_RELIABLE do
1403 begin
1404 if NetBuf[T].CurSize > 0 then
1405 begin
1406 P := enet_packet_create(NetBuf[T].Data, NetBuf[T].CurSize, F);
1407 if not Assigned(P) then continue;
1408 enet_peer_send(NetPeer, Chan, P);
1409 NetBuf[T].Clear();
1410 end;
1411 // next and last iteration is always RELIABLE
1412 F := LongWord(ENET_PACKET_FLAG_RELIABLE);
1413 Chan := NET_CHAN_IMPORTANT;
1414 end;
1415 end;
1417 procedure g_Net_Cleanup();
1418 begin
1419 NetIn.Clear();
1420 NetOut.Clear();
1421 NetBuf[NET_UNRELIABLE].Clear();
1422 NetBuf[NET_RELIABLE].Clear();
1424 //SetLength(NetClients, 0);
1425 clearNetClients(true); // clear array
1426 NetClientCount := 0;
1428 NetPeer := nil;
1429 NetHost := nil;
1430 g_Net_Slist_ServerClosed();
1431 NetMyID := -1;
1432 NetPlrUID1 := -1;
1433 NetPlrUID2 := -1;
1434 NetState := NET_STATE_NONE;
1436 NetPongSock := ENET_SOCKET_NULL;
1438 NetTimeToMaster := 0;
1439 NetTimeToUpdate := 0;
1440 NetTimeToReliable := 0;
1442 NetMode := NET_NONE;
1444 if NetPortThread <> NilThreadId then
1445 WaitForThreadTerminate(NetPortThread, 66666);
1447 NetPortThread := NilThreadId;
1448 g_Net_UnforwardPorts();
1450 if NetDump then
1451 g_Net_DumpEnd();
1452 end;
1454 procedure g_Net_Free();
1455 begin
1456 g_Net_Cleanup();
1458 //enet_deinitialize();
1459 NetInitDone := False;
1460 end;
1463 //**************************************************************************
1464 //
1465 // SERVER FUNCTIONS
1466 //
1467 //**************************************************************************
1469 function ForwardThread(Param: Pointer): PtrInt;
1470 begin
1471 Result := 0;
1472 if not g_Net_ForwardPorts() then Result := -1;
1473 end;
1475 function g_Net_Host(IPAddr: LongWord; Port: enet_uint16; MaxClients: Cardinal = 16): Boolean;
1476 begin
1477 if NetMode <> NET_NONE then
1478 begin
1479 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_INGAME]);
1480 Result := False;
1481 Exit;
1482 end;
1484 Result := True;
1486 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_HOST], [Port]));
1487 if not NetInitDone then
1488 begin
1489 if (not g_Net_Init()) then
1490 begin
1491 g_Console_Add(_lc[I_NET_MSG_FERROR] + _lc[I_NET_ERR_ENET]);
1492 Result := False;
1493 Exit;
1494 end
1495 else
1496 NetInitDone := True;
1497 end;
1499 NetAddr.host := IPAddr;
1500 NetAddr.port := Port;
1502 if NetForwardPorts then NetPortThread := BeginThread(ForwardThread);
1504 NetHost := enet_host_create(@NetAddr, NET_MAXCLIENTS, NET_CHANS, 0, 0);
1506 if (NetHost = nil) then
1507 begin
1508 g_Console_Add(_lc[I_NET_MSG_ERROR] + Format(_lc[I_NET_ERR_HOST], [Port]));
1509 Result := False;
1510 g_Net_Cleanup;
1511 Exit;
1512 end;
1514 NetPongSock := enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
1515 if NetPongSock <> ENET_SOCKET_NULL then
1516 begin
1517 NetPongAddr.host := IPAddr;
1518 NetPongAddr.port := NET_PING_PORT;
1519 if enet_socket_bind(NetPongSock, @NetPongAddr) < 0 then
1520 begin
1521 enet_socket_destroy(NetPongSock);
1522 NetPongSock := ENET_SOCKET_NULL;
1523 end
1524 else
1525 enet_socket_set_option(NetPongSock, ENET_SOCKOPT_NONBLOCK, 1);
1526 end;
1528 NetMode := NET_SERVER;
1529 NetOut.Clear();
1530 NetBuf[NET_UNRELIABLE].Clear();
1531 NetBuf[NET_RELIABLE].Clear();
1533 if NetDump then
1534 g_Net_DumpStart();
1535 end;
1537 procedure g_Net_Host_Die();
1538 var
1539 I: Integer;
1540 begin
1541 if NetMode <> NET_SERVER then Exit;
1543 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_DISCALL]);
1544 for I := 0 to High(NetClients) do
1545 if NetClients[I].Used then
1546 enet_peer_disconnect(NetClients[I].Peer, NET_DISC_DOWN);
1548 while enet_host_service(NetHost, @NetEvent, 1000) > 0 do
1549 if NetEvent.kind = ENET_EVENT_TYPE_RECEIVE then
1550 enet_packet_destroy(NetEvent.packet);
1552 for I := 0 to High(NetClients) do
1553 if NetClients[I].Used then
1554 begin
1555 FreeMemory(NetClients[I].Peer^.data);
1556 NetClients[I].Peer^.data := nil;
1557 enet_peer_reset(NetClients[I].Peer);
1558 NetClients[I].Peer := nil;
1559 NetClients[I].Used := False;
1560 NetClients[I].NetOut[NET_UNRELIABLE].Free();
1561 NetClients[I].NetOut[NET_RELIABLE].Free();
1562 end;
1564 clearNetClients(false); // don't clear array
1565 g_Net_Slist_ServerClosed();
1566 if NetPongSock <> ENET_SOCKET_NULL then
1567 enet_socket_destroy(NetPongSock);
1569 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_DIE]);
1570 enet_host_destroy(NetHost);
1572 NetMode := NET_NONE;
1574 g_Net_Cleanup;
1575 e_WriteLog('NET: Server stopped', TMsgType.Notify);
1576 end;
1579 procedure g_Net_Host_Send(ID: Integer; Reliable: Boolean; Chan: Byte = NET_CHAN_GAME);
1580 var
1581 T: Integer;
1582 begin
1583 if (Reliable) then
1584 T := NET_RELIABLE
1585 else
1586 T := NET_UNRELIABLE;
1588 if (ID >= 0) then
1589 begin
1590 if ID > High(NetClients) then Exit;
1591 if NetClients[ID].Peer = nil then Exit;
1592 // write size first
1593 NetClients[ID].NetOut[T].Write(Integer(NetOut.CurSize));
1594 NetClients[ID].NetOut[T].Write(NetOut);
1595 end
1596 else
1597 begin
1598 // write size first
1599 NetBuf[T].Write(Integer(NetOut.CurSize));
1600 NetBuf[T].Write(NetOut);
1601 end;
1603 if NetDump then g_Net_DumpSendBuffer();
1604 NetOut.Clear();
1605 end;
1607 procedure g_Net_Host_CheckPings();
1608 var
1609 ClAddr: ENetAddress;
1610 Buf: ENetBuffer;
1611 Len: Integer;
1612 ClTime: Int64;
1613 Ping: array [0..9] of Byte;
1614 NPl: Byte;
1615 begin
1616 if NetPongSock = ENET_SOCKET_NULL then Exit;
1618 Buf.data := Addr(Ping[0]);
1619 Buf.dataLength := 2+8;
1621 Ping[0] := 0;
1623 Len := enet_socket_receive(NetPongSock, @ClAddr, @Buf, 1);
1624 if Len < 0 then Exit;
1626 if (Ping[0] = Ord('D')) and (Ping[1] = Ord('F')) then
1627 begin
1628 ClTime := Int64(Addr(Ping[2])^);
1630 NetOut.Clear();
1631 NetOut.Write(Byte(Ord('D')));
1632 NetOut.Write(Byte(Ord('F')));
1633 NetOut.Write(NetPort);
1634 NetOut.Write(ClTime);
1635 TMasterHost.writeInfo(NetOut);
1636 NPl := 0;
1637 if gPlayer1 <> nil then Inc(NPl);
1638 if gPlayer2 <> nil then Inc(NPl);
1639 NetOut.Write(NPl);
1640 NetOut.Write(gNumBots);
1642 Buf.data := NetOut.Data;
1643 Buf.dataLength := NetOut.CurSize;
1644 enet_socket_send(NetPongSock, @ClAddr, @Buf, 1);
1646 NetOut.Clear();
1647 end;
1648 end;
1651 function g_Net_Host_Update(): enet_size_t;
1652 var
1653 IP: string;
1654 Port: Word;
1655 ID: Integer;
1656 TC: pTNetClient;
1657 TP: TPlayer;
1658 begin
1659 IP := '';
1660 Result := 0;
1662 if NetUseMaster then g_Net_Slist_Pulse();
1663 g_Net_Host_CheckPings();
1665 while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
1666 begin
1667 case (NetEvent.kind) of
1668 ENET_EVENT_TYPE_CONNECT:
1669 begin
1670 IP := IpToStr(NetEvent.Peer^.address.host);
1671 Port := NetEvent.Peer^.address.port;
1672 g_Console_Add(_lc[I_NET_MSG] +
1673 Format(_lc[I_NET_MSG_HOST_CONN], [IP, Port]));
1675 if (NetEvent.data <> NET_PROTOCOL_VER) then
1676 begin
1677 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_REJECT] +
1678 _lc[I_NET_DISC_PROTOCOL]);
1679 NetEvent.peer^.data := GetMemory(SizeOf(Byte));
1680 Byte(NetEvent.peer^.data^) := 255;
1681 enet_peer_disconnect(NetEvent.peer, NET_DISC_PROTOCOL);
1682 enet_host_flush(NetHost);
1683 Exit;
1684 end;
1686 ID := g_Net_FindSlot();
1688 if ID < 0 then
1689 begin
1690 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_REJECT] +
1691 _lc[I_NET_DISC_FULL]);
1692 NetEvent.Peer^.data := GetMemory(SizeOf(Byte));
1693 Byte(NetEvent.peer^.data^) := 255;
1694 enet_peer_disconnect(NetEvent.peer, NET_DISC_FULL);
1695 enet_host_flush(NetHost);
1696 Exit;
1697 end;
1699 NetClients[ID].Peer := NetEvent.peer;
1700 NetClients[ID].Peer^.data := GetMemory(SizeOf(Byte));
1701 Byte(NetClients[ID].Peer^.data^) := ID;
1702 NetClients[ID].State := NET_STATE_AUTH;
1703 NetClients[ID].RCONAuth := False;
1704 NetClients[ID].NetOut[NET_UNRELIABLE].Alloc(NET_BUFSIZE*2);
1705 NetClients[ID].NetOut[NET_RELIABLE].Alloc(NET_BUFSIZE*2);
1706 clearNetClientTransfers(NetClients[ID]); // just in case
1708 enet_peer_timeout(NetEvent.peer, ENET_PEER_TIMEOUT_LIMIT * 2, ENET_PEER_TIMEOUT_MINIMUM * 2, ENET_PEER_TIMEOUT_MAXIMUM * 2);
1710 Inc(NetClientCount);
1711 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_HOST_ADD], [ID]));
1712 end;
1714 ENET_EVENT_TYPE_RECEIVE:
1715 begin
1716 //e_LogWritefln('RECEIVE: chan=%u', [NetEvent.channelID]);
1717 if (NetEvent.channelID = NET_CHAN_DOWNLOAD_EX) then
1718 begin
1719 ProcessDownloadExPacket();
1720 end
1721 else
1722 begin
1723 ID := Byte(NetEvent.peer^.data^);
1724 if ID > High(NetClients) then Exit;
1725 TC := @NetClients[ID];
1727 if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
1728 g_Net_Host_HandlePacket(TC, NetEvent.packet, g_Net_HostMsgHandler);
1729 end;
1730 end;
1732 ENET_EVENT_TYPE_DISCONNECT:
1733 begin
1734 ID := Byte(NetEvent.peer^.data^);
1735 if ID > High(NetClients) then Exit;
1736 clearNetClient(NetClients[ID]);
1737 TC := @NetClients[ID];
1738 if TC = nil then Exit;
1740 if not (TC^.Used) then Exit;
1742 TP := g_Player_Get(TC^.Player);
1744 if TP <> nil then
1745 begin
1746 TP.Lives := 0;
1747 TP.Kill(K_SIMPLEKILL, 0, HIT_DISCON);
1748 g_Console_Add(Format(_lc[I_PLAYER_LEAVE], [TP.Name]), True);
1749 e_WriteLog('NET: Client ' + TP.Name + ' [' + IntToStr(ID) + '] disconnected.', TMsgType.Notify);
1750 g_Player_Remove(TP.UID);
1751 end;
1753 TC^.Used := False;
1754 TC^.State := NET_STATE_NONE;
1755 TC^.Peer := nil;
1756 TC^.Player := 0;
1757 TC^.RequestedFullUpdate := False;
1758 TC^.WaitForFirstSpawn := False;
1759 TC^.NetOut[NET_UNRELIABLE].Free();
1760 TC^.NetOut[NET_RELIABLE].Free();
1762 FreeMemory(NetEvent.peer^.data);
1763 NetEvent.peer^.data := nil;
1764 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_HOST_DISC], [ID]));
1765 Dec(NetClientCount);
1767 if NetUseMaster then g_Net_Slist_ServerPlayerLeaves();
1768 end;
1769 end;
1770 end;
1771 end;
1774 //**************************************************************************
1775 //
1776 // CLIENT FUNCTIONS
1777 //
1778 //**************************************************************************
1780 procedure g_Net_Disconnect(Forced: Boolean = False);
1781 begin
1782 if NetMode <> NET_CLIENT then Exit;
1783 if (NetHost = nil) or (NetPeer = nil) then Exit;
1785 if not Forced then
1786 begin
1787 enet_peer_disconnect(NetPeer, NET_DISC_NONE);
1789 while (enet_host_service(NetHost, @NetEvent, 1500) > 0) do
1790 begin
1791 if (NetEvent.kind = ENET_EVENT_TYPE_DISCONNECT) then
1792 begin
1793 NetPeer := nil;
1794 break;
1795 end;
1797 if (NetEvent.kind = ENET_EVENT_TYPE_RECEIVE) then
1798 enet_packet_destroy(NetEvent.packet);
1799 end;
1801 if NetPeer <> nil then
1802 begin
1803 enet_peer_reset(NetPeer);
1804 NetPeer := nil;
1805 end;
1806 end
1807 else
1808 begin
1809 e_WriteLog('NET: Kicked from server: ' + IntToStr(NetEvent.data), TMsgType.Notify);
1810 if (NetEvent.data <= NET_DISC_MAX) then
1811 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_KICK] +
1812 _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + NetEvent.data)], True);
1813 end;
1815 if NetHost <> nil then
1816 begin
1817 enet_host_destroy(NetHost);
1818 NetHost := nil;
1819 end;
1820 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_CLIENT_DISC]);
1822 g_Net_Cleanup;
1823 e_WriteLog('NET: Disconnected', TMsgType.Notify);
1824 end;
1826 procedure g_Net_Client_Send(Reliable: Boolean; Chan: Byte = NET_CHAN_GAME);
1827 var
1828 T: Integer;
1829 begin
1830 if (Reliable) then
1831 T := NET_RELIABLE
1832 else
1833 T := NET_UNRELIABLE;
1835 // write size first
1836 NetBuf[T].Write(Integer(NetOut.CurSize));
1837 NetBuf[T].Write(NetOut);
1839 if NetDump then g_Net_DumpSendBuffer();
1840 NetOut.Clear();
1841 g_Net_Flush(); // FIXME: for now, send immediately
1842 end;
1844 function g_Net_Client_Update(): enet_size_t;
1845 begin
1846 Result := 0;
1847 while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
1848 begin
1849 case NetEvent.kind of
1850 ENET_EVENT_TYPE_RECEIVE:
1851 begin
1852 if (NetEvent.channelID = NET_CHAN_DOWNLOAD_EX) then continue; // ignore all download packets, they're processed by separate code
1853 if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
1854 g_Net_Client_HandlePacket(NetEvent.packet, g_Net_ClientMsgHandler);
1855 end;
1857 ENET_EVENT_TYPE_DISCONNECT:
1858 begin
1859 g_Net_Disconnect(True);
1860 Result := 1;
1861 Exit;
1862 end;
1863 end;
1864 end
1865 end;
1867 function g_Net_Client_UpdateWhileLoading(): enet_size_t;
1868 begin
1869 Result := 0;
1870 while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
1871 begin
1872 case NetEvent.kind of
1873 ENET_EVENT_TYPE_RECEIVE:
1874 begin
1875 if (NetEvent.channelID = NET_CHAN_DOWNLOAD_EX) then continue; // ignore all download packets, they're processed by separate code
1876 if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
1877 g_Net_Client_HandlePacket(NetEvent.packet, g_Net_ClientLightMsgHandler);
1878 end;
1880 ENET_EVENT_TYPE_DISCONNECT:
1881 begin
1882 g_Net_Disconnect(True);
1883 Result := 1;
1884 Exit;
1885 end;
1886 end;
1887 end;
1888 g_Net_Flush();
1889 end;
1891 function g_Net_Connect(IP: string; Port: enet_uint16): Boolean;
1892 var
1893 OuterLoop: Boolean;
1894 TimeoutTime, T: Int64;
1895 begin
1896 if NetMode <> NET_NONE then
1897 begin
1898 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_ERR_INGAME], True);
1899 Result := False;
1900 Exit;
1901 end;
1903 Result := True;
1905 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_CLIENT_CONN],
1906 [IP, Port]));
1907 if not NetInitDone then
1908 begin
1909 if (not g_Net_Init()) then
1910 begin
1911 g_Console_Add(_lc[I_NET_MSG_FERROR] + _lc[I_NET_ERR_ENET], True);
1912 Result := False;
1913 Exit;
1914 end
1915 else
1916 NetInitDone := True;
1917 end;
1919 NetHost := enet_host_create(nil, 1, NET_CHANS, 0, 0);
1921 if (NetHost = nil) then
1922 begin
1923 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CLIENT], True);
1924 g_Net_Cleanup;
1925 Result := False;
1926 Exit;
1927 end;
1929 enet_address_set_host(@NetAddr, PChar(Addr(IP[1])));
1930 NetAddr.port := Port;
1932 NetPeer := enet_host_connect(NetHost, @NetAddr, NET_CHANS, NET_PROTOCOL_VER);
1934 if (NetPeer = nil) then
1935 begin
1936 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CLIENT], True);
1937 enet_host_destroy(NetHost);
1938 g_Net_Cleanup;
1939 Result := False;
1940 Exit;
1941 end;
1943 // предупредить что ждем слишком долго через N секунд
1944 TimeoutTime := sys_GetTicks() + NET_CONNECT_TIMEOUT;
1946 OuterLoop := True;
1947 while OuterLoop do
1948 begin
1949 while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
1950 begin
1951 if (NetEvent.kind = ENET_EVENT_TYPE_CONNECT) then
1952 begin
1953 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_CLIENT_DONE]);
1954 NetMode := NET_CLIENT;
1955 NetOut.Clear();
1956 enet_peer_timeout(NetPeer, ENET_PEER_TIMEOUT_LIMIT * 2, ENET_PEER_TIMEOUT_MINIMUM * 2, ENET_PEER_TIMEOUT_MAXIMUM * 2);
1957 NetClientIP := IP;
1958 NetClientPort := Port;
1959 if NetDump then
1960 g_Net_DumpStart();
1961 Exit;
1962 end;
1963 end;
1965 T := sys_GetTicks();
1966 if T > TimeoutTime then
1967 begin
1968 TimeoutTime := T + NET_CONNECT_TIMEOUT * 100; // одного предупреждения хватит
1969 g_Console_Add(_lc[I_NET_MSG_TIMEOUT_WARN], True);
1970 g_Console_Add(Format(_lc[I_NET_MSG_PORTS], [Integer(Port), Integer(NET_PING_PORT)]), True);
1971 end;
1973 ProcessLoading(true);
1975 if e_KeyPressed(IK_SPACE) or e_KeyPressed(IK_ESCAPE) or e_KeyPressed(VK_ESCAPE) or
1976 e_KeyPressed(JOY0_JUMP) or e_KeyPressed(JOY1_JUMP) or e_KeyPressed(JOY2_JUMP) or e_KeyPressed(JOY3_JUMP) then
1977 OuterLoop := False;
1978 end;
1980 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_TIMEOUT], True);
1981 g_Console_Add(Format(_lc[I_NET_MSG_PORTS], [Integer(Port), Integer(NET_PING_PORT)]), True);
1982 if NetPeer <> nil then enet_peer_reset(NetPeer);
1983 if NetHost <> nil then
1984 begin
1985 enet_host_destroy(NetHost);
1986 NetHost := nil;
1987 end;
1988 g_Net_Cleanup();
1989 Result := False;
1990 end;
1992 function IpToStr(IP: LongWord): string;
1993 var
1994 Ptr: Pointer;
1995 begin
1996 Ptr := Addr(IP);
1997 Result := IntToStr(PByte(Ptr + 0)^) + '.';
1998 Result := Result + IntToStr(PByte(Ptr + 1)^) + '.';
1999 Result := Result + IntToStr(PByte(Ptr + 2)^) + '.';
2000 Result := Result + IntToStr(PByte(Ptr + 3)^);
2001 end;
2003 function StrToIp(IPstr: string; var IP: LongWord): Boolean;
2004 var
2005 EAddr: ENetAddress;
2006 begin
2007 Result := enet_address_set_host(@EAddr, PChar(@IPstr[1])) = 0;
2008 IP := EAddr.host;
2009 end;
2011 function g_Net_Client_ByName(Name: string): pTNetClient;
2012 var
2013 a: Integer;
2014 pl: TPlayer;
2015 begin
2016 Result := nil;
2017 for a := Low(NetClients) to High(NetClients) do
2018 if (NetClients[a].Used) and (NetClients[a].State = NET_STATE_GAME) then
2019 begin
2020 pl := g_Player_Get(NetClients[a].Player);
2021 if pl = nil then continue;
2022 if Copy(LowerCase(pl.Name), 1, Length(Name)) <> LowerCase(Name) then continue;
2023 if NetClients[a].Peer <> nil then
2024 begin
2025 Result := @NetClients[a];
2026 Exit;
2027 end;
2028 end;
2029 end;
2031 function g_Net_Client_ByPlayer(PID: Word): pTNetClient;
2032 var
2033 a: Integer;
2034 begin
2035 Result := nil;
2036 for a := Low(NetClients) to High(NetClients) do
2037 if (NetClients[a].Used) and (NetClients[a].State = NET_STATE_GAME) then
2038 if NetClients[a].Player = PID then
2039 begin
2040 Result := @NetClients[a];
2041 Exit;
2042 end;
2043 end;
2045 function g_Net_ClientName_ByID(ID: Integer): string;
2046 var
2047 a: Integer;
2048 pl: TPlayer;
2049 begin
2050 Result := '';
2051 if ID = NET_EVERYONE then
2052 Exit;
2053 for a := Low(NetClients) to High(NetClients) do
2054 if (NetClients[a].ID = ID) and (NetClients[a].Used) and (NetClients[a].State = NET_STATE_GAME) then
2055 begin
2056 pl := g_Player_Get(NetClients[a].Player);
2057 if pl = nil then Exit;
2058 Result := pl.Name;
2059 end;
2060 end;
2062 procedure g_Net_SendData(Data: AByte; peer: pENetPeer; Reliable: Boolean; Chan: Byte = NET_CHAN_DOWNLOAD);
2063 var
2064 P: pENetPacket;
2065 F: enet_uint32;
2066 dataLength: Cardinal;
2067 begin
2068 dataLength := Length(Data);
2070 if (Reliable) then
2071 F := LongWord(ENET_PACKET_FLAG_RELIABLE)
2072 else
2073 F := 0;
2075 if (peer <> nil) then
2076 begin
2077 P := enet_packet_create(@Data[0], dataLength, F);
2078 if not Assigned(P) then Exit;
2079 enet_peer_send(peer, Chan, P);
2080 end
2081 else
2082 begin
2083 P := enet_packet_create(@Data[0], dataLength, F);
2084 if not Assigned(P) then Exit;
2085 enet_host_broadcast(NetHost, Chan, P);
2086 end;
2088 enet_host_flush(NetHost);
2089 end;
2091 function g_Net_IsHostBanned(IP: LongWord; Perm: Boolean = False): Boolean;
2092 var
2093 I: Integer;
2094 begin
2095 Result := False;
2096 if NetBannedHosts = nil then
2097 Exit;
2098 for I := 0 to High(NetBannedHosts) do
2099 if (NetBannedHosts[I].IP = IP) and ((not Perm) or (NetBannedHosts[I].Perm)) then
2100 begin
2101 Result := True;
2102 break;
2103 end;
2104 end;
2106 procedure g_Net_BanHost(IP: LongWord; Perm: Boolean = True); overload;
2107 var
2108 I, P: Integer;
2109 begin
2110 if IP = 0 then
2111 Exit;
2112 if g_Net_IsHostBanned(IP, Perm) then
2113 Exit;
2115 P := -1;
2116 for I := Low(NetBannedHosts) to High(NetBannedHosts) do
2117 if NetBannedHosts[I].IP = 0 then
2118 begin
2119 P := I;
2120 break;
2121 end;
2123 if P < 0 then
2124 begin
2125 SetLength(NetBannedHosts, Length(NetBannedHosts) + 1);
2126 P := High(NetBannedHosts);
2127 end;
2129 NetBannedHosts[P].IP := IP;
2130 NetBannedHosts[P].Perm := Perm;
2131 end;
2133 procedure g_Net_BanHost(IP: string; Perm: Boolean = True); overload;
2134 var
2135 a: LongWord;
2136 b: Boolean;
2137 begin
2138 b := StrToIp(IP, a);
2139 if b then
2140 g_Net_BanHost(a, Perm);
2141 end;
2143 procedure g_Net_UnbanNonPermHosts();
2144 var
2145 I: Integer;
2146 begin
2147 if NetBannedHosts = nil then
2148 Exit;
2149 for I := Low(NetBannedHosts) to High(NetBannedHosts) do
2150 if (NetBannedHosts[I].IP > 0) and not NetBannedHosts[I].Perm then
2151 begin
2152 NetBannedHosts[I].IP := 0;
2153 NetBannedHosts[I].Perm := True;
2154 end;
2155 end;
2157 function g_Net_UnbanHost(IP: string): Boolean; overload;
2158 var
2159 a: LongWord;
2160 begin
2161 Result := StrToIp(IP, a);
2162 if Result then
2163 Result := g_Net_UnbanHost(a);
2164 end;
2166 function g_Net_UnbanHost(IP: LongWord): Boolean; overload;
2167 var
2168 I: Integer;
2169 begin
2170 Result := False;
2171 if IP = 0 then
2172 Exit;
2173 if NetBannedHosts = nil then
2174 Exit;
2175 for I := 0 to High(NetBannedHosts) do
2176 if NetBannedHosts[I].IP = IP then
2177 begin
2178 NetBannedHosts[I].IP := 0;
2179 NetBannedHosts[I].Perm := True;
2180 Result := True;
2181 // no break here to clear all bans of this host, perm and non-perm
2182 end;
2183 end;
2185 procedure g_Net_SaveBanList();
2186 var
2187 F: TextFile;
2188 I: Integer;
2189 path: AnsiString;
2190 begin
2191 path := e_GetWriteableDir(DataDirs);
2192 if path <> '' then
2193 begin
2194 path := e_CatPath(path, BANLIST_FILENAME);
2195 Assign(F, path);
2196 Rewrite(F);
2197 if NetBannedHosts <> nil then
2198 for I := 0 to High(NetBannedHosts) do
2199 if NetBannedHosts[I].Perm and (NetBannedHosts[I].IP > 0) then
2200 Writeln(F, IpToStr(NetBannedHosts[I].IP));
2201 CloseFile(F)
2202 end
2203 end;
2205 procedure g_Net_DumpStart();
2206 begin
2207 if NetMode = NET_SERVER then
2208 NetDumpFile := e_CreateResource(LogDirs, NETDUMP_FILENAME + '_server')
2209 else
2210 NetDumpFile := e_CreateResource(LogDirs, NETDUMP_FILENAME + '_client');
2211 end;
2213 procedure g_Net_DumpSendBuffer();
2214 begin
2215 writeInt(NetDumpFile, gTime);
2216 writeInt(NetDumpFile, LongWord(NetOut.CurSize));
2217 writeInt(NetDumpFile, Byte(1));
2218 NetDumpFile.WriteBuffer(NetOut.Data^, NetOut.CurSize);
2219 end;
2221 procedure g_Net_DumpRecvBuffer(Buf: penet_uint8; Len: LongWord);
2222 begin
2223 if (Buf = nil) or (Len = 0) then Exit;
2224 writeInt(NetDumpFile, gTime);
2225 writeInt(NetDumpFile, Len);
2226 writeInt(NetDumpFile, Byte(0));
2227 NetDumpFile.WriteBuffer(Buf^, Len);
2228 end;
2230 procedure g_Net_DumpEnd();
2231 begin
2232 NetDumpFile.Free();
2233 NetDumpFile := nil;
2234 end;
2236 function g_Net_ForwardPorts(ForwardPongPort: Boolean = True): Boolean;
2237 {$IFDEF USE_MINIUPNPC}
2238 var
2239 DevList: PUPNPDev;
2240 Urls: TUPNPUrls;
2241 Data: TIGDDatas;
2242 LanAddr: array [0..255] of Char;
2243 StrPort: AnsiString;
2244 Err, I: Integer;
2245 begin
2246 Result := False;
2248 if NetPortForwarded = NetPort then
2249 begin
2250 Result := True;
2251 exit;
2252 end;
2254 NetPongForwarded := False;
2255 NetPortForwarded := 0;
2257 DevList := upnpDiscover(1000, nil, nil, 0, 0, 2, Addr(Err));
2258 if DevList = nil then
2259 begin
2260 conwritefln('port forwarding failed: upnpDiscover() failed: %d', [Err]);
2261 exit;
2262 end;
2264 I := UPNP_GetValidIGD(DevList, @Urls, @Data, Addr(LanAddr[0]), 256);
2266 if I = 0 then
2267 begin
2268 conwriteln('port forwarding failed: could not find an IGD device on this LAN');
2269 FreeUPNPDevList(DevList);
2270 FreeUPNPUrls(@Urls);
2271 exit;
2272 end;
2274 StrPort := IntToStr(NetPort);
2275 I := UPNP_AddPortMapping(
2276 Urls.controlURL, Addr(data.first.servicetype[1]),
2277 PChar(StrPort), PChar(StrPort), Addr(LanAddr[0]), PChar('D2DF'),
2278 PChar('UDP'), nil, PChar('0')
2279 );
2281 if I <> 0 then
2282 begin
2283 conwritefln('forwarding port %d failed: error %d', [NetPort, I]);
2284 FreeUPNPDevList(DevList);
2285 FreeUPNPUrls(@Urls);
2286 exit;
2287 end;
2289 if ForwardPongPort then
2290 begin
2291 StrPort := IntToStr(NET_PING_PORT);
2292 I := UPNP_AddPortMapping(
2293 Urls.controlURL, Addr(data.first.servicetype[1]),
2294 PChar(StrPort), PChar(StrPort), Addr(LanAddr[0]), PChar('D2DF'),
2295 PChar('UDP'), nil, PChar('0')
2296 );
2298 if I <> 0 then
2299 begin
2300 conwritefln('forwarding port %d failed: error %d', [NetPort + 1, I]);
2301 NetPongForwarded := False;
2302 end
2303 else
2304 begin
2305 conwritefln('forwarded port %d successfully', [NetPort + 1]);
2306 NetPongForwarded := True;
2307 end;
2308 end;
2310 conwritefln('forwarded port %d successfully', [NetPort]);
2311 NetIGDControl := AnsiString(Urls.controlURL);
2312 NetIGDService := data.first.servicetype;
2313 NetPortForwarded := NetPort;
2315 FreeUPNPDevList(DevList);
2316 FreeUPNPUrls(@Urls);
2317 Result := True;
2318 end;
2319 {$ELSE}
2320 begin
2321 Result := False;
2322 end;
2323 {$ENDIF}
2325 procedure g_Net_UnforwardPorts();
2326 {$IFDEF USE_MINIUPNPC}
2327 var
2328 I: Integer;
2329 StrPort: AnsiString;
2330 begin
2331 if NetPortForwarded = 0 then Exit;
2333 conwriteln('unforwarding ports...');
2335 StrPort := IntToStr(NetPortForwarded);
2336 I := UPNP_DeletePortMapping(
2337 PChar(NetIGDControl), Addr(NetIGDService[1]),
2338 PChar(StrPort), PChar('UDP'), nil
2339 );
2340 conwritefln(' port %d: %d', [NetPortForwarded, I]);
2342 if NetPongForwarded then
2343 begin
2344 NetPongForwarded := False;
2345 StrPort := IntToStr(NetPortForwarded + 1);
2346 I := UPNP_DeletePortMapping(
2347 PChar(NetIGDControl), Addr(NetIGDService[1]),
2348 PChar(StrPort), PChar('UDP'), nil
2349 );
2350 conwritefln(' port %d: %d', [NetPortForwarded + 1, I]);
2351 end;
2353 NetPortForwarded := 0;
2354 end;
2355 {$ELSE}
2356 begin
2357 end;
2358 {$ENDIF}
2361 initialization
2362 conRegVar('cl_downloadtimeout', @g_Net_DownloadTimeout, 0.0, 1000000.0, '', 'timeout in seconds, 0 to disable it');
2363 SetLength(NetClients, 0);
2364 g_Net_DownloadTimeout := 60;
2365 NetIn.Alloc(NET_BUFSIZE);
2366 NetOut.Alloc(NET_BUFSIZE);
2367 NetBuf[NET_UNRELIABLE].Alloc(NET_BUFSIZE*2);
2368 NetBuf[NET_RELIABLE].Alloc(NET_BUFSIZE*2);
2369 trans_omsg.Alloc(NET_BUFSIZE);
2370 finalization
2371 NetIn.Free();
2372 NetOut.Free();
2373 NetBuf[NET_UNRELIABLE].Free();
2374 NetBuf[NET_RELIABLE].Free();
2375 end.