DEADSOFTWARE

net: more code for file transfers; it was able to transfer a map, and even substiture...
[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].Peer = NetEvent.peer) then
777 begin
778 nid := f;
779 break;
780 end;
781 end;
782 //e_LogWritefln('RECEIVE: dlpacket; client=%d (datalen=%u)', [nid, NetEvent.packet^.dataLength]);
784 if (nid < 0) then exit; // wtf?!
785 nc := @NetClients[nid];
787 if (NetEvent.packet^.dataLength = 0) then
788 begin
789 KillClientByFT(nc^);
790 exit;
791 end;
793 tf := @NetClients[nid].Transfer;
794 tf.lastAckTime := GetTimerMS();
796 cmd := Byte(NetEvent.packet^.data^);
797 //e_LogWritefln('RECEIVE: nid=%d; cmd=%u', [nid, cmd]);
798 case cmd of
799 NTF_CLIENT_FILE_REQUEST: // file request
800 begin
801 if (tf.stream <> nil) then
802 begin
803 KillClientByFT(nc^);
804 exit;
805 end;
806 if (NetEvent.packet^.dataLength < 2) then
807 begin
808 KillClientByFT(nc^);
809 exit;
810 end;
811 // new transfer request; build packet
812 if not msg.Init(NetEvent.packet^.data+1, NetEvent.packet^.dataLength-1, True) then
813 begin
814 KillClientByFT(nc^);
815 exit;
816 end;
817 // get resource index
818 ridx := msg.ReadLongInt();
819 if (ridx < -1) or (ridx >= gExternalResources.Count) then
820 begin
821 e_LogWritefln('Invalid resource index %d', [ridx], TMsgType.Warning);
822 KillClientByFT(nc^);
823 exit;
824 end;
825 if (ridx < 0) then fname := MapsDir+gGameSettings.WAD else fname := GameDir+'/wads/'+gExternalResources[ridx];
826 if (length(fname) = 0) then
827 begin
828 e_WriteLog('Invalid filename: '+fname, TMsgType.Warning);
829 KillClientByFT(nc^);
830 exit;
831 end;
832 tf.diskName := findDiskWad(fname);
833 //if (length(tf.diskName) = 0) then tf.diskName := findDiskWad(GameDir+'/wads/'+fname);
834 if (length(tf.diskName) = 0) then
835 begin
836 e_LogWritefln('NETWORK: file "%s" not found!', [fname], TMsgType.Fatal);
837 KillClientByFT(nc^);
838 exit;
839 end;
840 // calculate hash
841 //TODO: cache hashes
842 tf.hash := MD5File(tf.diskName);
843 // create file stream
844 tf.diskName := findDiskWad(fname);
845 try
846 tf.stream := openDiskFileRO(tf.diskName);
847 except
848 tf.stream := nil;
849 end;
850 if (tf.stream = nil) then
851 begin
852 e_WriteLog(Format('NETWORK: file "%s" not found!', [fname]), TMsgType.Fatal);
853 KillClientByFT(nc^);
854 exit;
855 end;
856 e_LogWritefln('client #%d requested resource #%d (file is `%s` : `%s`)', [nc.ID, ridx, fname, tf.diskName]);
857 tf.size := tf.stream.size;
858 tf.chunkSize := FILE_CHUNK_SIZE; // arbitrary
859 tf.lastSentChunk := -1;
860 tf.lastAckChunk := -1;
861 tf.lastAckTime := GetTimerMS();
862 tf.inProgress := False; // waiting for the first ACK or for the cancel
863 GetMem(tf.diskBuffer, tf.chunkSize);
864 // sent file info message
865 omsg.Alloc(NET_BUFSIZE);
866 try
867 omsg.Clear();
868 omsg.Write(Byte(NTF_SERVER_FILE_INFO));
869 omsg.Write(tf.hash);
870 omsg.Write(tf.size);
871 omsg.Write(tf.chunkSize);
872 omsg.Write(ExtractFileName(fname));
873 pkt := enet_packet_create(omsg.Data, omsg.CurSize, ENET_PACKET_FLAG_RELIABLE);
874 if not Assigned(pkt) then
875 begin
876 KillClientByFT(nc^);
877 exit;
878 end;
879 if (enet_peer_send(nc.Peer, NET_CHAN_DOWNLOAD_EX, pkt) <> 0) then
880 begin
881 KillClientByFT(nc^);
882 exit;
883 end;
884 finally
885 omsg.Free();
886 end;
887 end;
888 NTF_CLIENT_ABORT: // do not send requested file, or abort current transfer
889 begin
890 e_LogWritefln('client #%d aborted file transfer', [nc.ID]);
891 clearNetClientTransfers(nc^);
892 end;
893 NTF_CLIENT_START: // start transfer; client may resume download by sending non-zero starting chunk
894 begin
895 if not Assigned(tf.stream) then
896 begin
897 KillClientByFT(nc^);
898 exit;
899 end;
900 if (tf.lastSentChunk <> -1) or (tf.lastAckChunk <> -1) or (tf.inProgress) then
901 begin
902 // double ack, get lost
903 KillClientByFT(nc^);
904 exit;
905 end;
906 if (NetEvent.packet^.dataLength < 2) then
907 begin
908 KillClientByFT(nc^);
909 exit;
910 end;
911 // build packet
912 if not msg.Init(NetEvent.packet^.data+1, NetEvent.packet^.dataLength-1, True) then
913 begin
914 KillClientByFT(nc^);
915 exit;
916 end;
917 chunk := msg.ReadLongInt();
918 if (chunk < 0) or (chunk > (tf.size+tf.chunkSize-1) div tf.chunkSize) then
919 begin
920 KillClientByFT(nc^);
921 exit;
922 end;
923 e_LogWritefln('client #%d started file transfer from chunk %d', [nc.ID, chunk]);
924 // start sending chunks
925 tf.inProgress := True;
926 tf.lastSentChunk := chunk-1;
927 tf.lastAckChunk := chunk-1;
928 end;
929 NTF_CLIENT_ACK: // chunk ack; chunk number follows
930 begin
931 if not Assigned(tf.stream) then
932 begin
933 KillClientByFT(nc^);
934 exit;
935 end;
936 if (tf.lastSentChunk < 0) or (not tf.inProgress) then
937 begin
938 // double ack, get lost
939 KillClientByFT(nc^);
940 exit;
941 end;
942 if (NetEvent.packet^.dataLength < 2) then
943 begin
944 KillClientByFT(nc^);
945 exit;
946 end;
947 // build packet
948 if not msg.Init(NetEvent.packet^.data+1, NetEvent.packet^.dataLength-1, True) then
949 begin
950 KillClientByFT(nc^);
951 exit;
952 end;
953 chunk := msg.ReadLongInt();
954 if (chunk < 0) or (chunk > (tf.size+tf.chunkSize-1) div tf.chunkSize) then
955 begin
956 KillClientByFT(nc^);
957 exit;
958 end;
959 // do it this way, so client may seek, or request retransfers for some reason
960 tf.lastAckChunk := chunk;
961 tf.lastSentChunk := chunk;
962 //e_LogWritefln('client #%d acked file transfer chunk %d', [nc.ID, chunk]);
963 end;
964 NTF_CLIENT_MAP_REQUEST:
965 begin
966 e_LogWritefln('client #%d requested map info', [nc.ID]);
967 omsg.Alloc(NET_BUFSIZE);
968 try
969 omsg.Clear();
970 dfn := findDiskWad(MapsDir+gGameSettings.WAD);
971 if (dfn = '') then dfn := '!wad_not_found!.wad'; //FIXME
972 md5 := MD5File(dfn);
973 st := openDiskFileRO(dfn);
974 if not assigned(st) then exit; //wtf?!
975 size := st.size;
976 st.Free;
977 // packet type
978 omsg.Write(Byte(NTF_SERVER_MAP_INFO));
979 // map wad name
980 omsg.Write(gGameSettings.WAD);
981 // map wad md5
982 omsg.Write(md5);
983 // map wad size
984 omsg.Write(size);
985 // number of external resources for map
986 omsg.Write(LongInt(gExternalResources.Count));
987 // external resource names
988 for f := 0 to gExternalResources.Count-1 do
989 begin
990 omsg.Write(ExtractFileName(gExternalResources[f])); // GameDir+'/wads/'+ResList.Strings[i]
991 end;
992 // send packet
993 pkt := enet_packet_create(omsg.Data, omsg.CurSize, ENET_PACKET_FLAG_RELIABLE);
994 if not Assigned(pkt) then exit;
995 if (enet_peer_send(nc.Peer, NET_CHAN_DOWNLOAD_EX, pkt) <> 0) then exit;
996 finally
997 omsg.Free();
998 end;
999 end;
1000 else
1001 begin
1002 KillClientByFT(NetClients[nid]);
1003 exit;
1004 end;
1005 end;
1006 end;
1009 function g_Net_Host_Update(): enet_size_t;
1010 var
1011 IP: string;
1012 Port: Word;
1013 ID: Integer;
1014 TC: pTNetClient;
1015 TP: TPlayer;
1016 f: Integer;
1017 //ctt: Int64;
1018 begin
1019 IP := '';
1020 Result := 0;
1022 if NetUseMaster then g_Net_Slist_Check;
1023 g_Net_Host_CheckPings;
1025 //ctt := -GetTimerMS();
1026 // process file transfers
1027 for f := Low(NetClients) to High(NetClients) do
1028 begin
1029 if (not NetClients[f].Used) then continue;
1030 if (NetClients[f].Transfer.stream = nil) then continue;
1031 ProcessHostFileTransfers(NetClients[f]);
1032 end;
1034 ctt := ctt+GetTimerMS();
1035 if (ctt > 1) then e_LogWritefln('all transfers: [%d]', [Integer(ctt)]);
1038 while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
1039 begin
1040 case (NetEvent.kind) of
1041 ENET_EVENT_TYPE_CONNECT:
1042 begin
1043 IP := IpToStr(NetEvent.Peer^.address.host);
1044 Port := NetEvent.Peer^.address.port;
1045 g_Console_Add(_lc[I_NET_MSG] +
1046 Format(_lc[I_NET_MSG_HOST_CONN], [IP, Port]));
1048 if (NetEvent.data <> NET_PROTOCOL_VER) then
1049 begin
1050 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_REJECT] +
1051 _lc[I_NET_DISC_PROTOCOL]);
1052 NetEvent.peer^.data := GetMemory(SizeOf(Byte));
1053 Byte(NetEvent.peer^.data^) := 255;
1054 enet_peer_disconnect(NetEvent.peer, NET_DISC_PROTOCOL);
1055 enet_host_flush(NetHost);
1056 Exit;
1057 end;
1059 ID := g_Net_FindSlot();
1061 if ID < 0 then
1062 begin
1063 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_REJECT] +
1064 _lc[I_NET_DISC_FULL]);
1065 NetEvent.Peer^.data := GetMemory(SizeOf(Byte));
1066 Byte(NetEvent.peer^.data^) := 255;
1067 enet_peer_disconnect(NetEvent.peer, NET_DISC_FULL);
1068 enet_host_flush(NetHost);
1069 Exit;
1070 end;
1072 NetClients[ID].Peer := NetEvent.peer;
1073 NetClients[ID].Peer^.data := GetMemory(SizeOf(Byte));
1074 Byte(NetClients[ID].Peer^.data^) := ID;
1075 NetClients[ID].State := NET_STATE_AUTH;
1076 NetClients[ID].RCONAuth := False;
1077 NetClients[ID].NetOut[NET_UNRELIABLE].Alloc(NET_BUFSIZE*2);
1078 NetClients[ID].NetOut[NET_RELIABLE].Alloc(NET_BUFSIZE*2);
1079 clearNetClientTransfers(NetClients[ID]); // just in case
1081 enet_peer_timeout(NetEvent.peer, ENET_PEER_TIMEOUT_LIMIT * 2, ENET_PEER_TIMEOUT_MINIMUM * 2, ENET_PEER_TIMEOUT_MAXIMUM * 2);
1083 Inc(NetClientCount);
1084 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_HOST_ADD], [ID]));
1085 end;
1087 ENET_EVENT_TYPE_RECEIVE:
1088 begin
1089 //e_LogWritefln('RECEIVE: chan=%u', [NetEvent.channelID]);
1090 if (NetEvent.channelID = NET_CHAN_DOWNLOAD_EX) then
1091 begin
1092 ProcessDownloadExPacket();
1093 end
1094 else
1095 begin
1096 ID := Byte(NetEvent.peer^.data^);
1097 if ID > High(NetClients) then Exit;
1098 TC := @NetClients[ID];
1100 if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
1101 g_Net_Host_HandlePacket(TC, NetEvent.packet, g_Net_HostMsgHandler);
1102 end;
1103 end;
1105 ENET_EVENT_TYPE_DISCONNECT:
1106 begin
1107 ID := Byte(NetEvent.peer^.data^);
1108 if ID > High(NetClients) then Exit;
1109 clearNetClient(NetClients[ID]);
1110 TC := @NetClients[ID];
1111 if TC = nil then Exit;
1113 if not (TC^.Used) then Exit;
1115 TP := g_Player_Get(TC^.Player);
1117 if TP <> nil then
1118 begin
1119 TP.Lives := 0;
1120 TP.Kill(K_SIMPLEKILL, 0, HIT_DISCON);
1121 g_Console_Add(Format(_lc[I_PLAYER_LEAVE], [TP.Name]), True);
1122 e_WriteLog('NET: Client ' + TP.Name + ' [' + IntToStr(ID) + '] disconnected.', TMsgType.Notify);
1123 g_Player_Remove(TP.UID);
1124 end;
1126 TC^.Used := False;
1127 TC^.State := NET_STATE_NONE;
1128 TC^.Peer := nil;
1129 TC^.Player := 0;
1130 TC^.RequestedFullUpdate := False;
1131 TC^.NetOut[NET_UNRELIABLE].Free();
1132 TC^.NetOut[NET_RELIABLE].Free();
1134 FreeMemory(NetEvent.peer^.data);
1135 NetEvent.peer^.data := nil;
1136 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_HOST_DISC], [ID]));
1137 Dec(NetClientCount);
1139 if NetUseMaster then g_Net_Slist_Update;
1140 end;
1141 end;
1142 end;
1143 end;
1146 { /// CLIENT FUNCTIONS /// }
1149 procedure g_Net_Disconnect(Forced: Boolean = False);
1150 begin
1151 if NetMode <> NET_CLIENT then Exit;
1152 if (NetHost = nil) or (NetPeer = nil) then Exit;
1154 if not Forced then
1155 begin
1156 enet_peer_disconnect(NetPeer, NET_DISC_NONE);
1158 while (enet_host_service(NetHost, @NetEvent, 1500) > 0) do
1159 begin
1160 if (NetEvent.kind = ENET_EVENT_TYPE_DISCONNECT) then
1161 begin
1162 NetPeer := nil;
1163 break;
1164 end;
1166 if (NetEvent.kind = ENET_EVENT_TYPE_RECEIVE) then
1167 enet_packet_destroy(NetEvent.packet);
1168 end;
1170 if NetPeer <> nil then
1171 begin
1172 enet_peer_reset(NetPeer);
1173 NetPeer := nil;
1174 end;
1175 end
1176 else
1177 begin
1178 e_WriteLog('NET: Kicked from server: ' + IntToStr(NetEvent.data), TMsgType.Notify);
1179 if (NetEvent.data <= NET_DISC_MAX) then
1180 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_KICK] +
1181 _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + NetEvent.data)], True);
1182 end;
1184 if NetHost <> nil then
1185 begin
1186 enet_host_destroy(NetHost);
1187 NetHost := nil;
1188 end;
1189 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_CLIENT_DISC]);
1191 g_Net_Cleanup;
1192 e_WriteLog('NET: Disconnected', TMsgType.Notify);
1193 end;
1195 procedure g_Net_Client_Send(Reliable: Boolean; Chan: Byte = NET_CHAN_GAME);
1196 var
1197 T: Integer;
1198 begin
1199 if (Reliable) then
1200 T := NET_RELIABLE
1201 else
1202 T := NET_UNRELIABLE;
1204 // write size first
1205 NetBuf[T].Write(Integer(NetOut.CurSize));
1206 NetBuf[T].Write(NetOut);
1208 if NetDump then g_Net_DumpSendBuffer();
1209 NetOut.Clear();
1210 g_Net_Flush(); // FIXME: for now, send immediately
1211 end;
1213 function g_Net_Client_Update(): enet_size_t;
1214 begin
1215 Result := 0;
1216 while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
1217 begin
1218 case NetEvent.kind of
1219 ENET_EVENT_TYPE_RECEIVE:
1220 begin
1221 if (NetEvent.channelID = NET_CHAN_DOWNLOAD_EX) then continue; // ignore all download packets, they're processed by separate code
1222 if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
1223 g_Net_Client_HandlePacket(NetEvent.packet, g_Net_ClientMsgHandler);
1224 end;
1226 ENET_EVENT_TYPE_DISCONNECT:
1227 begin
1228 g_Net_Disconnect(True);
1229 Result := 1;
1230 Exit;
1231 end;
1232 end;
1233 end
1234 end;
1236 function g_Net_Client_UpdateWhileLoading(): enet_size_t;
1237 begin
1238 Result := 0;
1239 while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
1240 begin
1241 case NetEvent.kind of
1242 ENET_EVENT_TYPE_RECEIVE:
1243 begin
1244 if (NetEvent.channelID = NET_CHAN_DOWNLOAD_EX) then continue; // ignore all download packets, they're processed by separate code
1245 if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
1246 g_Net_Client_HandlePacket(NetEvent.packet, g_Net_ClientLightMsgHandler);
1247 end;
1249 ENET_EVENT_TYPE_DISCONNECT:
1250 begin
1251 g_Net_Disconnect(True);
1252 Result := 1;
1253 Exit;
1254 end;
1255 end;
1256 end;
1257 g_Net_Flush();
1258 end;
1260 function g_Net_Connect(IP: string; Port: enet_uint16): Boolean;
1261 var
1262 OuterLoop: Boolean;
1263 TimeoutTime, T: Int64;
1264 begin
1265 if NetMode <> NET_NONE then
1266 begin
1267 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_ERR_INGAME], True);
1268 Result := False;
1269 Exit;
1270 end;
1272 Result := True;
1274 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_CLIENT_CONN],
1275 [IP, Port]));
1276 if not NetInitDone then
1277 begin
1278 if (not g_Net_Init()) then
1279 begin
1280 g_Console_Add(_lc[I_NET_MSG_FERROR] + _lc[I_NET_ERR_ENET], True);
1281 Result := False;
1282 Exit;
1283 end
1284 else
1285 NetInitDone := True;
1286 end;
1288 NetHost := enet_host_create(nil, 1, NET_CHANS, 0, 0);
1290 if (NetHost = nil) then
1291 begin
1292 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CLIENT], True);
1293 g_Net_Cleanup;
1294 Result := False;
1295 Exit;
1296 end;
1298 enet_address_set_host(@NetAddr, PChar(Addr(IP[1])));
1299 NetAddr.port := Port;
1301 NetPeer := enet_host_connect(NetHost, @NetAddr, NET_CHANS, NET_PROTOCOL_VER);
1303 if (NetPeer = nil) then
1304 begin
1305 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CLIENT], True);
1306 enet_host_destroy(NetHost);
1307 g_Net_Cleanup;
1308 Result := False;
1309 Exit;
1310 end;
1312 // предупредить что ждем слишком долго через N секунд
1313 TimeoutTime := GetTimer() + NET_CONNECT_TIMEOUT;
1315 OuterLoop := True;
1316 while OuterLoop do
1317 begin
1318 while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
1319 begin
1320 if (NetEvent.kind = ENET_EVENT_TYPE_CONNECT) then
1321 begin
1322 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_CLIENT_DONE]);
1323 NetMode := NET_CLIENT;
1324 NetOut.Clear();
1325 enet_peer_timeout(NetPeer, ENET_PEER_TIMEOUT_LIMIT * 2, ENET_PEER_TIMEOUT_MINIMUM * 2, ENET_PEER_TIMEOUT_MAXIMUM * 2);
1326 NetClientIP := IP;
1327 NetClientPort := Port;
1328 if NetDump then
1329 g_Net_DumpStart();
1330 Exit;
1331 end;
1332 end;
1334 T := GetTimer();
1335 if T > TimeoutTime then
1336 begin
1337 TimeoutTime := T + NET_CONNECT_TIMEOUT * 100; // одного предупреждения хватит
1338 g_Console_Add(_lc[I_NET_MSG_TIMEOUT_WARN], True);
1339 g_Console_Add(Format(_lc[I_NET_MSG_PORTS], [Integer(Port), Integer(NET_PING_PORT)]), True);
1340 end;
1342 ProcessLoading(true);
1344 if e_KeyPressed(IK_SPACE) or e_KeyPressed(IK_ESCAPE) or e_KeyPressed(VK_ESCAPE) or
1345 e_KeyPressed(JOY0_JUMP) or e_KeyPressed(JOY1_JUMP) or e_KeyPressed(JOY2_JUMP) or e_KeyPressed(JOY3_JUMP) then
1346 OuterLoop := False;
1347 end;
1349 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_TIMEOUT], True);
1350 g_Console_Add(Format(_lc[I_NET_MSG_PORTS], [Integer(Port), Integer(NET_PING_PORT)]), True);
1351 if NetPeer <> nil then enet_peer_reset(NetPeer);
1352 if NetHost <> nil then
1353 begin
1354 enet_host_destroy(NetHost);
1355 NetHost := nil;
1356 end;
1357 g_Net_Cleanup();
1358 Result := False;
1359 end;
1361 function IpToStr(IP: LongWord): string;
1362 var
1363 Ptr: Pointer;
1364 begin
1365 Ptr := Addr(IP);
1366 Result := IntToStr(PByte(Ptr + 0)^) + '.';
1367 Result := Result + IntToStr(PByte(Ptr + 1)^) + '.';
1368 Result := Result + IntToStr(PByte(Ptr + 2)^) + '.';
1369 Result := Result + IntToStr(PByte(Ptr + 3)^);
1370 end;
1372 function StrToIp(IPstr: string; var IP: LongWord): Boolean;
1373 var
1374 EAddr: ENetAddress;
1375 begin
1376 Result := enet_address_set_host(@EAddr, PChar(@IPstr[1])) = 0;
1377 IP := EAddr.host;
1378 end;
1380 function g_Net_Client_ByName(Name: string): pTNetClient;
1381 var
1382 a: Integer;
1383 pl: TPlayer;
1384 begin
1385 Result := nil;
1386 for a := Low(NetClients) to High(NetClients) do
1387 if (NetClients[a].Used) and (NetClients[a].State = NET_STATE_GAME) then
1388 begin
1389 pl := g_Player_Get(NetClients[a].Player);
1390 if pl = nil then continue;
1391 if Copy(LowerCase(pl.Name), 1, Length(Name)) <> LowerCase(Name) then continue;
1392 if NetClients[a].Peer <> nil then
1393 begin
1394 Result := @NetClients[a];
1395 Exit;
1396 end;
1397 end;
1398 end;
1400 function g_Net_Client_ByPlayer(PID: Word): pTNetClient;
1401 var
1402 a: Integer;
1403 begin
1404 Result := nil;
1405 for a := Low(NetClients) to High(NetClients) do
1406 if (NetClients[a].Used) and (NetClients[a].State = NET_STATE_GAME) then
1407 if NetClients[a].Player = PID then
1408 begin
1409 Result := @NetClients[a];
1410 Exit;
1411 end;
1412 end;
1414 function g_Net_ClientName_ByID(ID: Integer): string;
1415 var
1416 a: Integer;
1417 pl: TPlayer;
1418 begin
1419 Result := '';
1420 if ID = NET_EVERYONE then
1421 Exit;
1422 for a := Low(NetClients) to High(NetClients) do
1423 if (NetClients[a].ID = ID) and (NetClients[a].Used) and (NetClients[a].State = NET_STATE_GAME) then
1424 begin
1425 pl := g_Player_Get(NetClients[a].Player);
1426 if pl = nil then Exit;
1427 Result := pl.Name;
1428 end;
1429 end;
1431 procedure g_Net_SendData(Data: AByte; peer: pENetPeer; Reliable: Boolean; Chan: Byte = NET_CHAN_DOWNLOAD);
1432 var
1433 P: pENetPacket;
1434 F: enet_uint32;
1435 dataLength: Cardinal;
1436 begin
1437 dataLength := Length(Data);
1439 if (Reliable) then
1440 F := LongWord(ENET_PACKET_FLAG_RELIABLE)
1441 else
1442 F := 0;
1444 if (peer <> nil) then
1445 begin
1446 P := enet_packet_create(@Data[0], dataLength, F);
1447 if not Assigned(P) then Exit;
1448 enet_peer_send(peer, Chan, P);
1449 end
1450 else
1451 begin
1452 P := enet_packet_create(@Data[0], dataLength, F);
1453 if not Assigned(P) then Exit;
1454 enet_host_broadcast(NetHost, Chan, P);
1455 end;
1457 enet_host_flush(NetHost);
1458 end;
1460 function g_Net_UserRequestExit: Boolean;
1461 begin
1462 Result := e_KeyPressed(IK_SPACE) or
1463 e_KeyPressed(IK_ESCAPE) or
1464 e_KeyPressed(VK_ESCAPE) or
1465 e_KeyPressed(JOY0_JUMP) or
1466 e_KeyPressed(JOY1_JUMP) or
1467 e_KeyPressed(JOY2_JUMP) or
1468 e_KeyPressed(JOY3_JUMP)
1469 end;
1472 function g_Net_Wait_Event(msgId: Word): TMemoryStream;
1473 var
1474 ev: ENetEvent;
1475 rMsgId: Byte;
1476 Ptr: Pointer;
1477 stream: TMemoryStream;
1478 status: cint;
1479 begin
1480 FillChar(ev, SizeOf(ev), 0);
1481 stream := nil;
1482 repeat
1483 status := enet_host_service(NetHost, @ev, Trunc(g_Net_DownloadTimeout * 1000));
1484 if status > 0 then
1485 begin
1486 case ev.kind of
1487 ENET_EVENT_TYPE_RECEIVE:
1488 begin
1489 Ptr := ev.packet^.data;
1490 rMsgId := Byte(Ptr^);
1491 if rMsgId = msgId then
1492 begin
1493 stream := TMemoryStream.Create;
1494 stream.SetSize(ev.packet^.dataLength);
1495 stream.WriteBuffer(Ptr^, ev.packet^.dataLength);
1496 stream.Seek(0, soFromBeginning);
1497 status := 1 (* received *)
1498 end
1499 else
1500 begin
1501 (* looks that game state always received, so ignore it *)
1502 e_LogWritefln('g_Net_Wait_Event(%s): skip message %s', [msgId, rMsgId]);
1503 status := 2 (* continue *)
1504 end
1505 end;
1506 ENET_EVENT_TYPE_DISCONNECT:
1507 begin
1508 if (ev.data <= NET_DISC_MAX) then
1509 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' ' + _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + ev.data)], True);
1510 status := -2 (* error: disconnected *)
1511 end;
1512 else
1513 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' unknown ENet event ' + IntToStr(Ord(ev.kind)), True);
1514 status := -3 (* error: unknown event *)
1515 end;
1516 enet_packet_destroy(ev.packet)
1517 end
1518 else
1519 begin
1520 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' timeout reached', True);
1521 status := 0 (* error: timeout *)
1522 end;
1523 ProcessLoading(true);
1524 until (status <> 2) or g_Net_UserRequestExit();
1525 Result := stream
1526 end;
1530 function getNewTimeoutEnd (): Int64;
1531 begin
1532 result := GetTimerMS();
1533 if (g_Net_DownloadTimeout <= 0) then
1534 begin
1535 result := result+1000*60*3; // 3 minutes
1536 end
1537 else
1538 begin
1539 result := result+trunc(g_Net_DownloadTimeout*1000);
1540 end;
1541 end;
1544 function g_Net_SendMapRequest (): Boolean;
1545 var
1546 msg: TMsg;
1547 pkt: PENetPacket;
1548 begin
1549 result := false;
1550 e_LogWritefln('sending map request...', []);
1551 // send request
1552 msg.Alloc(NET_BUFSIZE);
1553 try
1554 msg.Clear();
1555 msg.Write(Byte(NTF_CLIENT_MAP_REQUEST));
1556 e_LogWritefln(' request size is %d', [msg.CurSize]);
1557 pkt := enet_packet_create(msg.Data, msg.CurSize, ENET_PACKET_FLAG_RELIABLE);
1558 if not Assigned(pkt) then exit;
1559 if (enet_peer_send(NetPeer, NET_CHAN_DOWNLOAD_EX, pkt) <> 0) then exit;
1560 enet_host_flush(NetHost);
1561 finally
1562 msg.Free();
1563 end;
1564 result := true;
1565 end;
1568 // returns `false` on error or user abort
1569 // fills:
1570 // hash
1571 // size
1572 // chunkSize
1573 // returns:
1574 // <0 on error
1575 // 0 on success
1576 // 1 on user abort
1577 // 2 on server abort
1578 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
1579 function g_Net_Wait_MapInfo (var tf: TNetFileTransfer; resList: TStringList): Integer;
1580 var
1581 ev: ENetEvent;
1582 rMsgId: Byte;
1583 Ptr: Pointer;
1584 msg: TMsg;
1585 freePacket: Boolean = false;
1586 ct, ett: Int64;
1587 status: cint;
1588 s: AnsiString;
1589 rc, f: LongInt;
1590 begin
1591 FillChar(ev, SizeOf(ev), 0);
1592 Result := -1;
1593 try
1594 ett := getNewTimeoutEnd();
1595 repeat
1596 status := enet_host_service(NetHost, @ev, 300);
1597 if (status < 0) then
1598 begin
1599 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True);
1600 Result := -1;
1601 exit;
1602 end;
1603 if (status = 0) then
1604 begin
1605 // check for timeout
1606 ct := GetTimerMS();
1607 if (ct >= ett) then
1608 begin
1609 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' timeout reached', True);
1610 Result := -1;
1611 exit;
1612 end;
1613 end
1614 else
1615 begin
1616 // some event
1617 case ev.kind of
1618 ENET_EVENT_TYPE_RECEIVE:
1619 begin
1620 freePacket := true;
1621 if (ev.channelID <> NET_CHAN_DOWNLOAD_EX) then
1622 begin
1623 //e_LogWritefln('g_Net_Wait_MapInfo: skip message from non-transfer channel', []);
1624 freePacket := false;
1625 g_Net_Client_HandlePacket(ev.packet, g_Net_ClientLightMsgHandler);
1626 if (g_Res_received_map_start) then begin result := -666; exit; end;
1627 end
1628 else
1629 begin
1630 ett := getNewTimeoutEnd();
1631 if (ev.packet.dataLength < 1) then
1632 begin
1633 e_LogWritefln('g_Net_Wait_MapInfo: invalid server packet (no data)', []);
1634 Result := -1;
1635 exit;
1636 end;
1637 Ptr := ev.packet^.data;
1638 rMsgId := Byte(Ptr^);
1639 e_LogWritefln('g_Net_Wait_MapInfo: got message %u from server (dataLength=%u)', [rMsgId, ev.packet^.dataLength]);
1640 if (rMsgId = NTF_SERVER_FILE_INFO) then
1641 begin
1642 e_LogWritefln('g_Net_Wait_MapInfo: waiting for map info reply, but got file info reply', []);
1643 Result := -1;
1644 exit;
1645 end
1646 else if (rMsgId = NTF_SERVER_ABORT) then
1647 begin
1648 e_LogWritefln('g_Net_Wait_MapInfo: server aborted transfer', []);
1649 Result := 2;
1650 exit;
1651 end
1652 else if (rMsgId = NTF_SERVER_MAP_INFO) then
1653 begin
1654 e_LogWritefln('g_Net_Wait_MapInfo: creating map info packet...', []);
1655 if not msg.Init(ev.packet^.data+1, ev.packet^.dataLength-1, True) then exit;
1656 e_LogWritefln('g_Net_Wait_MapInfo: parsing map info packet (rd=%d; max=%d)...', [msg.ReadCount, msg.MaxSize]);
1657 resList.Clear();
1658 // map wad name
1659 tf.diskName := msg.ReadString();
1660 e_LogWritefln('g_Net_Wait_MapInfo: map wad is `%s`', [tf.diskName]);
1661 // map wad md5
1662 tf.hash := msg.ReadMD5();
1663 // map wad size
1664 tf.size := msg.ReadLongInt();
1665 e_LogWritefln('g_Net_Wait_MapInfo: map wad size is %d', [tf.size]);
1666 // number of external resources for map
1667 rc := msg.ReadLongInt();
1668 if (rc < 0) or (rc > 1024) then
1669 begin
1670 e_LogWritefln('g_Net_Wait_Event: invalid number of map external resources (%d)', [rc]);
1671 Result := -1;
1672 exit;
1673 end;
1674 e_LogWritefln('g_Net_Wait_MapInfo: map external resource count is %d', [rc]);
1675 // external resource names
1676 for f := 0 to rc-1 do
1677 begin
1678 s := ExtractFileName(msg.ReadString());
1679 if (length(s) = 0) then
1680 begin
1681 Result := -1;
1682 exit;
1683 end;
1684 resList.append(s);
1685 end;
1686 e_LogWritefln('g_Net_Wait_MapInfo: got map info', []);
1687 Result := 0; // success
1688 exit;
1689 end
1690 else
1691 begin
1692 e_LogWritefln('g_Net_Wait_Event: invalid server packet type', []);
1693 Result := -1;
1694 exit;
1695 end;
1696 end;
1697 end;
1698 ENET_EVENT_TYPE_DISCONNECT:
1699 begin
1700 if (ev.data <= NET_DISC_MAX) then
1701 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' ' + _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + ev.data)], True);
1702 Result := -1;
1703 exit;
1704 end;
1705 else
1706 begin
1707 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' unknown ENet event ' + IntToStr(Ord(ev.kind)), True);
1708 result := -1;
1709 exit;
1710 end;
1711 end;
1712 if (freePacket) then begin freePacket := false; enet_packet_destroy(ev.packet); end;
1713 end;
1714 ProcessLoading(true);
1715 if g_Net_UserRequestExit() then
1716 begin
1717 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' user abort', True);
1718 Result := 1;
1719 exit;
1720 end;
1721 until false;
1722 finally
1723 if (freePacket) then enet_packet_destroy(ev.packet);
1724 end;
1725 end;
1728 // returns `false` on error or user abort
1729 // fills:
1730 // diskName (actually, base name)
1731 // hash
1732 // size
1733 // chunkSize
1734 // returns:
1735 // <0 on error
1736 // 0 on success
1737 // 1 on user abort
1738 // 2 on server abort
1739 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
1740 function g_Net_RequestResFileInfo (resIndex: LongInt; out tf: TNetFileTransfer): Integer;
1741 var
1742 ev: ENetEvent;
1743 rMsgId: Byte;
1744 Ptr: Pointer;
1745 msg: TMsg;
1746 freePacket: Boolean = false;
1747 ct, ett: Int64;
1748 status: cint;
1749 pkt: PENetPacket;
1750 begin
1751 // send request
1752 msg.Alloc(NET_BUFSIZE);
1753 try
1754 msg.Clear();
1755 msg.Write(Byte(NTF_CLIENT_FILE_REQUEST));
1756 msg.Write(resIndex);
1757 pkt := enet_packet_create(msg.Data, msg.CurSize, ENET_PACKET_FLAG_RELIABLE);
1758 if not Assigned(pkt) then
1759 begin
1760 result := -1;
1761 exit;
1762 end;
1763 if (enet_peer_send(NetPeer, NET_CHAN_DOWNLOAD_EX, pkt) <> 0) then
1764 begin
1765 result := -1;
1766 exit;
1767 end;
1768 finally
1769 msg.Free();
1770 end;
1772 FillChar(ev, SizeOf(ev), 0);
1773 Result := -1;
1774 try
1775 ett := getNewTimeoutEnd();
1776 repeat
1777 status := enet_host_service(NetHost, @ev, 300);
1778 if (status < 0) then
1779 begin
1780 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True);
1781 Result := -1;
1782 exit;
1783 end;
1784 if (status = 0) then
1785 begin
1786 // check for timeout
1787 ct := GetTimerMS();
1788 if (ct >= ett) then
1789 begin
1790 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' timeout reached', True);
1791 Result := -1;
1792 exit;
1793 end;
1794 end
1795 else
1796 begin
1797 // some event
1798 case ev.kind of
1799 ENET_EVENT_TYPE_RECEIVE:
1800 begin
1801 freePacket := true;
1802 if (ev.channelID <> NET_CHAN_DOWNLOAD_EX) then
1803 begin
1804 //e_LogWriteln('g_Net_Wait_Event: skip message from non-transfer channel');
1805 freePacket := false;
1806 g_Net_Client_HandlePacket(ev.packet, g_Net_ClientLightMsgHandler);
1807 if (g_Res_received_map_start) then begin result := -666; exit; end;
1808 end
1809 else
1810 begin
1811 ett := getNewTimeoutEnd();
1812 if (ev.packet.dataLength < 1) then
1813 begin
1814 e_LogWriteln('g_Net_Wait_Event: invalid server packet (no data)');
1815 Result := -1;
1816 exit;
1817 end;
1818 Ptr := ev.packet^.data;
1819 rMsgId := Byte(Ptr^);
1820 e_LogWritefln('received transfer packet with id %d (%u bytes)', [rMsgId, ev.packet^.dataLength]);
1821 if (rMsgId = NTF_SERVER_FILE_INFO) then
1822 begin
1823 if not msg.Init(ev.packet^.data+1, ev.packet^.dataLength-1, True) then exit;
1824 tf.hash := msg.ReadMD5();
1825 tf.size := msg.ReadLongInt();
1826 tf.chunkSize := msg.ReadLongInt();
1827 tf.diskName := ExtractFileName(msg.readString());
1828 if (tf.size < 0) or (tf.chunkSize <> FILE_CHUNK_SIZE) or (length(tf.diskName) = 0) then
1829 begin
1830 e_LogWritefln('g_Net_RequestResFileInfo: invalid file info packet', []);
1831 Result := -1;
1832 exit;
1833 end;
1834 e_LogWritefln('got file info for resource #%d: size=%d; name=%s', [resIndex, tf.size, tf.diskName]);
1835 Result := 0; // success
1836 exit;
1837 end
1838 else if (rMsgId = NTF_SERVER_ABORT) then
1839 begin
1840 e_LogWriteln('g_Net_RequestResFileInfo: server aborted transfer');
1841 Result := 2;
1842 exit;
1843 end
1844 else if (rMsgId = NTF_SERVER_MAP_INFO) then
1845 begin
1846 e_LogWriteln('g_Net_RequestResFileInfo: waiting for map info reply, but got file info reply');
1847 Result := -1;
1848 exit;
1849 end
1850 else
1851 begin
1852 e_LogWriteln('g_Net_RequestResFileInfo: invalid server packet type');
1853 Result := -1;
1854 exit;
1855 end;
1856 end;
1857 end;
1858 ENET_EVENT_TYPE_DISCONNECT:
1859 begin
1860 if (ev.data <= NET_DISC_MAX) then
1861 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' ' + _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + ev.data)], True);
1862 Result := -1;
1863 exit;
1864 end;
1865 else
1866 begin
1867 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' unknown ENet event ' + IntToStr(Ord(ev.kind)), True);
1868 result := -1;
1869 exit;
1870 end;
1871 end;
1872 if (freePacket) then begin freePacket := false; enet_packet_destroy(ev.packet); end;
1873 end;
1874 ProcessLoading(true);
1875 if g_Net_UserRequestExit() then
1876 begin
1877 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' user abort', True);
1878 Result := 1;
1879 exit;
1880 end;
1881 until false;
1882 finally
1883 if (freePacket) then enet_packet_destroy(ev.packet);
1884 end;
1885 end;
1888 function g_Net_AbortResTransfer (var tf: TNetFileTransfer): Boolean;
1889 var
1890 msg: TMsg;
1891 pkt: PENetPacket;
1892 begin
1893 result := false;
1894 e_LogWritefln('aborting file transfer...', []);
1895 // send request
1896 msg.Alloc(NET_BUFSIZE);
1897 try
1898 msg.Clear();
1899 msg.Write(Byte(NTF_CLIENT_ABORT));
1900 pkt := enet_packet_create(msg.Data, msg.CurSize, ENET_PACKET_FLAG_RELIABLE);
1901 if not Assigned(pkt) then exit;
1902 if (enet_peer_send(NetPeer, NET_CHAN_DOWNLOAD_EX, pkt) <> 0) then exit;
1903 enet_host_flush(NetHost);
1904 finally
1905 msg.Free();
1906 end;
1907 result := true;
1908 end;
1911 // returns `false` on error or user abort
1912 // fills:
1913 // hash
1914 // size
1915 // chunkSize
1916 // returns:
1917 // <0 on error
1918 // 0 on success
1919 // 1 on user abort
1920 // 2 on server abort
1921 // for maps, first `tf.diskName` name will be map wad name, and `tf.hash`/`tf.size` will contain map info
1922 function g_Net_ReceiveResourceFile (resIndex: LongInt; var tf: TNetFileTransfer; strm: TStream): Integer;
1923 var
1924 ev: ENetEvent;
1925 rMsgId: Byte;
1926 Ptr: Pointer;
1927 msg: TMsg;
1928 omsg: TMsg;
1929 freePacket: Boolean = false;
1930 ct, ett: Int64;
1931 status: cint;
1932 nextChunk: Integer = 0;
1933 chunkTotal: Integer;
1934 chunk: Integer;
1935 csize: Integer;
1936 buf: PChar = nil;
1937 pkt: PENetPacket;
1938 //stx: Int64;
1939 begin
1940 // send request
1941 msg.Alloc(NET_BUFSIZE);
1942 try
1943 msg.Clear();
1944 msg.Write(Byte(NTF_CLIENT_START));
1945 msg.Write(LongInt(0));
1946 pkt := enet_packet_create(msg.Data, msg.CurSize, ENET_PACKET_FLAG_RELIABLE);
1947 if not Assigned(pkt) then exit;
1948 if (enet_peer_send(NetPeer, NET_CHAN_DOWNLOAD_EX, pkt) <> 0) then exit;
1949 finally
1950 msg.Free();
1951 end;
1953 chunkTotal := (tf.size+tf.chunkSize-1) div tf.chunkSize;
1954 e_LogWritefln('receiving file `%s` (%d chunks)', [tf.diskName, chunkTotal], TMsgType.Notify);
1955 g_Game_SetLoadingText('downloading "'+ExtractFileName(tf.diskName)+'"', chunkTotal, False);
1957 // wait for reply data
1958 FillChar(ev, SizeOf(ev), 0);
1959 Result := -1;
1960 GetMem(buf, tf.chunkSize);
1961 try
1962 ett := getNewTimeoutEnd();
1963 repeat
1964 //stx := -GetTimerMS();
1965 status := enet_host_service(NetHost, @ev, 300);
1966 if (status < 0) then
1967 begin
1968 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True);
1969 Result := -1;
1970 exit;
1971 end;
1972 if (status = 0) then
1973 begin
1974 // check for timeout
1975 ct := GetTimerMS();
1976 if (ct >= ett) then
1977 begin
1978 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' timeout reached', True);
1979 Result := -1;
1980 exit;
1981 end;
1982 end
1983 else
1984 begin
1985 // some event
1986 case ev.kind of
1987 ENET_EVENT_TYPE_RECEIVE:
1988 begin
1989 freePacket := true;
1990 if (ev.channelID <> NET_CHAN_DOWNLOAD_EX) then
1991 begin
1992 //e_LogWritefln('g_Net_Wait_Event: skip message from non-transfer channel', []);
1993 freePacket := false;
1994 g_Net_Client_HandlePacket(ev.packet, g_Net_ClientLightMsgHandler);
1995 if (g_Res_received_map_start) then begin result := -666; exit; end;
1996 end
1997 else
1998 begin
1999 //stx := stx+GetTimerMS();
2000 //e_LogWritefln('g_Net_ReceiveResourceFile: stx=%d', [Integer(stx)]);
2001 //stx := -GetTimerMS();
2002 ett := getNewTimeoutEnd();
2003 if (ev.packet.dataLength < 1) then
2004 begin
2005 e_LogWritefln('g_Net_ReceiveResourceFile: invalid server packet (no data)', []);
2006 Result := -1;
2007 exit;
2008 end;
2009 Ptr := ev.packet^.data;
2010 rMsgId := Byte(Ptr^);
2011 if (rMsgId = NTF_SERVER_DONE) then
2012 begin
2013 e_LogWritefln('file transfer complete.', []);
2014 result := 0;
2015 exit;
2016 end
2017 else if (rMsgId = NTF_SERVER_CHUNK) then
2018 begin
2019 if not msg.Init(ev.packet^.data+1, ev.packet^.dataLength-1, True) then exit;
2020 chunk := msg.ReadLongInt();
2021 csize := msg.ReadLongInt();
2022 if (chunk <> nextChunk) then
2023 begin
2024 e_LogWritefln('received chunk %d, but expected chunk %d', [chunk, nextChunk]);
2025 Result := -1;
2026 exit;
2027 end;
2028 if (csize < 0) or (csize > tf.chunkSize) then
2029 begin
2030 e_LogWritefln('received chunk with size %d, but expected chunk size is %d', [csize, tf.chunkSize]);
2031 Result := -1;
2032 exit;
2033 end;
2034 //e_LogWritefln('got chunk #%d of #%d (csize=%d)', [chunk, (tf.size+tf.chunkSize-1) div tf.chunkSize, csize]);
2035 msg.ReadData(buf, csize);
2036 strm.WriteBuffer(buf^, csize);
2037 nextChunk := chunk+1;
2038 g_Game_StepLoading();
2039 // send ack
2040 omsg.Alloc(NET_BUFSIZE);
2041 try
2042 omsg.Clear();
2043 omsg.Write(Byte(NTF_CLIENT_ACK));
2044 omsg.Write(LongInt(chunk));
2045 pkt := enet_packet_create(omsg.Data, omsg.CurSize, ENET_PACKET_FLAG_RELIABLE);
2046 if not Assigned(pkt) then exit;
2047 if (enet_peer_send(NetPeer, NET_CHAN_DOWNLOAD_EX, pkt) <> 0) then exit;
2048 finally
2049 omsg.Free();
2050 end;
2051 end
2052 else if (rMsgId = NTF_SERVER_ABORT) then
2053 begin
2054 e_LogWritefln('g_Net_ReceiveResourceFile: server aborted transfer', []);
2055 Result := 2;
2056 exit;
2057 end
2058 else
2059 begin
2060 e_LogWritefln('g_Net_ReceiveResourceFile: invalid server packet type', []);
2061 Result := -1;
2062 exit;
2063 end;
2064 //stx := stx+GetTimerMS();
2065 //e_LogWritefln('g_Net_ReceiveResourceFile: process stx=%d', [Integer(stx)]);
2066 end;
2067 end;
2068 ENET_EVENT_TYPE_DISCONNECT:
2069 begin
2070 if (ev.data <= NET_DISC_MAX) then
2071 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' ' + _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + ev.data)], True);
2072 Result := -1;
2073 exit;
2074 end;
2075 else
2076 begin
2077 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' unknown ENet event ' + IntToStr(Ord(ev.kind)), True);
2078 result := -1;
2079 exit;
2080 end;
2081 end;
2082 if (freePacket) then begin freePacket := false; enet_packet_destroy(ev.packet); end;
2083 end;
2084 ProcessLoading(true);
2085 if g_Net_UserRequestExit() then
2086 begin
2087 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' user abort', True);
2088 Result := 1;
2089 exit;
2090 end;
2091 until false;
2092 finally
2093 FreeMem(buf);
2094 if (freePacket) then enet_packet_destroy(ev.packet);
2095 end;
2096 end;
2099 function g_Net_IsHostBanned(IP: LongWord; Perm: Boolean = False): Boolean;
2100 var
2101 I: Integer;
2102 begin
2103 Result := False;
2104 if NetBannedHosts = nil then
2105 Exit;
2106 for I := 0 to High(NetBannedHosts) do
2107 if (NetBannedHosts[I].IP = IP) and ((not Perm) or (NetBannedHosts[I].Perm)) then
2108 begin
2109 Result := True;
2110 break;
2111 end;
2112 end;
2114 procedure g_Net_BanHost(IP: LongWord; Perm: Boolean = True); overload;
2115 var
2116 I, P: Integer;
2117 begin
2118 if IP = 0 then
2119 Exit;
2120 if g_Net_IsHostBanned(IP, Perm) then
2121 Exit;
2123 P := -1;
2124 for I := Low(NetBannedHosts) to High(NetBannedHosts) do
2125 if NetBannedHosts[I].IP = 0 then
2126 begin
2127 P := I;
2128 break;
2129 end;
2131 if P < 0 then
2132 begin
2133 SetLength(NetBannedHosts, Length(NetBannedHosts) + 1);
2134 P := High(NetBannedHosts);
2135 end;
2137 NetBannedHosts[P].IP := IP;
2138 NetBannedHosts[P].Perm := Perm;
2139 end;
2141 procedure g_Net_BanHost(IP: string; Perm: Boolean = True); overload;
2142 var
2143 a: LongWord;
2144 b: Boolean;
2145 begin
2146 b := StrToIp(IP, a);
2147 if b then
2148 g_Net_BanHost(a, Perm);
2149 end;
2151 procedure g_Net_UnbanNonPermHosts();
2152 var
2153 I: Integer;
2154 begin
2155 if NetBannedHosts = nil then
2156 Exit;
2157 for I := Low(NetBannedHosts) to High(NetBannedHosts) do
2158 if (NetBannedHosts[I].IP > 0) and not NetBannedHosts[I].Perm then
2159 begin
2160 NetBannedHosts[I].IP := 0;
2161 NetBannedHosts[I].Perm := True;
2162 end;
2163 end;
2165 function g_Net_UnbanHost(IP: string): Boolean; overload;
2166 var
2167 a: LongWord;
2168 begin
2169 Result := StrToIp(IP, a);
2170 if Result then
2171 Result := g_Net_UnbanHost(a);
2172 end;
2174 function g_Net_UnbanHost(IP: LongWord): Boolean; overload;
2175 var
2176 I: Integer;
2177 begin
2178 Result := False;
2179 if IP = 0 then
2180 Exit;
2181 if NetBannedHosts = nil then
2182 Exit;
2183 for I := 0 to High(NetBannedHosts) do
2184 if NetBannedHosts[I].IP = IP then
2185 begin
2186 NetBannedHosts[I].IP := 0;
2187 NetBannedHosts[I].Perm := True;
2188 Result := True;
2189 // no break here to clear all bans of this host, perm and non-perm
2190 end;
2191 end;
2193 procedure g_Net_SaveBanList();
2194 var
2195 F: TextFile;
2196 I: Integer;
2197 begin
2198 Assign(F, DataDir + BANLIST_FILENAME);
2199 Rewrite(F);
2200 if NetBannedHosts <> nil then
2201 for I := 0 to High(NetBannedHosts) do
2202 if NetBannedHosts[I].Perm and (NetBannedHosts[I].IP > 0) then
2203 Writeln(F, IpToStr(NetBannedHosts[I].IP));
2204 CloseFile(F);
2205 end;
2207 procedure g_Net_DumpStart();
2208 begin
2209 if NetMode = NET_SERVER then
2210 NetDumpFile := createDiskFile(NETDUMP_FILENAME + '_server')
2211 else
2212 NetDumpFile := createDiskFile(NETDUMP_FILENAME + '_client');
2213 end;
2215 procedure g_Net_DumpSendBuffer();
2216 begin
2217 writeInt(NetDumpFile, gTime);
2218 writeInt(NetDumpFile, LongWord(NetOut.CurSize));
2219 writeInt(NetDumpFile, Byte(1));
2220 NetDumpFile.WriteBuffer(NetOut.Data^, NetOut.CurSize);
2221 end;
2223 procedure g_Net_DumpRecvBuffer(Buf: penet_uint8; Len: LongWord);
2224 begin
2225 if (Buf = nil) or (Len = 0) then Exit;
2226 writeInt(NetDumpFile, gTime);
2227 writeInt(NetDumpFile, Len);
2228 writeInt(NetDumpFile, Byte(0));
2229 NetDumpFile.WriteBuffer(Buf^, Len);
2230 end;
2232 procedure g_Net_DumpEnd();
2233 begin
2234 NetDumpFile.Free();
2235 NetDumpFile := nil;
2236 end;
2238 function g_Net_ForwardPorts(ForwardPongPort: Boolean = True): Boolean;
2239 {$IFDEF USE_MINIUPNPC}
2240 var
2241 DevList: PUPNPDev;
2242 Urls: TUPNPUrls;
2243 Data: TIGDDatas;
2244 LanAddr: array [0..255] of Char;
2245 StrPort: AnsiString;
2246 Err, I: Integer;
2247 begin
2248 Result := False;
2250 if NetPortForwarded = NetPort then
2251 begin
2252 Result := True;
2253 exit;
2254 end;
2256 NetPongForwarded := False;
2257 NetPortForwarded := 0;
2259 DevList := upnpDiscover(1000, nil, nil, 0, 0, 2, Addr(Err));
2260 if DevList = nil then
2261 begin
2262 conwritefln('port forwarding failed: upnpDiscover() failed: %d', [Err]);
2263 exit;
2264 end;
2266 I := UPNP_GetValidIGD(DevList, @Urls, @Data, Addr(LanAddr[0]), 256);
2268 if I = 0 then
2269 begin
2270 conwriteln('port forwarding failed: could not find an IGD device on this LAN');
2271 FreeUPNPDevList(DevList);
2272 FreeUPNPUrls(@Urls);
2273 exit;
2274 end;
2276 StrPort := IntToStr(NetPort);
2277 I := UPNP_AddPortMapping(
2278 Urls.controlURL, Addr(data.first.servicetype[1]),
2279 PChar(StrPort), PChar(StrPort), Addr(LanAddr[0]), PChar('D2DF'),
2280 PChar('UDP'), nil, PChar('0')
2281 );
2283 if I <> 0 then
2284 begin
2285 conwritefln('forwarding port %d failed: error %d', [NetPort, I]);
2286 FreeUPNPDevList(DevList);
2287 FreeUPNPUrls(@Urls);
2288 exit;
2289 end;
2291 if ForwardPongPort then
2292 begin
2293 StrPort := IntToStr(NET_PING_PORT);
2294 I := UPNP_AddPortMapping(
2295 Urls.controlURL, Addr(data.first.servicetype[1]),
2296 PChar(StrPort), PChar(StrPort), Addr(LanAddr[0]), PChar('D2DF'),
2297 PChar('UDP'), nil, PChar('0')
2298 );
2300 if I <> 0 then
2301 begin
2302 conwritefln('forwarding port %d failed: error %d', [NetPort + 1, I]);
2303 NetPongForwarded := False;
2304 end
2305 else
2306 begin
2307 conwritefln('forwarded port %d successfully', [NetPort + 1]);
2308 NetPongForwarded := True;
2309 end;
2310 end;
2312 conwritefln('forwarded port %d successfully', [NetPort]);
2313 NetIGDControl := AnsiString(Urls.controlURL);
2314 NetIGDService := data.first.servicetype;
2315 NetPortForwarded := NetPort;
2317 FreeUPNPDevList(DevList);
2318 FreeUPNPUrls(@Urls);
2319 Result := True;
2320 end;
2321 {$ELSE}
2322 begin
2323 Result := False;
2324 end;
2325 {$ENDIF}
2327 procedure g_Net_UnforwardPorts();
2328 {$IFDEF USE_MINIUPNPC}
2329 var
2330 I: Integer;
2331 StrPort: AnsiString;
2332 begin
2333 if NetPortForwarded = 0 then Exit;
2335 conwriteln('unforwarding ports...');
2337 StrPort := IntToStr(NetPortForwarded);
2338 I := UPNP_DeletePortMapping(
2339 PChar(NetIGDControl), Addr(NetIGDService[1]),
2340 PChar(StrPort), PChar('UDP'), nil
2341 );
2342 conwritefln(' port %d: %d', [NetPortForwarded, I]);
2344 if NetPongForwarded then
2345 begin
2346 NetPongForwarded := False;
2347 StrPort := IntToStr(NetPortForwarded + 1);
2348 I := UPNP_DeletePortMapping(
2349 PChar(NetIGDControl), Addr(NetIGDService[1]),
2350 PChar(StrPort), PChar('UDP'), nil
2351 );
2352 conwritefln(' port %d: %d', [NetPortForwarded + 1, I]);
2353 end;
2355 NetPortForwarded := 0;
2356 end;
2357 {$ELSE}
2358 begin
2359 end;
2360 {$ENDIF}
2362 initialization
2363 conRegVar('cl_downloadtimeout', @g_Net_DownloadTimeout, 0.0, 1000000.0, '', 'timeout in seconds, 0 to disable it');
2364 SetLength(NetClients, 0);
2365 g_Net_DownloadTimeout := 60;
2366 NetIn.Alloc(NET_BUFSIZE);
2367 NetOut.Alloc(NET_BUFSIZE);
2368 NetBuf[NET_UNRELIABLE].Alloc(NET_BUFSIZE*2);
2369 NetBuf[NET_RELIABLE].Alloc(NET_BUFSIZE*2);
2370 finalization
2371 NetIn.Free();
2372 NetOut.Free();
2373 NetBuf[NET_UNRELIABLE].Free();
2374 NetBuf[NET_RELIABLE].Free();
2375 end.