DEADSOFTWARE

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