DEADSOFTWARE

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