DEADSOFTWARE

Net: Buffer outgoing messages
[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, either version 3 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *)
16 {$INCLUDE ../shared/a_modes.inc}
17 unit g_net;
19 interface
21 uses
22 e_log, e_msg, ENet, Classes, MAPDEF{$IFDEF USE_MINIUPNPC}, miniupnpc;{$ELSE};{$ENDIF}
24 const
25 NET_PROTOCOL_VER = 181;
27 NET_MAXCLIENTS = 24;
28 NET_CHANS = 11;
30 NET_CHAN_SERVICE = 0;
31 NET_CHAN_IMPORTANT = 1;
32 NET_CHAN_GAME = 2;
33 NET_CHAN_PLAYER = 3;
34 NET_CHAN_PLAYERPOS = 4;
35 NET_CHAN_MONSTER = 5;
36 NET_CHAN_MONSTERPOS = 6;
37 NET_CHAN_LARGEDATA = 7;
38 NET_CHAN_CHAT = 8;
39 NET_CHAN_DOWNLOAD = 9;
40 NET_CHAN_SHOTS = 10;
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;
65 NET_STATE_NONE = 0;
66 NET_STATE_AUTH = 1;
67 NET_STATE_GAME = 2;
69 NET_CONNECT_TIMEOUT = 1000 * 10;
71 BANLIST_FILENAME = 'banlist.txt';
72 NETDUMP_FILENAME = 'netdump';
74 {$IFDEF FREEBSD}
75 NilThreadId = nil;
76 {$ELSE}
77 NilThreadId = 0;
78 {$ENDIF}
80 type
81 TNetClient = record
82 ID: Byte;
83 Used: Boolean;
84 State: Byte;
85 Peer: pENetPeer;
86 Player: Word;
87 RequestedFullUpdate: Boolean;
88 RCONAuth: Boolean;
89 Voted: Boolean;
90 NetOut: array [0..1] of TMsg;
91 end;
92 TBanRecord = record
93 IP: LongWord;
94 Perm: Boolean;
95 end;
96 pTNetClient = ^TNetClient;
98 AByte = array of Byte;
100 var
101 NetInitDone: Boolean = False;
102 NetMode: Byte = NET_NONE;
103 NetDump: Boolean = False;
105 NetServerName: string = 'Unnamed Server';
106 NetPassword: string = '';
107 NetPort: Word = 25666;
109 NetAllowRCON: Boolean = False;
110 NetRCONPassword: string = '';
112 NetTimeToUpdate: Cardinal = 0;
113 NetTimeToReliable: Cardinal = 0;
114 NetTimeToMaster: Cardinal = 0;
116 NetHost: pENetHost = nil;
117 NetPeer: pENetPeer = nil;
118 NetEvent: ENetEvent;
119 NetAddr: ENetAddress;
121 NetPongAddr: ENetAddress;
122 NetPongSock: ENetSocket = ENET_SOCKET_NULL;
124 NetUseMaster: Boolean = True;
125 NetSlistAddr: ENetAddress;
126 NetSlistIP: string = 'mpms.doom2d.org';
127 NetSlistPort: Word = 25665;
129 NetClientIP: string = '127.0.0.1';
130 NetClientPort: Word = 25666;
132 NetIn, NetOut: TMsg;
133 NetBuf: array [0..1] of TMsg;
135 NetClients: array of TNetClient;
136 NetClientCount: Byte = 0;
137 NetMaxClients: Byte = 255;
138 NetBannedHosts: array of TBanRecord;
140 NetState: Integer = NET_STATE_NONE;
142 NetMyID: Integer = -1;
143 NetPlrUID1: Integer = -1;
144 NetPlrUID2: Integer = -1;
146 NetInterpLevel: Integer = 1;
147 NetUpdateRate: Cardinal = 0; // as soon as possible
148 NetRelupdRate: Cardinal = 18; // around two times a second
149 NetMasterRate: Cardinal = 60000;
151 NetForcePlayerUpdate: Boolean = False;
152 NetPredictSelf: Boolean = True;
153 NetForwardPorts: Boolean = False;
155 NetGotEverything: Boolean = False;
156 NetGotKeys: Boolean = False;
158 {$IFDEF USE_MINIUPNPC}
159 NetPortForwarded: Word = 0;
160 NetPongForwarded: Boolean = False;
161 NetIGDControl: AnsiString;
162 NetIGDService: TURLStr;
163 {$ENDIF}
165 NetPortThread: TThreadID = NilThreadId;
167 NetDumpFile: TStream;
169 function g_Net_Init(): Boolean;
170 procedure g_Net_Cleanup();
171 procedure g_Net_Free();
172 procedure g_Net_Flush();
174 function g_Net_Host(IPAddr: LongWord; Port: enet_uint16; MaxClients: Cardinal = 16): Boolean;
175 procedure g_Net_Host_Die();
176 procedure g_Net_Host_Send(ID: Integer; Reliable: Boolean; Chan: Byte = NET_CHAN_GAME);
177 function g_Net_Host_Update(): enet_size_t;
179 function g_Net_Connect(IP: string; Port: enet_uint16): Boolean;
180 procedure g_Net_Disconnect(Forced: Boolean = False);
181 procedure g_Net_Client_Send(Reliable: Boolean; Chan: Byte = NET_CHAN_GAME);
182 function g_Net_Client_Update(): enet_size_t;
183 function g_Net_Client_UpdateWhileLoading(): enet_size_t;
185 function g_Net_Client_ByName(Name: string): pTNetClient;
186 function g_Net_Client_ByPlayer(PID: Word): pTNetClient;
187 function g_Net_ClientName_ByID(ID: Integer): string;
189 procedure g_Net_SendData(Data: AByte; peer: pENetPeer; Reliable: Boolean; Chan: Byte = NET_CHAN_DOWNLOAD);
190 function g_Net_Wait_Event(msgId: Word): TMemoryStream;
192 function IpToStr(IP: LongWord): string;
193 function StrToIp(IPstr: string; var IP: LongWord): Boolean;
195 function g_Net_IsHostBanned(IP: LongWord; Perm: Boolean = False): Boolean;
196 procedure g_Net_BanHost(IP: LongWord; Perm: Boolean = True); overload;
197 procedure g_Net_BanHost(IP: string; Perm: Boolean = True); overload;
198 function g_Net_UnbanHost(IP: string): Boolean; overload;
199 function g_Net_UnbanHost(IP: LongWord): Boolean; overload;
200 procedure g_Net_UnbanNonPermHosts();
201 procedure g_Net_SaveBanList();
203 procedure g_Net_DumpStart();
204 procedure g_Net_DumpSendBuffer();
205 procedure g_Net_DumpRecvBuffer(Buf: penet_uint8; Len: LongWord);
206 procedure g_Net_DumpEnd();
208 function g_Net_ForwardPorts(ForwardPongPort: Boolean = True): Boolean;
209 procedure g_Net_UnforwardPorts();
211 implementation
213 uses
214 SysUtils,
215 e_input, g_nethandler, g_netmsg, g_netmaster, g_player, g_window, g_console,
216 g_main, g_game, g_language, g_weapons, utils, ctypes;
218 var
219 g_Net_DownloadTimeout: Single;
222 { /// SERVICE FUNCTIONS /// }
225 function g_Net_FindSlot(): Integer;
226 var
227 I: Integer;
228 F: Boolean;
229 N, C: Integer;
230 begin
231 N := -1;
232 F := False;
233 C := 0;
234 for I := Low(NetClients) to High(NetClients) do
235 begin
236 if NetClients[I].Used then
237 Inc(C)
238 else
239 if not F then
240 begin
241 F := True;
242 N := I;
243 end;
244 end;
245 if C >= NetMaxClients then
246 begin
247 Result := -1;
248 Exit;
249 end;
251 if not F then
252 begin
253 if (Length(NetClients) >= NetMaxClients) then
254 N := -1
255 else
256 begin
257 SetLength(NetClients, Length(NetClients) + 1);
258 N := High(NetClients);
259 end;
260 end;
262 if N >= 0 then
263 begin
264 NetClients[N].Used := True;
265 NetClients[N].ID := N;
266 NetClients[N].RequestedFullUpdate := False;
267 NetClients[N].RCONAuth := False;
268 NetClients[N].Voted := False;
269 NetClients[N].Player := 0;
270 end;
272 Result := N;
273 end;
275 function g_Net_Init(): Boolean;
276 var
277 F: TextFile;
278 IPstr: string;
279 IP: LongWord;
280 begin
281 NetIn.Clear();
282 NetOut.Clear();
283 NetBuf[NET_UNRELIABLE].Clear();
284 NetBuf[NET_RELIABLE].Clear();
285 SetLength(NetClients, 0);
286 NetPeer := nil;
287 NetHost := nil;
288 NetMyID := -1;
289 NetPlrUID1 := -1;
290 NetPlrUID2 := -1;
291 NetAddr.port := 25666;
292 SetLength(NetBannedHosts, 0);
293 if FileExists(DataDir + BANLIST_FILENAME) then
294 begin
295 Assign(F, DataDir + BANLIST_FILENAME);
296 Reset(F);
297 while not EOF(F) do
298 begin
299 Readln(F, IPstr);
300 if StrToIp(IPstr, IP) then
301 g_Net_BanHost(IP);
302 end;
303 CloseFile(F);
304 g_Net_SaveBanList();
305 end;
307 Result := (enet_initialize() = 0);
308 end;
310 procedure g_Net_Flush();
311 var
312 T: Integer;
313 P: pENetPacket;
314 F, Chan: enet_uint32;
315 I: Integer;
316 begin
317 F := 0;
318 Chan := NET_CHAN_GAME;
320 if NetMode = NET_SERVER then
321 for T := NET_UNRELIABLE to NET_RELIABLE do
322 begin
323 if NetBuf[T].CurSize > 0 then
324 begin
325 P := enet_packet_create(NetBuf[T].Data, NetBuf[T].CurSize, F);
326 if not Assigned(P) then continue;
327 enet_host_broadcast(NetHost, Chan, P);
328 NetBuf[T].Clear();
329 end;
331 for I := Low(NetClients) to High(NetClients) do
332 begin
333 if not NetClients[I].Used then continue;
334 if NetClients[I].NetOut[T].CurSize <= 0 then continue;
335 P := enet_packet_create(NetClients[I].NetOut[T].Data, NetClients[I].NetOut[T].CurSize, F);
336 if not Assigned(P) then continue;
337 enet_peer_send(NetClients[I].Peer, Chan, P);
338 NetClients[I].NetOut[T].Clear();
339 end;
341 // next and last iteration is always RELIABLE
342 F := LongWord(ENET_PACKET_FLAG_RELIABLE);
343 Chan := NET_CHAN_IMPORTANT;
344 end
345 else if NetMode = NET_CLIENT then
346 for T := NET_UNRELIABLE to NET_RELIABLE do
347 begin
348 if NetBuf[T].CurSize > 0 then
349 begin
350 P := enet_packet_create(NetBuf[T].Data, NetBuf[T].CurSize, F);
351 if not Assigned(P) then continue;
352 enet_peer_send(NetPeer, Chan, P);
353 NetBuf[T].Clear();
354 end;
355 // next and last iteration is always RELIABLE
356 F := LongWord(ENET_PACKET_FLAG_RELIABLE);
357 Chan := NET_CHAN_IMPORTANT;
358 end;
359 end;
361 procedure g_Net_Cleanup();
362 begin
363 NetIn.Clear();
364 NetOut.Clear();
365 NetBuf[NET_UNRELIABLE].Clear();
366 NetBuf[NET_RELIABLE].Clear();
368 SetLength(NetClients, 0);
369 NetClientCount := 0;
371 NetPeer := nil;
372 NetHost := nil;
373 NetMPeer := nil;
374 NetMHost := nil;
375 NetMyID := -1;
376 NetPlrUID1 := -1;
377 NetPlrUID2 := -1;
378 NetState := NET_STATE_NONE;
380 NetPongSock := ENET_SOCKET_NULL;
382 NetTimeToMaster := 0;
383 NetTimeToUpdate := 0;
384 NetTimeToReliable := 0;
386 NetMode := NET_NONE;
388 if NetPortThread <> NilThreadId then
389 WaitForThreadTerminate(NetPortThread, 66666);
391 NetPortThread := NilThreadId;
392 g_Net_UnforwardPorts();
394 if NetDump then
395 g_Net_DumpEnd();
396 end;
398 procedure g_Net_Free();
399 begin
400 g_Net_Cleanup();
402 enet_deinitialize();
403 NetInitDone := False;
404 end;
407 { /// SERVER FUNCTIONS /// }
410 function ForwardThread(Param: Pointer): PtrInt;
411 begin
412 Result := 0;
413 if not g_Net_ForwardPorts() then Result := -1;
414 end;
416 function g_Net_Host(IPAddr: LongWord; Port: enet_uint16; MaxClients: Cardinal = 16): Boolean;
417 begin
418 if NetMode <> NET_NONE then
419 begin
420 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_INGAME]);
421 Result := False;
422 Exit;
423 end;
425 Result := True;
427 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_HOST], [Port]));
428 if not NetInitDone then
429 begin
430 if (not g_Net_Init()) then
431 begin
432 g_Console_Add(_lc[I_NET_MSG_FERROR] + _lc[I_NET_ERR_ENET]);
433 Result := False;
434 Exit;
435 end
436 else
437 NetInitDone := True;
438 end;
440 NetAddr.host := IPAddr;
441 NetAddr.port := Port;
443 if NetForwardPorts then NetPortThread := BeginThread(ForwardThread);
445 NetHost := enet_host_create(@NetAddr, NET_MAXCLIENTS, NET_CHANS, 0, 0);
447 if (NetHost = nil) then
448 begin
449 g_Console_Add(_lc[I_NET_MSG_ERROR] + Format(_lc[I_NET_ERR_HOST], [Port]));
450 Result := False;
451 g_Net_Cleanup;
452 Exit;
453 end;
455 NetPongSock := enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
456 if NetPongSock <> ENET_SOCKET_NULL then
457 begin
458 NetPongAddr.host := IPAddr;
459 NetPongAddr.port := NET_PING_PORT;
460 if enet_socket_bind(NetPongSock, @NetPongAddr) < 0 then
461 begin
462 enet_socket_destroy(NetPongSock);
463 NetPongSock := ENET_SOCKET_NULL;
464 end
465 else
466 enet_socket_set_option(NetPongSock, ENET_SOCKOPT_NONBLOCK, 1);
467 end;
469 NetMode := NET_SERVER;
470 NetOut.Clear();
471 NetBuf[NET_UNRELIABLE].Clear();
472 NetBuf[NET_RELIABLE].Clear();
474 if NetDump then
475 g_Net_DumpStart();
476 end;
478 procedure g_Net_Host_Die();
479 var
480 I: Integer;
481 begin
482 if NetMode <> NET_SERVER then Exit;
484 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_DISCALL]);
485 for I := 0 to High(NetClients) do
486 if NetClients[I].Used then
487 enet_peer_disconnect(NetClients[I].Peer, NET_DISC_DOWN);
489 while enet_host_service(NetHost, @NetEvent, 1000) > 0 do
490 if NetEvent.kind = ENET_EVENT_TYPE_RECEIVE then
491 enet_packet_destroy(NetEvent.packet);
493 for I := 0 to High(NetClients) do
494 if NetClients[I].Used then
495 begin
496 FreeMemory(NetClients[I].Peer^.data);
497 NetClients[I].Peer^.data := nil;
498 enet_peer_reset(NetClients[I].Peer);
499 NetClients[I].Peer := nil;
500 NetClients[I].Used := False;
501 NetClients[I].NetOut[NET_UNRELIABLE].Free();
502 NetClients[I].NetOut[NET_RELIABLE].Free();
503 end;
505 if (NetMPeer <> nil) and (NetMHost <> nil) then g_Net_Slist_Disconnect;
506 if NetPongSock <> ENET_SOCKET_NULL then
507 enet_socket_destroy(NetPongSock);
509 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_DIE]);
510 enet_host_destroy(NetHost);
512 NetMode := NET_NONE;
514 g_Net_Cleanup;
515 e_WriteLog('NET: Server stopped', TMsgType.Notify);
516 end;
519 procedure g_Net_Host_Send(ID: Integer; Reliable: Boolean; Chan: Byte = NET_CHAN_GAME);
520 var
521 T: Integer;
522 begin
523 if (Reliable) then
524 T := NET_RELIABLE
525 else
526 T := NET_UNRELIABLE;
528 if (ID >= 0) then
529 begin
530 if ID > High(NetClients) then Exit;
531 if NetClients[ID].Peer = nil then Exit;
532 // write size first
533 NetClients[ID].NetOut[T].Write(Integer(NetOut.CurSize));
534 NetClients[ID].NetOut[T].Write(NetOut);
535 end
536 else
537 begin
538 // write size first
539 NetBuf[T].Write(Integer(NetOut.CurSize));
540 NetBuf[T].Write(NetOut);
541 end;
543 if NetDump then g_Net_DumpSendBuffer();
544 NetOut.Clear();
545 end;
547 procedure g_Net_Host_CheckPings();
548 var
549 ClAddr: ENetAddress;
550 Buf: ENetBuffer;
551 Len: Integer;
552 ClTime: Int64;
553 Ping: array [0..9] of Byte;
554 NPl: Byte;
555 begin
556 if NetPongSock = ENET_SOCKET_NULL then Exit;
558 Buf.data := Addr(Ping[0]);
559 Buf.dataLength := 2+8;
561 Ping[0] := 0;
563 Len := enet_socket_receive(NetPongSock, @ClAddr, @Buf, 1);
564 if Len < 0 then Exit;
566 if (Ping[0] = Ord('D')) and (Ping[1] = Ord('F')) then
567 begin
568 ClTime := Int64(Addr(Ping[2])^);
570 NetOut.Clear();
571 NetOut.Write(Byte(Ord('D')));
572 NetOut.Write(Byte(Ord('F')));
573 NetOut.Write(NetPort);
574 NetOut.Write(ClTime);
575 g_Net_Slist_WriteInfo();
576 NPl := 0;
577 if gPlayer1 <> nil then Inc(NPl);
578 if gPlayer2 <> nil then Inc(NPl);
579 NetOut.Write(NPl);
580 NetOut.Write(gNumBots);
582 Buf.data := NetOut.Data;
583 Buf.dataLength := NetOut.CurSize;
584 enet_socket_send(NetPongSock, @ClAddr, @Buf, 1);
586 NetOut.Clear();
587 end;
588 end;
590 function g_Net_Host_Update(): enet_size_t;
591 var
592 IP: string;
593 Port: Word;
594 ID: Integer;
595 TC: pTNetClient;
596 TP: TPlayer;
597 begin
598 IP := '';
599 Result := 0;
601 if NetUseMaster then
602 g_Net_Slist_Check;
603 g_Net_Host_CheckPings;
605 while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
606 begin
607 case (NetEvent.kind) of
608 ENET_EVENT_TYPE_CONNECT:
609 begin
610 IP := IpToStr(NetEvent.Peer^.address.host);
611 Port := NetEvent.Peer^.address.port;
612 g_Console_Add(_lc[I_NET_MSG] +
613 Format(_lc[I_NET_MSG_HOST_CONN], [IP, Port]));
615 if (NetEvent.data <> NET_PROTOCOL_VER) then
616 begin
617 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_REJECT] +
618 _lc[I_NET_DISC_PROTOCOL]);
619 NetEvent.peer^.data := GetMemory(SizeOf(Byte));
620 Byte(NetEvent.peer^.data^) := 255;
621 enet_peer_disconnect(NetEvent.peer, NET_DISC_PROTOCOL);
622 enet_host_flush(NetHost);
623 Exit;
624 end;
626 ID := g_Net_FindSlot();
628 if ID < 0 then
629 begin
630 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_REJECT] +
631 _lc[I_NET_DISC_FULL]);
632 NetEvent.Peer^.data := GetMemory(SizeOf(Byte));
633 Byte(NetEvent.peer^.data^) := 255;
634 enet_peer_disconnect(NetEvent.peer, NET_DISC_FULL);
635 enet_host_flush(NetHost);
636 Exit;
637 end;
639 NetClients[ID].Peer := NetEvent.peer;
640 NetClients[ID].Peer^.data := GetMemory(SizeOf(Byte));
641 Byte(NetClients[ID].Peer^.data^) := ID;
642 NetClients[ID].State := NET_STATE_AUTH;
643 NetClients[ID].RCONAuth := False;
644 NetClients[ID].NetOut[NET_UNRELIABLE].Alloc(NET_BUFSIZE*2);
645 NetClients[ID].NetOut[NET_RELIABLE].Alloc(NET_BUFSIZE*2);
647 enet_peer_timeout(NetEvent.peer, ENET_PEER_TIMEOUT_LIMIT * 2, ENET_PEER_TIMEOUT_MINIMUM * 2, ENET_PEER_TIMEOUT_MAXIMUM * 2);
649 Inc(NetClientCount);
650 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_HOST_ADD], [ID]));
651 end;
653 ENET_EVENT_TYPE_RECEIVE:
654 begin
655 ID := Byte(NetEvent.peer^.data^);
656 if ID > High(NetClients) then Exit;
657 TC := @NetClients[ID];
659 if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
660 g_Net_Host_HandlePacket(TC, NetEvent.packet, g_Net_HostMsgHandler);
661 end;
663 ENET_EVENT_TYPE_DISCONNECT:
664 begin
665 ID := Byte(NetEvent.peer^.data^);
666 if ID > High(NetClients) then Exit;
667 TC := @NetClients[ID];
668 if TC = nil then Exit;
670 if not (TC^.Used) then Exit;
672 TP := g_Player_Get(TC^.Player);
674 if TP <> nil then
675 begin
676 TP.Lives := 0;
677 TP.Kill(K_SIMPLEKILL, 0, HIT_DISCON);
678 g_Console_Add(Format(_lc[I_PLAYER_LEAVE], [TP.Name]), True);
679 e_WriteLog('NET: Client ' + TP.Name + ' [' + IntToStr(ID) + '] disconnected.', TMsgType.Notify);
680 g_Player_Remove(TP.UID);
681 end;
683 TC^.Used := False;
684 TC^.State := NET_STATE_NONE;
685 TC^.Peer := nil;
686 TC^.Player := 0;
687 TC^.RequestedFullUpdate := False;
688 TC^.NetOut[NET_UNRELIABLE].Free();
689 TC^.NetOut[NET_RELIABLE].Free();
691 FreeMemory(NetEvent.peer^.data);
692 NetEvent.peer^.data := nil;
693 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_HOST_DISC], [ID]));
694 Dec(NetClientCount);
696 if NetUseMaster then g_Net_Slist_Update;
697 end;
698 end;
699 end;
700 end;
703 { /// CLIENT FUNCTIONS /// }
706 procedure g_Net_Disconnect(Forced: Boolean = False);
707 begin
708 if NetMode <> NET_CLIENT then Exit;
709 if (NetHost = nil) or (NetPeer = nil) then Exit;
711 if not Forced then
712 begin
713 enet_peer_disconnect(NetPeer, NET_DISC_NONE);
715 while (enet_host_service(NetHost, @NetEvent, 1500) > 0) do
716 begin
717 if (NetEvent.kind = ENET_EVENT_TYPE_DISCONNECT) then
718 begin
719 NetPeer := nil;
720 break;
721 end;
723 if (NetEvent.kind = ENET_EVENT_TYPE_RECEIVE) then
724 enet_packet_destroy(NetEvent.packet);
725 end;
727 if NetPeer <> nil then
728 begin
729 enet_peer_reset(NetPeer);
730 NetPeer := nil;
731 end;
732 end
733 else
734 begin
735 e_WriteLog('NET: Kicked from server: ' + IntToStr(NetEvent.data), TMsgType.Notify);
736 if (NetEvent.data <= NET_DISC_MAX) then
737 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_KICK] +
738 _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + NetEvent.data)], True);
739 end;
741 if NetHost <> nil then
742 begin
743 enet_host_destroy(NetHost);
744 NetHost := nil;
745 end;
746 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_CLIENT_DISC]);
748 g_Net_Cleanup;
749 e_WriteLog('NET: Disconnected', TMsgType.Notify);
750 end;
752 procedure g_Net_Client_Send(Reliable: Boolean; Chan: Byte = NET_CHAN_GAME);
753 var
754 T: Integer;
755 begin
756 if (Reliable) then
757 T := NET_RELIABLE
758 else
759 T := NET_UNRELIABLE;
761 // write size first
762 NetBuf[T].Write(Integer(NetOut.CurSize));
763 NetBuf[T].Write(NetOut);
765 if NetDump then g_Net_DumpSendBuffer();
766 NetOut.Clear();
767 g_Net_Flush(); // FIXME: for now, send immediately
768 end;
770 function g_Net_Client_Update(): enet_size_t;
771 begin
772 Result := 0;
773 while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
774 begin
775 case NetEvent.kind of
776 ENET_EVENT_TYPE_RECEIVE:
777 begin
778 if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
779 g_Net_Client_HandlePacket(NetEvent.packet, g_Net_ClientMsgHandler);
780 end;
782 ENET_EVENT_TYPE_DISCONNECT:
783 begin
784 g_Net_Disconnect(True);
785 Result := 1;
786 Exit;
787 end;
788 end;
789 end
790 end;
792 function g_Net_Client_UpdateWhileLoading(): enet_size_t;
793 begin
794 Result := 0;
795 while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
796 begin
797 case NetEvent.kind of
798 ENET_EVENT_TYPE_RECEIVE:
799 begin
800 if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
801 g_Net_Client_HandlePacket(NetEvent.packet, g_Net_ClientLightMsgHandler);
802 end;
804 ENET_EVENT_TYPE_DISCONNECT:
805 begin
806 g_Net_Disconnect(True);
807 Result := 1;
808 Exit;
809 end;
810 end;
811 end;
812 g_Net_Flush();
813 end;
815 function g_Net_Connect(IP: string; Port: enet_uint16): Boolean;
816 var
817 OuterLoop: Boolean;
818 TimeoutTime, T: Int64;
819 begin
820 if NetMode <> NET_NONE then
821 begin
822 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_ERR_INGAME], True);
823 Result := False;
824 Exit;
825 end;
827 Result := True;
829 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_CLIENT_CONN],
830 [IP, Port]));
831 if not NetInitDone then
832 begin
833 if (not g_Net_Init()) then
834 begin
835 g_Console_Add(_lc[I_NET_MSG_FERROR] + _lc[I_NET_ERR_ENET], True);
836 Result := False;
837 Exit;
838 end
839 else
840 NetInitDone := True;
841 end;
843 NetHost := enet_host_create(nil, 1, NET_CHANS, 0, 0);
845 if (NetHost = nil) then
846 begin
847 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CLIENT], True);
848 g_Net_Cleanup;
849 Result := False;
850 Exit;
851 end;
853 enet_address_set_host(@NetAddr, PChar(Addr(IP[1])));
854 NetAddr.port := Port;
856 NetPeer := enet_host_connect(NetHost, @NetAddr, NET_CHANS, NET_PROTOCOL_VER);
858 if (NetPeer = nil) then
859 begin
860 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CLIENT], True);
861 enet_host_destroy(NetHost);
862 g_Net_Cleanup;
863 Result := False;
864 Exit;
865 end;
867 // предупредить что ждем слишком долго через N секунд
868 TimeoutTime := GetTimer() + NET_CONNECT_TIMEOUT;
870 OuterLoop := True;
871 while OuterLoop do
872 begin
873 while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
874 begin
875 if (NetEvent.kind = ENET_EVENT_TYPE_CONNECT) then
876 begin
877 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_CLIENT_DONE]);
878 NetMode := NET_CLIENT;
879 NetOut.Clear();
880 enet_peer_timeout(NetPeer, ENET_PEER_TIMEOUT_LIMIT * 2, ENET_PEER_TIMEOUT_MINIMUM * 2, ENET_PEER_TIMEOUT_MAXIMUM * 2);
881 NetClientIP := IP;
882 NetClientPort := Port;
883 if NetDump then
884 g_Net_DumpStart();
885 Exit;
886 end;
887 end;
889 T := GetTimer();
890 if T > TimeoutTime then
891 begin
892 TimeoutTime := T + NET_CONNECT_TIMEOUT * 100; // одного предупреждения хватит
893 g_Console_Add(_lc[I_NET_MSG_TIMEOUT_WARN], True);
894 g_Console_Add(Format(_lc[I_NET_MSG_PORTS], [Integer(Port), Integer(NET_PING_PORT)]), True);
895 end;
897 ProcessLoading(true);
899 if e_KeyPressed(IK_SPACE) or e_KeyPressed(IK_ESCAPE) or e_KeyPressed(VK_ESCAPE) or
900 e_KeyPressed(JOY0_JUMP) or e_KeyPressed(JOY1_JUMP) or e_KeyPressed(JOY2_JUMP) or e_KeyPressed(JOY3_JUMP) then
901 OuterLoop := False;
902 end;
904 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_TIMEOUT], True);
905 g_Console_Add(Format(_lc[I_NET_MSG_PORTS], [Integer(Port), Integer(NET_PING_PORT)]), True);
906 if NetPeer <> nil then enet_peer_reset(NetPeer);
907 if NetHost <> nil then
908 begin
909 enet_host_destroy(NetHost);
910 NetHost := nil;
911 end;
912 g_Net_Cleanup();
913 Result := False;
914 end;
916 function IpToStr(IP: LongWord): string;
917 var
918 Ptr: Pointer;
919 begin
920 Ptr := Addr(IP);
921 Result := IntToStr(PByte(Ptr + 0)^) + '.';
922 Result := Result + IntToStr(PByte(Ptr + 1)^) + '.';
923 Result := Result + IntToStr(PByte(Ptr + 2)^) + '.';
924 Result := Result + IntToStr(PByte(Ptr + 3)^);
925 end;
927 function StrToIp(IPstr: string; var IP: LongWord): Boolean;
928 var
929 EAddr: ENetAddress;
930 begin
931 Result := enet_address_set_host(@EAddr, PChar(@IPstr[1])) = 0;
932 IP := EAddr.host;
933 end;
935 function g_Net_Client_ByName(Name: string): pTNetClient;
936 var
937 a: Integer;
938 pl: TPlayer;
939 begin
940 Result := nil;
941 for a := Low(NetClients) to High(NetClients) do
942 if (NetClients[a].Used) and (NetClients[a].State = NET_STATE_GAME) then
943 begin
944 pl := g_Player_Get(NetClients[a].Player);
945 if pl = nil then continue;
946 if Copy(LowerCase(pl.Name), 1, Length(Name)) <> LowerCase(Name) then continue;
947 if NetClients[a].Peer <> nil then
948 begin
949 Result := @NetClients[a];
950 Exit;
951 end;
952 end;
953 end;
955 function g_Net_Client_ByPlayer(PID: Word): pTNetClient;
956 var
957 a: Integer;
958 begin
959 Result := nil;
960 for a := Low(NetClients) to High(NetClients) do
961 if (NetClients[a].Used) and (NetClients[a].State = NET_STATE_GAME) then
962 if NetClients[a].Player = PID then
963 begin
964 Result := @NetClients[a];
965 Exit;
966 end;
967 end;
969 function g_Net_ClientName_ByID(ID: Integer): string;
970 var
971 a: Integer;
972 pl: TPlayer;
973 begin
974 Result := '';
975 if ID = NET_EVERYONE then
976 Exit;
977 for a := Low(NetClients) to High(NetClients) do
978 if (NetClients[a].ID = ID) and (NetClients[a].Used) and (NetClients[a].State = NET_STATE_GAME) then
979 begin
980 pl := g_Player_Get(NetClients[a].Player);
981 if pl = nil then Exit;
982 Result := pl.Name;
983 end;
984 end;
986 procedure g_Net_SendData(Data: AByte; peer: pENetPeer; Reliable: Boolean; Chan: Byte = NET_CHAN_DOWNLOAD);
987 var
988 P: pENetPacket;
989 F: enet_uint32;
990 dataLength: Cardinal;
991 begin
992 dataLength := Length(Data);
994 if (Reliable) then
995 F := LongWord(ENET_PACKET_FLAG_RELIABLE)
996 else
997 F := 0;
999 if (peer <> nil) then
1000 begin
1001 P := enet_packet_create(@Data[0], dataLength, F);
1002 if not Assigned(P) then Exit;
1003 enet_peer_send(peer, Chan, P);
1004 end
1005 else
1006 begin
1007 P := enet_packet_create(@Data[0], dataLength, F);
1008 if not Assigned(P) then Exit;
1009 enet_host_broadcast(NetHost, Chan, P);
1010 end;
1012 enet_host_flush(NetHost);
1013 end;
1015 function UserRequestExit: Boolean;
1016 begin
1017 Result := e_KeyPressed(IK_SPACE) or
1018 e_KeyPressed(IK_ESCAPE) or
1019 e_KeyPressed(VK_ESCAPE) or
1020 e_KeyPressed(JOY0_JUMP) or
1021 e_KeyPressed(JOY1_JUMP) or
1022 e_KeyPressed(JOY2_JUMP) or
1023 e_KeyPressed(JOY3_JUMP)
1024 end;
1026 function g_Net_Wait_Event(msgId: Word): TMemoryStream;
1027 var
1028 ev: ENetEvent;
1029 rMsgId: Byte;
1030 Ptr: Pointer;
1031 stream: TMemoryStream;
1032 status: cint;
1033 begin
1034 FillChar(ev, SizeOf(ev), 0);
1035 stream := nil;
1036 repeat
1037 status := enet_host_service(NetHost, @ev, Trunc(g_Net_DownloadTimeout * 1000));
1038 if status > 0 then
1039 begin
1040 case ev.kind of
1041 ENET_EVENT_TYPE_RECEIVE:
1042 begin
1043 Ptr := ev.packet^.data;
1044 rMsgId := Byte(Ptr^);
1045 if rMsgId = msgId then
1046 begin
1047 stream := TMemoryStream.Create;
1048 stream.SetSize(ev.packet^.dataLength);
1049 stream.WriteBuffer(Ptr^, ev.packet^.dataLength);
1050 stream.Seek(0, soFromBeginning);
1051 status := 1 (* received *)
1052 end
1053 else
1054 begin
1055 (* looks that game state always received, so ignore it *)
1056 e_LogWritefln('g_Net_Wait_Event(%s): skip message %s', [msgId, rMsgId]);
1057 status := 2 (* continue *)
1058 end
1059 end;
1060 ENET_EVENT_TYPE_DISCONNECT:
1061 begin
1062 if (ev.data <= NET_DISC_MAX) then
1063 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' ' + _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + ev.data)], True);
1064 status := -2 (* error: disconnected *)
1065 end;
1066 else
1067 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' unknown ENet event ' + IntToStr(Ord(ev.kind)), True);
1068 status := -3 (* error: unknown event *)
1069 end;
1070 enet_packet_destroy(ev.packet)
1071 end
1072 else
1073 begin
1074 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' timeout reached', True);
1075 status := 0 (* error: timeout *)
1076 end;
1077 ProcessLoading(true);
1078 until (status <> 2) or UserRequestExit();
1079 Result := stream
1080 end;
1082 function g_Net_IsHostBanned(IP: LongWord; Perm: Boolean = False): Boolean;
1083 var
1084 I: Integer;
1085 begin
1086 Result := False;
1087 if NetBannedHosts = nil then
1088 Exit;
1089 for I := 0 to High(NetBannedHosts) do
1090 if (NetBannedHosts[I].IP = IP) and ((not Perm) or (NetBannedHosts[I].Perm)) then
1091 begin
1092 Result := True;
1093 break;
1094 end;
1095 end;
1097 procedure g_Net_BanHost(IP: LongWord; Perm: Boolean = True); overload;
1098 var
1099 I, P: Integer;
1100 begin
1101 if IP = 0 then
1102 Exit;
1103 if g_Net_IsHostBanned(IP, Perm) then
1104 Exit;
1106 P := -1;
1107 for I := Low(NetBannedHosts) to High(NetBannedHosts) do
1108 if NetBannedHosts[I].IP = 0 then
1109 begin
1110 P := I;
1111 break;
1112 end;
1114 if P < 0 then
1115 begin
1116 SetLength(NetBannedHosts, Length(NetBannedHosts) + 1);
1117 P := High(NetBannedHosts);
1118 end;
1120 NetBannedHosts[P].IP := IP;
1121 NetBannedHosts[P].Perm := Perm;
1122 end;
1124 procedure g_Net_BanHost(IP: string; Perm: Boolean = True); overload;
1125 var
1126 a: LongWord;
1127 b: Boolean;
1128 begin
1129 b := StrToIp(IP, a);
1130 if b then
1131 g_Net_BanHost(a, Perm);
1132 end;
1134 procedure g_Net_UnbanNonPermHosts();
1135 var
1136 I: Integer;
1137 begin
1138 if NetBannedHosts = nil then
1139 Exit;
1140 for I := Low(NetBannedHosts) to High(NetBannedHosts) do
1141 if (NetBannedHosts[I].IP > 0) and not NetBannedHosts[I].Perm then
1142 begin
1143 NetBannedHosts[I].IP := 0;
1144 NetBannedHosts[I].Perm := True;
1145 end;
1146 end;
1148 function g_Net_UnbanHost(IP: string): Boolean; overload;
1149 var
1150 a: LongWord;
1151 begin
1152 Result := StrToIp(IP, a);
1153 if Result then
1154 Result := g_Net_UnbanHost(a);
1155 end;
1157 function g_Net_UnbanHost(IP: LongWord): Boolean; overload;
1158 var
1159 I: Integer;
1160 begin
1161 Result := False;
1162 if IP = 0 then
1163 Exit;
1164 if NetBannedHosts = nil then
1165 Exit;
1166 for I := 0 to High(NetBannedHosts) do
1167 if NetBannedHosts[I].IP = IP then
1168 begin
1169 NetBannedHosts[I].IP := 0;
1170 NetBannedHosts[I].Perm := True;
1171 Result := True;
1172 // no break here to clear all bans of this host, perm and non-perm
1173 end;
1174 end;
1176 procedure g_Net_SaveBanList();
1177 var
1178 F: TextFile;
1179 I: Integer;
1180 begin
1181 Assign(F, DataDir + BANLIST_FILENAME);
1182 Rewrite(F);
1183 if NetBannedHosts <> nil then
1184 for I := 0 to High(NetBannedHosts) do
1185 if NetBannedHosts[I].Perm and (NetBannedHosts[I].IP > 0) then
1186 Writeln(F, IpToStr(NetBannedHosts[I].IP));
1187 CloseFile(F);
1188 end;
1190 procedure g_Net_DumpStart();
1191 begin
1192 if NetMode = NET_SERVER then
1193 NetDumpFile := createDiskFile(NETDUMP_FILENAME + '_server')
1194 else
1195 NetDumpFile := createDiskFile(NETDUMP_FILENAME + '_client');
1196 end;
1198 procedure g_Net_DumpSendBuffer();
1199 begin
1200 writeInt(NetDumpFile, gTime);
1201 writeInt(NetDumpFile, LongWord(NetOut.CurSize));
1202 writeInt(NetDumpFile, Byte(1));
1203 NetDumpFile.WriteBuffer(NetOut.Data^, NetOut.CurSize);
1204 end;
1206 procedure g_Net_DumpRecvBuffer(Buf: penet_uint8; Len: LongWord);
1207 begin
1208 if (Buf = nil) or (Len = 0) then Exit;
1209 writeInt(NetDumpFile, gTime);
1210 writeInt(NetDumpFile, Len);
1211 writeInt(NetDumpFile, Byte(0));
1212 NetDumpFile.WriteBuffer(Buf^, Len);
1213 end;
1215 procedure g_Net_DumpEnd();
1216 begin
1217 NetDumpFile.Free();
1218 NetDumpFile := nil;
1219 end;
1221 function g_Net_ForwardPorts(ForwardPongPort: Boolean = True): Boolean;
1222 {$IFDEF USE_MINIUPNPC}
1223 var
1224 DevList: PUPNPDev;
1225 Urls: TUPNPUrls;
1226 Data: TIGDDatas;
1227 LanAddr: array [0..255] of Char;
1228 StrPort: AnsiString;
1229 Err, I: Integer;
1230 begin
1231 Result := False;
1233 if NetPortForwarded = NetPort then
1234 begin
1235 Result := True;
1236 exit;
1237 end;
1239 NetPongForwarded := False;
1240 NetPortForwarded := 0;
1242 DevList := upnpDiscover(1000, nil, nil, 0, 0, 2, Addr(Err));
1243 if DevList = nil then
1244 begin
1245 conwritefln('port forwarding failed: upnpDiscover() failed: %d', [Err]);
1246 exit;
1247 end;
1249 I := UPNP_GetValidIGD(DevList, @Urls, @Data, Addr(LanAddr[0]), 256);
1251 if I = 0 then
1252 begin
1253 conwriteln('port forwarding failed: could not find an IGD device on this LAN');
1254 FreeUPNPDevList(DevList);
1255 FreeUPNPUrls(@Urls);
1256 exit;
1257 end;
1259 StrPort := IntToStr(NetPort);
1260 I := UPNP_AddPortMapping(
1261 Urls.controlURL, Addr(data.first.servicetype[1]),
1262 PChar(StrPort), PChar(StrPort), Addr(LanAddr[0]), PChar('D2DF'),
1263 PChar('UDP'), nil, PChar('0')
1264 );
1266 if I <> 0 then
1267 begin
1268 conwritefln('forwarding port %d failed: error %d', [NetPort, I]);
1269 FreeUPNPDevList(DevList);
1270 FreeUPNPUrls(@Urls);
1271 exit;
1272 end;
1274 if ForwardPongPort then
1275 begin
1276 StrPort := IntToStr(NET_PING_PORT);
1277 I := UPNP_AddPortMapping(
1278 Urls.controlURL, Addr(data.first.servicetype[1]),
1279 PChar(StrPort), PChar(StrPort), Addr(LanAddr[0]), PChar('D2DF'),
1280 PChar('UDP'), nil, PChar('0')
1281 );
1283 if I <> 0 then
1284 begin
1285 conwritefln('forwarding port %d failed: error %d', [NetPort + 1, I]);
1286 NetPongForwarded := False;
1287 end
1288 else
1289 begin
1290 conwritefln('forwarded port %d successfully', [NetPort + 1]);
1291 NetPongForwarded := True;
1292 end;
1293 end;
1295 conwritefln('forwarded port %d successfully', [NetPort]);
1296 NetIGDControl := AnsiString(Urls.controlURL);
1297 NetIGDService := data.first.servicetype;
1298 NetPortForwarded := NetPort;
1300 FreeUPNPDevList(DevList);
1301 FreeUPNPUrls(@Urls);
1302 Result := True;
1303 end;
1304 {$ELSE}
1305 begin
1306 Result := False;
1307 end;
1308 {$ENDIF}
1310 procedure g_Net_UnforwardPorts();
1311 {$IFDEF USE_MINIUPNPC}
1312 var
1313 I: Integer;
1314 StrPort: AnsiString;
1315 begin
1316 if NetPortForwarded = 0 then Exit;
1318 conwriteln('unforwarding ports...');
1320 StrPort := IntToStr(NetPortForwarded);
1321 I := UPNP_DeletePortMapping(
1322 PChar(NetIGDControl), Addr(NetIGDService[1]),
1323 PChar(StrPort), PChar('UDP'), nil
1324 );
1325 conwritefln(' port %d: %d', [NetPortForwarded, I]);
1327 if NetPongForwarded then
1328 begin
1329 NetPongForwarded := False;
1330 StrPort := IntToStr(NetPortForwarded + 1);
1331 I := UPNP_DeletePortMapping(
1332 PChar(NetIGDControl), Addr(NetIGDService[1]),
1333 PChar(StrPort), PChar('UDP'), nil
1334 );
1335 conwritefln(' port %d: %d', [NetPortForwarded + 1, I]);
1336 end;
1338 NetPortForwarded := 0;
1339 end;
1340 {$ELSE}
1341 begin
1342 end;
1343 {$ENDIF}
1345 initialization
1346 conRegVar('cl_downloadtimeout', @g_Net_DownloadTimeout, 0.0, 1000000.0, '', 'timeout in seconds, 0 to disable it');
1347 g_Net_DownloadTimeout := 60;
1348 NetIn.Alloc(NET_BUFSIZE);
1349 NetOut.Alloc(NET_BUFSIZE);
1350 NetBuf[NET_UNRELIABLE].Alloc(NET_BUFSIZE*2);
1351 NetBuf[NET_RELIABLE].Alloc(NET_BUFSIZE*2);
1352 finalization
1353 NetIn.Free();
1354 NetOut.Free();
1355 NetBuf[NET_UNRELIABLE].Free();
1356 NetBuf[NET_RELIABLE].Free();
1357 end.