DEADSOFTWARE

changed license to GPLv3 only; sorry, no trust to FSF anymore
[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, MAPDEF{$IFDEF USE_MINIUPNPC}, miniupnpc;{$ELSE};{$ENDIF}
23 const
24 NET_PROTOCOL_VER = 181;
26 NET_MAXCLIENTS = 24;
27 NET_CHANS = 11;
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;
41 NET_NONE = 0;
42 NET_SERVER = 1;
43 NET_CLIENT = 2;
45 NET_BUFSIZE = $FFFF;
46 NET_PING_PORT = $DF2D;
48 NET_EVERYONE = -1;
50 NET_UNRELIABLE = 0;
51 NET_RELIABLE = 1;
53 NET_DISC_NONE: enet_uint32 = 0;
54 NET_DISC_PROTOCOL: enet_uint32 = 1;
55 NET_DISC_VERSION: enet_uint32 = 2;
56 NET_DISC_FULL: enet_uint32 = 3;
57 NET_DISC_KICK: enet_uint32 = 4;
58 NET_DISC_DOWN: enet_uint32 = 5;
59 NET_DISC_PASSWORD: enet_uint32 = 6;
60 NET_DISC_TEMPBAN: enet_uint32 = 7;
61 NET_DISC_BAN: enet_uint32 = 8;
62 NET_DISC_MAX: enet_uint32 = 8;
64 NET_STATE_NONE = 0;
65 NET_STATE_AUTH = 1;
66 NET_STATE_GAME = 2;
68 NET_CONNECT_TIMEOUT = 1000 * 10;
70 BANLIST_FILENAME = 'banlist.txt';
71 NETDUMP_FILENAME = 'netdump';
73 {$IFDEF FREEBSD}
74 NilThreadId = nil;
75 {$ELSE}
76 NilThreadId = 0;
77 {$ENDIF}
79 type
80 TNetClient = record
81 ID: Byte;
82 Used: Boolean;
83 State: Byte;
84 Peer: pENetPeer;
85 Player: Word;
86 RequestedFullUpdate: Boolean;
87 RCONAuth: Boolean;
88 Voted: Boolean;
89 NetOut: array [0..1] of TMsg;
90 end;
91 TBanRecord = record
92 IP: LongWord;
93 Perm: Boolean;
94 end;
95 pTNetClient = ^TNetClient;
97 AByte = array of Byte;
99 var
100 NetInitDone: Boolean = False;
101 NetMode: Byte = NET_NONE;
102 NetDump: Boolean = False;
104 NetServerName: string = 'Unnamed Server';
105 NetPassword: string = '';
106 NetPort: Word = 25666;
108 NetAllowRCON: Boolean = False;
109 NetRCONPassword: string = '';
111 NetTimeToUpdate: Cardinal = 0;
112 NetTimeToReliable: Cardinal = 0;
113 NetTimeToMaster: Cardinal = 0;
115 NetHost: pENetHost = nil;
116 NetPeer: pENetPeer = nil;
117 NetEvent: ENetEvent;
118 NetAddr: ENetAddress;
120 NetPongAddr: ENetAddress;
121 NetPongSock: ENetSocket = ENET_SOCKET_NULL;
123 NetUseMaster: Boolean = True;
124 NetSlistAddr: ENetAddress;
125 NetSlistIP: string = 'mpms.doom2d.org';
126 NetSlistPort: Word = 25665;
128 NetClientIP: string = '127.0.0.1';
129 NetClientPort: Word = 25666;
131 NetIn, NetOut: TMsg;
132 NetBuf: array [0..1] of TMsg;
134 NetClients: array of TNetClient;
135 NetClientCount: Byte = 0;
136 NetMaxClients: Byte = 255;
137 NetBannedHosts: array of TBanRecord;
139 NetState: Integer = NET_STATE_NONE;
141 NetMyID: Integer = -1;
142 NetPlrUID1: Integer = -1;
143 NetPlrUID2: Integer = -1;
145 NetInterpLevel: Integer = 1;
146 NetUpdateRate: Cardinal = 0; // as soon as possible
147 NetRelupdRate: Cardinal = 18; // around two times a second
148 NetMasterRate: Cardinal = 60000;
150 NetForcePlayerUpdate: Boolean = False;
151 NetPredictSelf: Boolean = True;
152 NetForwardPorts: Boolean = False;
154 NetGotEverything: Boolean = False;
155 NetGotKeys: Boolean = False;
157 {$IFDEF USE_MINIUPNPC}
158 NetPortForwarded: Word = 0;
159 NetPongForwarded: Boolean = False;
160 NetIGDControl: AnsiString;
161 NetIGDService: TURLStr;
162 {$ENDIF}
164 NetPortThread: TThreadID = NilThreadId;
166 NetDumpFile: TStream;
168 function g_Net_Init(): Boolean;
169 procedure g_Net_Cleanup();
170 procedure g_Net_Free();
171 procedure g_Net_Flush();
173 function g_Net_Host(IPAddr: LongWord; Port: enet_uint16; MaxClients: Cardinal = 16): Boolean;
174 procedure g_Net_Host_Die();
175 procedure g_Net_Host_Send(ID: Integer; Reliable: Boolean; Chan: Byte = NET_CHAN_GAME);
176 function g_Net_Host_Update(): enet_size_t;
178 function g_Net_Connect(IP: string; Port: enet_uint16): Boolean;
179 procedure g_Net_Disconnect(Forced: Boolean = False);
180 procedure g_Net_Client_Send(Reliable: Boolean; Chan: Byte = NET_CHAN_GAME);
181 function g_Net_Client_Update(): enet_size_t;
182 function g_Net_Client_UpdateWhileLoading(): enet_size_t;
184 function g_Net_Client_ByName(Name: string): pTNetClient;
185 function g_Net_Client_ByPlayer(PID: Word): pTNetClient;
186 function g_Net_ClientName_ByID(ID: Integer): string;
188 procedure g_Net_SendData(Data: AByte; peer: pENetPeer; Reliable: Boolean; Chan: Byte = NET_CHAN_DOWNLOAD);
189 function g_Net_Wait_Event(msgId: Word): TMemoryStream;
191 function IpToStr(IP: LongWord): string;
192 function StrToIp(IPstr: string; var IP: LongWord): Boolean;
194 function g_Net_IsHostBanned(IP: LongWord; Perm: Boolean = False): Boolean;
195 procedure g_Net_BanHost(IP: LongWord; Perm: Boolean = True); overload;
196 procedure g_Net_BanHost(IP: string; Perm: Boolean = True); overload;
197 function g_Net_UnbanHost(IP: string): Boolean; overload;
198 function g_Net_UnbanHost(IP: LongWord): Boolean; overload;
199 procedure g_Net_UnbanNonPermHosts();
200 procedure g_Net_SaveBanList();
202 procedure g_Net_DumpStart();
203 procedure g_Net_DumpSendBuffer();
204 procedure g_Net_DumpRecvBuffer(Buf: penet_uint8; Len: LongWord);
205 procedure g_Net_DumpEnd();
207 function g_Net_ForwardPorts(ForwardPongPort: Boolean = True): Boolean;
208 procedure g_Net_UnforwardPorts();
210 implementation
212 uses
213 SysUtils,
214 e_input, g_nethandler, g_netmsg, g_netmaster, g_player, g_window, g_console,
215 g_main, g_game, g_language, g_weapons, utils, ctypes;
217 var
218 g_Net_DownloadTimeout: Single;
221 { /// SERVICE FUNCTIONS /// }
224 function g_Net_FindSlot(): Integer;
225 var
226 I: Integer;
227 F: Boolean;
228 N, C: Integer;
229 begin
230 N := -1;
231 F := False;
232 C := 0;
233 for I := Low(NetClients) to High(NetClients) do
234 begin
235 if NetClients[I].Used then
236 Inc(C)
237 else
238 if not F then
239 begin
240 F := True;
241 N := I;
242 end;
243 end;
244 if C >= NetMaxClients then
245 begin
246 Result := -1;
247 Exit;
248 end;
250 if not F then
251 begin
252 if (Length(NetClients) >= NetMaxClients) then
253 N := -1
254 else
255 begin
256 SetLength(NetClients, Length(NetClients) + 1);
257 N := High(NetClients);
258 end;
259 end;
261 if N >= 0 then
262 begin
263 NetClients[N].Used := True;
264 NetClients[N].ID := N;
265 NetClients[N].RequestedFullUpdate := False;
266 NetClients[N].RCONAuth := False;
267 NetClients[N].Voted := False;
268 NetClients[N].Player := 0;
269 end;
271 Result := N;
272 end;
274 function g_Net_Init(): Boolean;
275 var
276 F: TextFile;
277 IPstr: string;
278 IP: LongWord;
279 begin
280 NetIn.Clear();
281 NetOut.Clear();
282 NetBuf[NET_UNRELIABLE].Clear();
283 NetBuf[NET_RELIABLE].Clear();
284 SetLength(NetClients, 0);
285 NetPeer := nil;
286 NetHost := nil;
287 NetMyID := -1;
288 NetPlrUID1 := -1;
289 NetPlrUID2 := -1;
290 NetAddr.port := 25666;
291 SetLength(NetBannedHosts, 0);
292 if FileExists(DataDir + BANLIST_FILENAME) then
293 begin
294 Assign(F, DataDir + BANLIST_FILENAME);
295 Reset(F);
296 while not EOF(F) do
297 begin
298 Readln(F, IPstr);
299 if StrToIp(IPstr, IP) then
300 g_Net_BanHost(IP);
301 end;
302 CloseFile(F);
303 g_Net_SaveBanList();
304 end;
306 Result := (enet_initialize() = 0);
307 end;
309 procedure g_Net_Flush();
310 var
311 T: Integer;
312 P: pENetPacket;
313 F, Chan: enet_uint32;
314 I: Integer;
315 begin
316 F := 0;
317 Chan := NET_CHAN_GAME;
319 if NetMode = NET_SERVER then
320 for T := NET_UNRELIABLE to NET_RELIABLE do
321 begin
322 if NetBuf[T].CurSize > 0 then
323 begin
324 P := enet_packet_create(NetBuf[T].Data, NetBuf[T].CurSize, F);
325 if not Assigned(P) then continue;
326 enet_host_broadcast(NetHost, Chan, P);
327 NetBuf[T].Clear();
328 end;
330 for I := Low(NetClients) to High(NetClients) do
331 begin
332 if not NetClients[I].Used then continue;
333 if NetClients[I].NetOut[T].CurSize <= 0 then continue;
334 P := enet_packet_create(NetClients[I].NetOut[T].Data, NetClients[I].NetOut[T].CurSize, F);
335 if not Assigned(P) then continue;
336 enet_peer_send(NetClients[I].Peer, Chan, P);
337 NetClients[I].NetOut[T].Clear();
338 end;
340 // next and last iteration is always RELIABLE
341 F := LongWord(ENET_PACKET_FLAG_RELIABLE);
342 Chan := NET_CHAN_IMPORTANT;
343 end
344 else if NetMode = NET_CLIENT then
345 for T := NET_UNRELIABLE to NET_RELIABLE do
346 begin
347 if NetBuf[T].CurSize > 0 then
348 begin
349 P := enet_packet_create(NetBuf[T].Data, NetBuf[T].CurSize, F);
350 if not Assigned(P) then continue;
351 enet_peer_send(NetPeer, Chan, P);
352 NetBuf[T].Clear();
353 end;
354 // next and last iteration is always RELIABLE
355 F := LongWord(ENET_PACKET_FLAG_RELIABLE);
356 Chan := NET_CHAN_IMPORTANT;
357 end;
358 end;
360 procedure g_Net_Cleanup();
361 begin
362 NetIn.Clear();
363 NetOut.Clear();
364 NetBuf[NET_UNRELIABLE].Clear();
365 NetBuf[NET_RELIABLE].Clear();
367 SetLength(NetClients, 0);
368 NetClientCount := 0;
370 NetPeer := nil;
371 NetHost := nil;
372 NetMPeer := nil;
373 NetMHost := nil;
374 NetMyID := -1;
375 NetPlrUID1 := -1;
376 NetPlrUID2 := -1;
377 NetState := NET_STATE_NONE;
379 NetPongSock := ENET_SOCKET_NULL;
381 NetTimeToMaster := 0;
382 NetTimeToUpdate := 0;
383 NetTimeToReliable := 0;
385 NetMode := NET_NONE;
387 if NetPortThread <> NilThreadId then
388 WaitForThreadTerminate(NetPortThread, 66666);
390 NetPortThread := NilThreadId;
391 g_Net_UnforwardPorts();
393 if NetDump then
394 g_Net_DumpEnd();
395 end;
397 procedure g_Net_Free();
398 begin
399 g_Net_Cleanup();
401 enet_deinitialize();
402 NetInitDone := False;
403 end;
406 { /// SERVER FUNCTIONS /// }
409 function ForwardThread(Param: Pointer): PtrInt;
410 begin
411 Result := 0;
412 if not g_Net_ForwardPorts() then Result := -1;
413 end;
415 function g_Net_Host(IPAddr: LongWord; Port: enet_uint16; MaxClients: Cardinal = 16): Boolean;
416 begin
417 if NetMode <> NET_NONE then
418 begin
419 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_INGAME]);
420 Result := False;
421 Exit;
422 end;
424 Result := True;
426 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_HOST], [Port]));
427 if not NetInitDone then
428 begin
429 if (not g_Net_Init()) then
430 begin
431 g_Console_Add(_lc[I_NET_MSG_FERROR] + _lc[I_NET_ERR_ENET]);
432 Result := False;
433 Exit;
434 end
435 else
436 NetInitDone := True;
437 end;
439 NetAddr.host := IPAddr;
440 NetAddr.port := Port;
442 if NetForwardPorts then NetPortThread := BeginThread(ForwardThread);
444 NetHost := enet_host_create(@NetAddr, NET_MAXCLIENTS, NET_CHANS, 0, 0);
446 if (NetHost = nil) then
447 begin
448 g_Console_Add(_lc[I_NET_MSG_ERROR] + Format(_lc[I_NET_ERR_HOST], [Port]));
449 Result := False;
450 g_Net_Cleanup;
451 Exit;
452 end;
454 NetPongSock := enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
455 if NetPongSock <> ENET_SOCKET_NULL then
456 begin
457 NetPongAddr.host := IPAddr;
458 NetPongAddr.port := NET_PING_PORT;
459 if enet_socket_bind(NetPongSock, @NetPongAddr) < 0 then
460 begin
461 enet_socket_destroy(NetPongSock);
462 NetPongSock := ENET_SOCKET_NULL;
463 end
464 else
465 enet_socket_set_option(NetPongSock, ENET_SOCKOPT_NONBLOCK, 1);
466 end;
468 NetMode := NET_SERVER;
469 NetOut.Clear();
470 NetBuf[NET_UNRELIABLE].Clear();
471 NetBuf[NET_RELIABLE].Clear();
473 if NetDump then
474 g_Net_DumpStart();
475 end;
477 procedure g_Net_Host_Die();
478 var
479 I: Integer;
480 begin
481 if NetMode <> NET_SERVER then Exit;
483 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_DISCALL]);
484 for I := 0 to High(NetClients) do
485 if NetClients[I].Used then
486 enet_peer_disconnect(NetClients[I].Peer, NET_DISC_DOWN);
488 while enet_host_service(NetHost, @NetEvent, 1000) > 0 do
489 if NetEvent.kind = ENET_EVENT_TYPE_RECEIVE then
490 enet_packet_destroy(NetEvent.packet);
492 for I := 0 to High(NetClients) do
493 if NetClients[I].Used then
494 begin
495 FreeMemory(NetClients[I].Peer^.data);
496 NetClients[I].Peer^.data := nil;
497 enet_peer_reset(NetClients[I].Peer);
498 NetClients[I].Peer := nil;
499 NetClients[I].Used := False;
500 NetClients[I].NetOut[NET_UNRELIABLE].Free();
501 NetClients[I].NetOut[NET_RELIABLE].Free();
502 end;
504 if (NetMPeer <> nil) and (NetMHost <> nil) then g_Net_Slist_Disconnect;
505 if NetPongSock <> ENET_SOCKET_NULL then
506 enet_socket_destroy(NetPongSock);
508 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_DIE]);
509 enet_host_destroy(NetHost);
511 NetMode := NET_NONE;
513 g_Net_Cleanup;
514 e_WriteLog('NET: Server stopped', TMsgType.Notify);
515 end;
518 procedure g_Net_Host_Send(ID: Integer; Reliable: Boolean; Chan: Byte = NET_CHAN_GAME);
519 var
520 T: Integer;
521 begin
522 if (Reliable) then
523 T := NET_RELIABLE
524 else
525 T := NET_UNRELIABLE;
527 if (ID >= 0) then
528 begin
529 if ID > High(NetClients) then Exit;
530 if NetClients[ID].Peer = nil then Exit;
531 // write size first
532 NetClients[ID].NetOut[T].Write(Integer(NetOut.CurSize));
533 NetClients[ID].NetOut[T].Write(NetOut);
534 end
535 else
536 begin
537 // write size first
538 NetBuf[T].Write(Integer(NetOut.CurSize));
539 NetBuf[T].Write(NetOut);
540 end;
542 if NetDump then g_Net_DumpSendBuffer();
543 NetOut.Clear();
544 end;
546 procedure g_Net_Host_CheckPings();
547 var
548 ClAddr: ENetAddress;
549 Buf: ENetBuffer;
550 Len: Integer;
551 ClTime: Int64;
552 Ping: array [0..9] of Byte;
553 NPl: Byte;
554 begin
555 if NetPongSock = ENET_SOCKET_NULL then Exit;
557 Buf.data := Addr(Ping[0]);
558 Buf.dataLength := 2+8;
560 Ping[0] := 0;
562 Len := enet_socket_receive(NetPongSock, @ClAddr, @Buf, 1);
563 if Len < 0 then Exit;
565 if (Ping[0] = Ord('D')) and (Ping[1] = Ord('F')) then
566 begin
567 ClTime := Int64(Addr(Ping[2])^);
569 NetOut.Clear();
570 NetOut.Write(Byte(Ord('D')));
571 NetOut.Write(Byte(Ord('F')));
572 NetOut.Write(NetPort);
573 NetOut.Write(ClTime);
574 g_Net_Slist_WriteInfo();
575 NPl := 0;
576 if gPlayer1 <> nil then Inc(NPl);
577 if gPlayer2 <> nil then Inc(NPl);
578 NetOut.Write(NPl);
579 NetOut.Write(gNumBots);
581 Buf.data := NetOut.Data;
582 Buf.dataLength := NetOut.CurSize;
583 enet_socket_send(NetPongSock, @ClAddr, @Buf, 1);
585 NetOut.Clear();
586 end;
587 end;
589 function g_Net_Host_Update(): enet_size_t;
590 var
591 IP: string;
592 Port: Word;
593 ID: Integer;
594 TC: pTNetClient;
595 TP: TPlayer;
596 begin
597 IP := '';
598 Result := 0;
600 if NetUseMaster then
601 g_Net_Slist_Check;
602 g_Net_Host_CheckPings;
604 while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
605 begin
606 case (NetEvent.kind) of
607 ENET_EVENT_TYPE_CONNECT:
608 begin
609 IP := IpToStr(NetEvent.Peer^.address.host);
610 Port := NetEvent.Peer^.address.port;
611 g_Console_Add(_lc[I_NET_MSG] +
612 Format(_lc[I_NET_MSG_HOST_CONN], [IP, Port]));
614 if (NetEvent.data <> NET_PROTOCOL_VER) then
615 begin
616 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_REJECT] +
617 _lc[I_NET_DISC_PROTOCOL]);
618 NetEvent.peer^.data := GetMemory(SizeOf(Byte));
619 Byte(NetEvent.peer^.data^) := 255;
620 enet_peer_disconnect(NetEvent.peer, NET_DISC_PROTOCOL);
621 enet_host_flush(NetHost);
622 Exit;
623 end;
625 ID := g_Net_FindSlot();
627 if ID < 0 then
628 begin
629 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_REJECT] +
630 _lc[I_NET_DISC_FULL]);
631 NetEvent.Peer^.data := GetMemory(SizeOf(Byte));
632 Byte(NetEvent.peer^.data^) := 255;
633 enet_peer_disconnect(NetEvent.peer, NET_DISC_FULL);
634 enet_host_flush(NetHost);
635 Exit;
636 end;
638 NetClients[ID].Peer := NetEvent.peer;
639 NetClients[ID].Peer^.data := GetMemory(SizeOf(Byte));
640 Byte(NetClients[ID].Peer^.data^) := ID;
641 NetClients[ID].State := NET_STATE_AUTH;
642 NetClients[ID].RCONAuth := False;
643 NetClients[ID].NetOut[NET_UNRELIABLE].Alloc(NET_BUFSIZE*2);
644 NetClients[ID].NetOut[NET_RELIABLE].Alloc(NET_BUFSIZE*2);
646 enet_peer_timeout(NetEvent.peer, ENET_PEER_TIMEOUT_LIMIT * 2, ENET_PEER_TIMEOUT_MINIMUM * 2, ENET_PEER_TIMEOUT_MAXIMUM * 2);
648 Inc(NetClientCount);
649 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_HOST_ADD], [ID]));
650 end;
652 ENET_EVENT_TYPE_RECEIVE:
653 begin
654 ID := Byte(NetEvent.peer^.data^);
655 if ID > High(NetClients) then Exit;
656 TC := @NetClients[ID];
658 if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
659 g_Net_Host_HandlePacket(TC, NetEvent.packet, g_Net_HostMsgHandler);
660 end;
662 ENET_EVENT_TYPE_DISCONNECT:
663 begin
664 ID := Byte(NetEvent.peer^.data^);
665 if ID > High(NetClients) then Exit;
666 TC := @NetClients[ID];
667 if TC = nil then Exit;
669 if not (TC^.Used) then Exit;
671 TP := g_Player_Get(TC^.Player);
673 if TP <> nil then
674 begin
675 TP.Lives := 0;
676 TP.Kill(K_SIMPLEKILL, 0, HIT_DISCON);
677 g_Console_Add(Format(_lc[I_PLAYER_LEAVE], [TP.Name]), True);
678 e_WriteLog('NET: Client ' + TP.Name + ' [' + IntToStr(ID) + '] disconnected.', TMsgType.Notify);
679 g_Player_Remove(TP.UID);
680 end;
682 TC^.Used := False;
683 TC^.State := NET_STATE_NONE;
684 TC^.Peer := nil;
685 TC^.Player := 0;
686 TC^.RequestedFullUpdate := False;
687 TC^.NetOut[NET_UNRELIABLE].Free();
688 TC^.NetOut[NET_RELIABLE].Free();
690 FreeMemory(NetEvent.peer^.data);
691 NetEvent.peer^.data := nil;
692 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_HOST_DISC], [ID]));
693 Dec(NetClientCount);
695 if NetUseMaster then g_Net_Slist_Update;
696 end;
697 end;
698 end;
699 end;
702 { /// CLIENT FUNCTIONS /// }
705 procedure g_Net_Disconnect(Forced: Boolean = False);
706 begin
707 if NetMode <> NET_CLIENT then Exit;
708 if (NetHost = nil) or (NetPeer = nil) then Exit;
710 if not Forced then
711 begin
712 enet_peer_disconnect(NetPeer, NET_DISC_NONE);
714 while (enet_host_service(NetHost, @NetEvent, 1500) > 0) do
715 begin
716 if (NetEvent.kind = ENET_EVENT_TYPE_DISCONNECT) then
717 begin
718 NetPeer := nil;
719 break;
720 end;
722 if (NetEvent.kind = ENET_EVENT_TYPE_RECEIVE) then
723 enet_packet_destroy(NetEvent.packet);
724 end;
726 if NetPeer <> nil then
727 begin
728 enet_peer_reset(NetPeer);
729 NetPeer := nil;
730 end;
731 end
732 else
733 begin
734 e_WriteLog('NET: Kicked from server: ' + IntToStr(NetEvent.data), TMsgType.Notify);
735 if (NetEvent.data <= NET_DISC_MAX) then
736 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_KICK] +
737 _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + NetEvent.data)], True);
738 end;
740 if NetHost <> nil then
741 begin
742 enet_host_destroy(NetHost);
743 NetHost := nil;
744 end;
745 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_CLIENT_DISC]);
747 g_Net_Cleanup;
748 e_WriteLog('NET: Disconnected', TMsgType.Notify);
749 end;
751 procedure g_Net_Client_Send(Reliable: Boolean; Chan: Byte = NET_CHAN_GAME);
752 var
753 T: Integer;
754 begin
755 if (Reliable) then
756 T := NET_RELIABLE
757 else
758 T := NET_UNRELIABLE;
760 // write size first
761 NetBuf[T].Write(Integer(NetOut.CurSize));
762 NetBuf[T].Write(NetOut);
764 if NetDump then g_Net_DumpSendBuffer();
765 NetOut.Clear();
766 g_Net_Flush(); // FIXME: for now, send immediately
767 end;
769 function g_Net_Client_Update(): enet_size_t;
770 begin
771 Result := 0;
772 while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
773 begin
774 case NetEvent.kind of
775 ENET_EVENT_TYPE_RECEIVE:
776 begin
777 if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
778 g_Net_Client_HandlePacket(NetEvent.packet, g_Net_ClientMsgHandler);
779 end;
781 ENET_EVENT_TYPE_DISCONNECT:
782 begin
783 g_Net_Disconnect(True);
784 Result := 1;
785 Exit;
786 end;
787 end;
788 end
789 end;
791 function g_Net_Client_UpdateWhileLoading(): enet_size_t;
792 begin
793 Result := 0;
794 while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
795 begin
796 case NetEvent.kind of
797 ENET_EVENT_TYPE_RECEIVE:
798 begin
799 if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
800 g_Net_Client_HandlePacket(NetEvent.packet, g_Net_ClientLightMsgHandler);
801 end;
803 ENET_EVENT_TYPE_DISCONNECT:
804 begin
805 g_Net_Disconnect(True);
806 Result := 1;
807 Exit;
808 end;
809 end;
810 end;
811 g_Net_Flush();
812 end;
814 function g_Net_Connect(IP: string; Port: enet_uint16): Boolean;
815 var
816 OuterLoop: Boolean;
817 TimeoutTime, T: Int64;
818 begin
819 if NetMode <> NET_NONE then
820 begin
821 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_ERR_INGAME], True);
822 Result := False;
823 Exit;
824 end;
826 Result := True;
828 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_CLIENT_CONN],
829 [IP, Port]));
830 if not NetInitDone then
831 begin
832 if (not g_Net_Init()) then
833 begin
834 g_Console_Add(_lc[I_NET_MSG_FERROR] + _lc[I_NET_ERR_ENET], True);
835 Result := False;
836 Exit;
837 end
838 else
839 NetInitDone := True;
840 end;
842 NetHost := enet_host_create(nil, 1, NET_CHANS, 0, 0);
844 if (NetHost = nil) then
845 begin
846 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CLIENT], True);
847 g_Net_Cleanup;
848 Result := False;
849 Exit;
850 end;
852 enet_address_set_host(@NetAddr, PChar(Addr(IP[1])));
853 NetAddr.port := Port;
855 NetPeer := enet_host_connect(NetHost, @NetAddr, NET_CHANS, NET_PROTOCOL_VER);
857 if (NetPeer = nil) then
858 begin
859 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CLIENT], True);
860 enet_host_destroy(NetHost);
861 g_Net_Cleanup;
862 Result := False;
863 Exit;
864 end;
866 // предупредить что ждем слишком долго через N секунд
867 TimeoutTime := GetTimer() + NET_CONNECT_TIMEOUT;
869 OuterLoop := True;
870 while OuterLoop do
871 begin
872 while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
873 begin
874 if (NetEvent.kind = ENET_EVENT_TYPE_CONNECT) then
875 begin
876 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_CLIENT_DONE]);
877 NetMode := NET_CLIENT;
878 NetOut.Clear();
879 enet_peer_timeout(NetPeer, ENET_PEER_TIMEOUT_LIMIT * 2, ENET_PEER_TIMEOUT_MINIMUM * 2, ENET_PEER_TIMEOUT_MAXIMUM * 2);
880 NetClientIP := IP;
881 NetClientPort := Port;
882 if NetDump then
883 g_Net_DumpStart();
884 Exit;
885 end;
886 end;
888 T := GetTimer();
889 if T > TimeoutTime then
890 begin
891 TimeoutTime := T + NET_CONNECT_TIMEOUT * 100; // одного предупреждения хватит
892 g_Console_Add(_lc[I_NET_MSG_TIMEOUT_WARN], True);
893 g_Console_Add(Format(_lc[I_NET_MSG_PORTS], [Integer(Port), Integer(NET_PING_PORT)]), True);
894 end;
896 ProcessLoading(true);
898 if e_KeyPressed(IK_SPACE) or e_KeyPressed(IK_ESCAPE) or e_KeyPressed(VK_ESCAPE) or
899 e_KeyPressed(JOY0_JUMP) or e_KeyPressed(JOY1_JUMP) or e_KeyPressed(JOY2_JUMP) or e_KeyPressed(JOY3_JUMP) then
900 OuterLoop := False;
901 end;
903 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_TIMEOUT], True);
904 g_Console_Add(Format(_lc[I_NET_MSG_PORTS], [Integer(Port), Integer(NET_PING_PORT)]), True);
905 if NetPeer <> nil then enet_peer_reset(NetPeer);
906 if NetHost <> nil then
907 begin
908 enet_host_destroy(NetHost);
909 NetHost := nil;
910 end;
911 g_Net_Cleanup();
912 Result := False;
913 end;
915 function IpToStr(IP: LongWord): string;
916 var
917 Ptr: Pointer;
918 begin
919 Ptr := Addr(IP);
920 Result := IntToStr(PByte(Ptr + 0)^) + '.';
921 Result := Result + IntToStr(PByte(Ptr + 1)^) + '.';
922 Result := Result + IntToStr(PByte(Ptr + 2)^) + '.';
923 Result := Result + IntToStr(PByte(Ptr + 3)^);
924 end;
926 function StrToIp(IPstr: string; var IP: LongWord): Boolean;
927 var
928 EAddr: ENetAddress;
929 begin
930 Result := enet_address_set_host(@EAddr, PChar(@IPstr[1])) = 0;
931 IP := EAddr.host;
932 end;
934 function g_Net_Client_ByName(Name: string): pTNetClient;
935 var
936 a: Integer;
937 pl: TPlayer;
938 begin
939 Result := nil;
940 for a := Low(NetClients) to High(NetClients) do
941 if (NetClients[a].Used) and (NetClients[a].State = NET_STATE_GAME) then
942 begin
943 pl := g_Player_Get(NetClients[a].Player);
944 if pl = nil then continue;
945 if Copy(LowerCase(pl.Name), 1, Length(Name)) <> LowerCase(Name) then continue;
946 if NetClients[a].Peer <> nil then
947 begin
948 Result := @NetClients[a];
949 Exit;
950 end;
951 end;
952 end;
954 function g_Net_Client_ByPlayer(PID: Word): pTNetClient;
955 var
956 a: Integer;
957 begin
958 Result := nil;
959 for a := Low(NetClients) to High(NetClients) do
960 if (NetClients[a].Used) and (NetClients[a].State = NET_STATE_GAME) then
961 if NetClients[a].Player = PID then
962 begin
963 Result := @NetClients[a];
964 Exit;
965 end;
966 end;
968 function g_Net_ClientName_ByID(ID: Integer): string;
969 var
970 a: Integer;
971 pl: TPlayer;
972 begin
973 Result := '';
974 if ID = NET_EVERYONE then
975 Exit;
976 for a := Low(NetClients) to High(NetClients) do
977 if (NetClients[a].ID = ID) and (NetClients[a].Used) and (NetClients[a].State = NET_STATE_GAME) then
978 begin
979 pl := g_Player_Get(NetClients[a].Player);
980 if pl = nil then Exit;
981 Result := pl.Name;
982 end;
983 end;
985 procedure g_Net_SendData(Data: AByte; peer: pENetPeer; Reliable: Boolean; Chan: Byte = NET_CHAN_DOWNLOAD);
986 var
987 P: pENetPacket;
988 F: enet_uint32;
989 dataLength: Cardinal;
990 begin
991 dataLength := Length(Data);
993 if (Reliable) then
994 F := LongWord(ENET_PACKET_FLAG_RELIABLE)
995 else
996 F := 0;
998 if (peer <> nil) then
999 begin
1000 P := enet_packet_create(@Data[0], dataLength, F);
1001 if not Assigned(P) then Exit;
1002 enet_peer_send(peer, Chan, P);
1003 end
1004 else
1005 begin
1006 P := enet_packet_create(@Data[0], dataLength, F);
1007 if not Assigned(P) then Exit;
1008 enet_host_broadcast(NetHost, Chan, P);
1009 end;
1011 enet_host_flush(NetHost);
1012 end;
1014 function UserRequestExit: Boolean;
1015 begin
1016 Result := e_KeyPressed(IK_SPACE) or
1017 e_KeyPressed(IK_ESCAPE) or
1018 e_KeyPressed(VK_ESCAPE) or
1019 e_KeyPressed(JOY0_JUMP) or
1020 e_KeyPressed(JOY1_JUMP) or
1021 e_KeyPressed(JOY2_JUMP) or
1022 e_KeyPressed(JOY3_JUMP)
1023 end;
1025 function g_Net_Wait_Event(msgId: Word): TMemoryStream;
1026 var
1027 ev: ENetEvent;
1028 rMsgId: Byte;
1029 Ptr: Pointer;
1030 stream: TMemoryStream;
1031 status: cint;
1032 begin
1033 FillChar(ev, SizeOf(ev), 0);
1034 stream := nil;
1035 repeat
1036 status := enet_host_service(NetHost, @ev, Trunc(g_Net_DownloadTimeout * 1000));
1037 if status > 0 then
1038 begin
1039 case ev.kind of
1040 ENET_EVENT_TYPE_RECEIVE:
1041 begin
1042 Ptr := ev.packet^.data;
1043 rMsgId := Byte(Ptr^);
1044 if rMsgId = msgId then
1045 begin
1046 stream := TMemoryStream.Create;
1047 stream.SetSize(ev.packet^.dataLength);
1048 stream.WriteBuffer(Ptr^, ev.packet^.dataLength);
1049 stream.Seek(0, soFromBeginning);
1050 status := 1 (* received *)
1051 end
1052 else
1053 begin
1054 (* looks that game state always received, so ignore it *)
1055 e_LogWritefln('g_Net_Wait_Event(%s): skip message %s', [msgId, rMsgId]);
1056 status := 2 (* continue *)
1057 end
1058 end;
1059 ENET_EVENT_TYPE_DISCONNECT:
1060 begin
1061 if (ev.data <= NET_DISC_MAX) then
1062 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' ' + _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + ev.data)], True);
1063 status := -2 (* error: disconnected *)
1064 end;
1065 else
1066 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' unknown ENet event ' + IntToStr(Ord(ev.kind)), True);
1067 status := -3 (* error: unknown event *)
1068 end;
1069 enet_packet_destroy(ev.packet)
1070 end
1071 else
1072 begin
1073 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' timeout reached', True);
1074 status := 0 (* error: timeout *)
1075 end;
1076 ProcessLoading(true);
1077 until (status <> 2) or UserRequestExit();
1078 Result := stream
1079 end;
1081 function g_Net_IsHostBanned(IP: LongWord; Perm: Boolean = False): Boolean;
1082 var
1083 I: Integer;
1084 begin
1085 Result := False;
1086 if NetBannedHosts = nil then
1087 Exit;
1088 for I := 0 to High(NetBannedHosts) do
1089 if (NetBannedHosts[I].IP = IP) and ((not Perm) or (NetBannedHosts[I].Perm)) then
1090 begin
1091 Result := True;
1092 break;
1093 end;
1094 end;
1096 procedure g_Net_BanHost(IP: LongWord; Perm: Boolean = True); overload;
1097 var
1098 I, P: Integer;
1099 begin
1100 if IP = 0 then
1101 Exit;
1102 if g_Net_IsHostBanned(IP, Perm) then
1103 Exit;
1105 P := -1;
1106 for I := Low(NetBannedHosts) to High(NetBannedHosts) do
1107 if NetBannedHosts[I].IP = 0 then
1108 begin
1109 P := I;
1110 break;
1111 end;
1113 if P < 0 then
1114 begin
1115 SetLength(NetBannedHosts, Length(NetBannedHosts) + 1);
1116 P := High(NetBannedHosts);
1117 end;
1119 NetBannedHosts[P].IP := IP;
1120 NetBannedHosts[P].Perm := Perm;
1121 end;
1123 procedure g_Net_BanHost(IP: string; Perm: Boolean = True); overload;
1124 var
1125 a: LongWord;
1126 b: Boolean;
1127 begin
1128 b := StrToIp(IP, a);
1129 if b then
1130 g_Net_BanHost(a, Perm);
1131 end;
1133 procedure g_Net_UnbanNonPermHosts();
1134 var
1135 I: Integer;
1136 begin
1137 if NetBannedHosts = nil then
1138 Exit;
1139 for I := Low(NetBannedHosts) to High(NetBannedHosts) do
1140 if (NetBannedHosts[I].IP > 0) and not NetBannedHosts[I].Perm then
1141 begin
1142 NetBannedHosts[I].IP := 0;
1143 NetBannedHosts[I].Perm := True;
1144 end;
1145 end;
1147 function g_Net_UnbanHost(IP: string): Boolean; overload;
1148 var
1149 a: LongWord;
1150 begin
1151 Result := StrToIp(IP, a);
1152 if Result then
1153 Result := g_Net_UnbanHost(a);
1154 end;
1156 function g_Net_UnbanHost(IP: LongWord): Boolean; overload;
1157 var
1158 I: Integer;
1159 begin
1160 Result := False;
1161 if IP = 0 then
1162 Exit;
1163 if NetBannedHosts = nil then
1164 Exit;
1165 for I := 0 to High(NetBannedHosts) do
1166 if NetBannedHosts[I].IP = IP then
1167 begin
1168 NetBannedHosts[I].IP := 0;
1169 NetBannedHosts[I].Perm := True;
1170 Result := True;
1171 // no break here to clear all bans of this host, perm and non-perm
1172 end;
1173 end;
1175 procedure g_Net_SaveBanList();
1176 var
1177 F: TextFile;
1178 I: Integer;
1179 begin
1180 Assign(F, DataDir + BANLIST_FILENAME);
1181 Rewrite(F);
1182 if NetBannedHosts <> nil then
1183 for I := 0 to High(NetBannedHosts) do
1184 if NetBannedHosts[I].Perm and (NetBannedHosts[I].IP > 0) then
1185 Writeln(F, IpToStr(NetBannedHosts[I].IP));
1186 CloseFile(F);
1187 end;
1189 procedure g_Net_DumpStart();
1190 begin
1191 if NetMode = NET_SERVER then
1192 NetDumpFile := createDiskFile(NETDUMP_FILENAME + '_server')
1193 else
1194 NetDumpFile := createDiskFile(NETDUMP_FILENAME + '_client');
1195 end;
1197 procedure g_Net_DumpSendBuffer();
1198 begin
1199 writeInt(NetDumpFile, gTime);
1200 writeInt(NetDumpFile, LongWord(NetOut.CurSize));
1201 writeInt(NetDumpFile, Byte(1));
1202 NetDumpFile.WriteBuffer(NetOut.Data^, NetOut.CurSize);
1203 end;
1205 procedure g_Net_DumpRecvBuffer(Buf: penet_uint8; Len: LongWord);
1206 begin
1207 if (Buf = nil) or (Len = 0) then Exit;
1208 writeInt(NetDumpFile, gTime);
1209 writeInt(NetDumpFile, Len);
1210 writeInt(NetDumpFile, Byte(0));
1211 NetDumpFile.WriteBuffer(Buf^, Len);
1212 end;
1214 procedure g_Net_DumpEnd();
1215 begin
1216 NetDumpFile.Free();
1217 NetDumpFile := nil;
1218 end;
1220 function g_Net_ForwardPorts(ForwardPongPort: Boolean = True): Boolean;
1221 {$IFDEF USE_MINIUPNPC}
1222 var
1223 DevList: PUPNPDev;
1224 Urls: TUPNPUrls;
1225 Data: TIGDDatas;
1226 LanAddr: array [0..255] of Char;
1227 StrPort: AnsiString;
1228 Err, I: Integer;
1229 begin
1230 Result := False;
1232 if NetPortForwarded = NetPort then
1233 begin
1234 Result := True;
1235 exit;
1236 end;
1238 NetPongForwarded := False;
1239 NetPortForwarded := 0;
1241 DevList := upnpDiscover(1000, nil, nil, 0, 0, 2, Addr(Err));
1242 if DevList = nil then
1243 begin
1244 conwritefln('port forwarding failed: upnpDiscover() failed: %d', [Err]);
1245 exit;
1246 end;
1248 I := UPNP_GetValidIGD(DevList, @Urls, @Data, Addr(LanAddr[0]), 256);
1250 if I = 0 then
1251 begin
1252 conwriteln('port forwarding failed: could not find an IGD device on this LAN');
1253 FreeUPNPDevList(DevList);
1254 FreeUPNPUrls(@Urls);
1255 exit;
1256 end;
1258 StrPort := IntToStr(NetPort);
1259 I := UPNP_AddPortMapping(
1260 Urls.controlURL, Addr(data.first.servicetype[1]),
1261 PChar(StrPort), PChar(StrPort), Addr(LanAddr[0]), PChar('D2DF'),
1262 PChar('UDP'), nil, PChar('0')
1263 );
1265 if I <> 0 then
1266 begin
1267 conwritefln('forwarding port %d failed: error %d', [NetPort, I]);
1268 FreeUPNPDevList(DevList);
1269 FreeUPNPUrls(@Urls);
1270 exit;
1271 end;
1273 if ForwardPongPort then
1274 begin
1275 StrPort := IntToStr(NET_PING_PORT);
1276 I := UPNP_AddPortMapping(
1277 Urls.controlURL, Addr(data.first.servicetype[1]),
1278 PChar(StrPort), PChar(StrPort), Addr(LanAddr[0]), PChar('D2DF'),
1279 PChar('UDP'), nil, PChar('0')
1280 );
1282 if I <> 0 then
1283 begin
1284 conwritefln('forwarding port %d failed: error %d', [NetPort + 1, I]);
1285 NetPongForwarded := False;
1286 end
1287 else
1288 begin
1289 conwritefln('forwarded port %d successfully', [NetPort + 1]);
1290 NetPongForwarded := True;
1291 end;
1292 end;
1294 conwritefln('forwarded port %d successfully', [NetPort]);
1295 NetIGDControl := AnsiString(Urls.controlURL);
1296 NetIGDService := data.first.servicetype;
1297 NetPortForwarded := NetPort;
1299 FreeUPNPDevList(DevList);
1300 FreeUPNPUrls(@Urls);
1301 Result := True;
1302 end;
1303 {$ELSE}
1304 begin
1305 Result := False;
1306 end;
1307 {$ENDIF}
1309 procedure g_Net_UnforwardPorts();
1310 {$IFDEF USE_MINIUPNPC}
1311 var
1312 I: Integer;
1313 StrPort: AnsiString;
1314 begin
1315 if NetPortForwarded = 0 then Exit;
1317 conwriteln('unforwarding ports...');
1319 StrPort := IntToStr(NetPortForwarded);
1320 I := UPNP_DeletePortMapping(
1321 PChar(NetIGDControl), Addr(NetIGDService[1]),
1322 PChar(StrPort), PChar('UDP'), nil
1323 );
1324 conwritefln(' port %d: %d', [NetPortForwarded, I]);
1326 if NetPongForwarded then
1327 begin
1328 NetPongForwarded := False;
1329 StrPort := IntToStr(NetPortForwarded + 1);
1330 I := UPNP_DeletePortMapping(
1331 PChar(NetIGDControl), Addr(NetIGDService[1]),
1332 PChar(StrPort), PChar('UDP'), nil
1333 );
1334 conwritefln(' port %d: %d', [NetPortForwarded + 1, I]);
1335 end;
1337 NetPortForwarded := 0;
1338 end;
1339 {$ELSE}
1340 begin
1341 end;
1342 {$ENDIF}
1344 initialization
1345 conRegVar('cl_downloadtimeout', @g_Net_DownloadTimeout, 0.0, 1000000.0, '', 'timeout in seconds, 0 to disable it');
1346 g_Net_DownloadTimeout := 60;
1347 NetIn.Alloc(NET_BUFSIZE);
1348 NetOut.Alloc(NET_BUFSIZE);
1349 NetBuf[NET_UNRELIABLE].Alloc(NET_BUFSIZE*2);
1350 NetBuf[NET_RELIABLE].Alloc(NET_BUFSIZE*2);
1351 finalization
1352 NetIn.Free();
1353 NetOut.Free();
1354 NetBuf[NET_UNRELIABLE].Free();
1355 NetBuf[NET_RELIABLE].Free();
1356 end.