DEADSOFTWARE

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