{*********************************************************

 SlavaNap source code.

 Copyright 2001,2002 by SlavaNap development team
 Released under GNU General Public License

 Latest version is available at
 http://www.slavanap.org

**********************************************************

 Unit: Servers

 class for server-server connections

*********************************************************}
unit Servers;

interface

{$I Defines.pas}

uses
  SysUtils, Classes, Classes2, Graphics, ZLibEx, WinSock, Windows, Constants,
  STypes, BlckSock, SynSock, Class_Cmdlist, StringResources;

type
  TAllowLinkType = (linkAll, linkList, linkNone, linkCustom);
  TConnectionState = (conNotConnected, conConnecting, conConnected);
  TServerAuthentication = (authResolve, authPassword);
  TServer = class(TObject)
    Host: string;
    Alias: string;
    Port: Integer;
    Hub: TServer;
    Connected: TConnectionState;
    Thread: TThread;
    Logged: Boolean;
    Version: string;
    Console: string;
    Num_Users, Num_Files, Max_Users: Integer;
    Num_Bytes: Int64;
    Server_Handle: Integer;
    Mypassword: string;
    Remotepassword: string;
    Authentication: TServerAuthentication;
    Compress: TZCompressionLevel;
    Relink: Cardinal;
    Comments: string;
    Forced_Ip: string;
    Socket: HSocket;
    Out_List: TNapCmdList;
    Out_Buf: TNapCmdList;
    Recv_Buf: string;
    Recv_Len: Integer;
    Login_Start: Cardinal;
    Incoming: Boolean;
    Lag: Boolean;
    Reg_User: string;
    Redirects: Integer;
    Truestats: Boolean;
    constructor Create;
    destructor Destroy; override;
    procedure Exec(Id: Integer; Cmd: string; Log_Cmd: Boolean = True);
    procedure Relay(Id: Integer; Cmd: string; Dst, Src: Integer);
    procedure Receive(N: Integer);
    procedure Connect;
    procedure ResetData;
    procedure Flush;
    procedure Compile;
    function CountLag: Integer;
    procedure AddToOutput(Str: string);
  end;
  TServerThread = class(TThread)
    Srv: TServer;
    Ip: string;
    Err_Msg: string;
    constructor Create(Server: TServer);
    procedure Execute; override;
    destructor Destroy; override;
    procedure ResolveIP;
    procedure SyncComplete;
    procedure Error(Str: string);
    procedure SyncError;
  end;

function GetServerName(Srv: TServer): string;
function GetServerAlias(Srv: TServer): string;
function GetServerLink(Srv: TServer): TServer;
function GetServerHandle(Srv: TServer): Integer;
function FindServer(Host: string; Allow_Alias: Boolean): TServer; overload;
function FindServer(Handle: Integer): TServer; overload;
function CheckLag(Srv: TServer): Boolean;
function CountSubServers(Srv: TServer): Integer;
function CountLinkedServers(Ignored: TServer): Integer;
procedure LoadServers;
procedure SaveServers;

implementation

uses
  Vars, Lang, Handler, Thread, Memory_Manager;

var
  Console_List, Handle_List: string;

function GetServerName(Srv: TServer): string;
begin
  if Srv = nil then
    Result := ServerName_T
  else
    Result := Srv.Host;
end;

function GetServerAlias(Srv: TServer): string;
begin
  if Srv = nil then
    Result := ServerAlias
  else if Srv.Alias <> '' then
    Result := Srv.Alias
  else
    Result := Srv.Host;
end;

function GetServerLink(Srv: TServer): TServer;
begin
  Result := nil;
  if Srv = nil then Exit;
  while Srv.Hub <> nil do
    Srv := Srv.Hub;
  Result := Srv;
end;

function CountSubServers(Srv: TServer): Integer;
var
  I, J: Integer;
  Srv2: TServer;
begin
  J := 1;
  for I := 0 to DB_Servers.Count - 1 do
  begin
    Srv2 := DB_Servers.Items[I];
    if Srv2.Hub = Srv then
      Inc(J, CountSubServers(Srv2));
  end;
  Result := J;
end;

function CountLinkedServers(Ignored: TServer): Integer;
var
  I, J: Integer;
  Srv: TServer;
begin
  J := 1;
  for I := 0 to DB_Servers.Count - 1 do
  begin
    Srv := DB_Servers.Items[I];
    if Srv <> Ignored then
      if Srv.Logged then
        if Srv.Hub = nil then
          Inc(J, CountSubServers(Srv));
  end;
  Result := J;
end;

function CheckLag(Srv: TServer): Boolean;
begin
  Result := False;
  if Srv = nil then Exit;
  Srv := GetServerLink(Srv);
  if Srv = nil then Exit;
  Result := True;
  if Srv.Connected <> conConnected then Exit;
  if (Current_Time - Srv.Login_Start) < SERVER_SYNCH_TIMEOUT then Exit;
  Result := Srv.Lag;
end;

function GetServerHandle(Srv: TServer): Integer;
begin
  if Srv = nil then
    Result := MyServerHandle
  else
    Result := Srv.Server_Handle;
end;

function FindServer(Host: string; Allow_Alias: Boolean): TServer;
var
  I: Integer;
  Srv: TServer;
begin
  Host := LowerCase(Host);
  if DB_Servers <> nil then
    for I := 0 to DB_Servers.Count - 1 do
    begin
      Srv := DB_Servers.Items[I];
      if Srv.Host = Host then
      begin
        Result := Srv;
        Exit;
      end;
      if Allow_Alias and (Srv.Alias = Host) then
      begin
        Result := Srv;
        Exit;
      end;
    end;
  Result := nil;
end;

function FindServer(Handle: Integer): TServer;
var
  I: Integer;
  Srv: TServer;
begin
  if DB_Servers <> nil then
    for I := 0 to DB_Servers.Count - 1 do
    begin
      Srv := DB_Servers.Items[I];
      if Srv.Server_Handle = Handle then
      begin
        Result := Srv;
        Exit;
      end;
    end;
  Result := nil;
end;

procedure LoadServers;
var
  List, Lst: TMyStringList;
  I: Integer;
  Server: TServer;
  Num: Integer;
begin
  List := CreateStringList;
  try
    List.LoadFromFile(ApplicationDir + 'servers');
  except
    on E: Exception do
    begin
      DebugLog('LoadServers : Exception ' + E.Message);
      FreeStringList(List);
      Exit;
    end;
  end;
  Lst := CreateStringList;
  Num := 2;
  for I := 0 to List.Count - 1 do
  begin
    if List.Strings[I] = '# Version 2' then
      Num := 5;
    SplitString(List.Strings[I], Lst);
    if Lst.Count >= Num then
      if Length(List.Strings[I]) > 0 then
        if List.Strings[I][1] <> '#' then
          if FindServer(Lst.Strings[0], False) = nil then
            if Lst.Strings[0] <> ServerName_T then
            begin
              Server := TServer.Create;
              Server.Connected := conNotConnected;
              Lst.Strings[0] := LowerCase(Lst.Strings[0]);
              Server.Host := Lst.Strings[0];
              Server.Port := StrToIntDef(Lst.Strings[1], 8888);
              Server.Server_Handle := 0;
              if Lst.Count > 4 then
              begin
                Server.MyPassword := Lst.Strings[2];
                Server.RemotePassword := Lst.Strings[3];
                Server.Authentication :=
                  TServerAuthentication(StrToIntDef(Lst.Strings[4],
                  Ord(Server.Authentication)));
              end;
              if Lst.Count > 5 then
                Server.Compress :=
                  TZCompressionLevel(StrToIntDef(Lst.Strings[5], 3));
              if Lst.Count > 6 then
                Server.Relink := StrToIntDef(Lst.Strings[6], 0) * 60000;
              if Lst.Count > 7 then
                Server.Comments := Lst.Strings[7];
              if Lst.Count > 8 then
                Server.Forced_Ip := Lst.Strings[8];
              if Lst.Count > 9 then
                Server.Alias := Lst.Strings[9];
              Server.TrueStats := False;
              if Lst.Count > 10 then
                if Lst.Strings[10] = '1' then
                  Server.TrueStats := True;
              Server.Redirects := 0;
              if Server.Relink = 1000 then
                Server.Relink := 900000;
              if Server.Relink < 60000 then
                Server.Relink := 0;
              if Server.Relink > 3600000 then
                Server.Relink := Server.Relink div 60;
              DB_Servers.Add(Server);
            end;
  end;
  FreeStringList(Lst);
  FreeStringList(List);
end;

procedure SaveServers;
var
  Str, Str2: string;
  List: TMyStringList;
  I: Integer;
  Server: TServer;
begin
  List := CreateStringList;
  for I := 0 to DB_Servers.Count - 1 do
  begin
    Server := DB_Servers.Items[I];
    if Server.Host <> ServerName_T then
    begin
      if Server.TrueStats = False then
        Str2 := '0'
      else
        Str2 := '1';
      Str := Server.Host + ' ' + IntToStr(Server.Port) + ' ' +
        AddStr(Server.MyPassword) + ' ' + AddStr(Server.RemotePassword) + ' ' +
        IntToStr(Ord(Server.Authentication)) + ' ' + IntToStr(Ord(Server.Compress))
        + ' ' + IntToStr(Server.Relink div 60000) + ' ' + AddStr(Server.Comments) +
        ' ' + AddStr(Server.Forced_Ip) + ' ' + AddStr(Server.Alias) + ' ' +
        AddStr(Str2);
      List.Add(Str);
    end;
  end;
  List.Sort;
  List.Insert(0, RS_Servers_FileDescription00);
  List.Insert(1, RS_Servers_FileDescription01);
  List.Insert(2, RS_Servers_FileDescription02);
  List.Insert(3, RS_Servers_FileDescription03);
  List.Insert(4, RS_Servers_FileDescription04);
  List.Insert(5, RS_Servers_FileDescription05);
  List.Insert(6, RS_Servers_FileDescription06);
  List.Insert(7, RS_Servers_FileDescription07);
  List.Insert(8, RS_Servers_FileDescription08);
  List.Insert(9, RS_Servers_FileDescription09);
  List.Insert(10, RS_Servers_FileDescription10);
  List.Insert(11, RS_Servers_FileDescription11);
  try
    List.SaveToFile(ApplicationDir + 'servers');
  except
    on E: Exception do
      DebugLog('SaveServers : Exception ' + E.Message);
  end;
  FreeStringList(List);
end;

procedure CreateConsoleList;
var
  I: Integer;
  Srv: TServer;
begin
  Console_List := '';
  Handle_List := '';
  for I := 0 to DB_Servers.Count - 1 do
  begin
    Srv := DB_Servers.Items[I];
    if Srv.Logged then
    begin
      Console_List := Console_List + Srv.Console + ' ';
      Handle_List := Handle_List + IntToStr(Srv.Server_Handle) + ' ';
    end;
  end;
  Console_List := Console_List + Cons.Nick;
  Handle_List := Handle_List + IntToStr(MyServerHandle);
end;

constructor TServer.Create;
begin
  inherited Create;
  Reg_User := '';
  Host := '';
  Port := 8888;
  Thread := nil;
  MyPassword := 'password';
  RemotePassword := 'password';
  Compress := zcMax;
  Relink := 0;
  Comments := '';
  Forced_Ip := '';
  Alias := '';
  Socket := INVALID_SOCKET;
  Out_List := nil;
  Out_Buf := nil;
  Recv_Buf := '';
  ResetData;
end;

destructor TServer.Destroy;
begin
  if Thread <> nil then
  try
    Thread.Terminate;
    Thread := nil;
  except
  end;
  if Socket <> INVALID_SOCKET then
  begin
    DoCloseSocket(Socket);
    Socket := INVALID_SOCKET;
  end;
  if Out_List <> nil then
  try
    FreeCmdList(Out_List);
    Out_List := nil;
  except
  end;
  if Out_Buf <> nil then
  try
    FreeCmdList(Out_Buf);
    Out_Buf := nil;
  except
  end;
  SetLength(Recv_Buf, 0);
  SetLength(Host, 0);
  SetLength(Version, 0);
  SetLength(Console, 0);
  SetLength(Mypassword, 0);
  SetLength(Remotepassword, 0);
  SetLength(Alias, 0);
  inherited Destroy;
end;

procedure TServer.ResetData;
begin
  SetLength(Recv_Buf, 0);
  Recv_Len := 0;
  Num_Users := 0;
  Num_Files := 0;
  Max_Users := 0;
  Num_Bytes := 0;
  Console := '';
  Hub := nil;
  Connected := conNotConnected;
  Version := '';
  Server_Handle := 0;
  Logged := False;
  Login_Start := GetTickCount;
  Lag := False;
  Incoming := False;
end;

procedure TServer.Relay(Id: Integer; Cmd: string; Dst, Src: Integer);
begin
  if Connected = conNotConnected then Exit;
  if Id <> MSG_SRV_RELAY then
  begin
    Cmd := IntToStr(Dst) + ' ' + IntToStr(Src) + ' ' + IntToStr(Id) + ' ' + Cmd;
    Id := MSG_SRV_RELAY;
  end;
  if Hub <> nil then
    Hub.Relay(Id, Cmd, Dst, Src)
  else
    Exec(Id, Cmd, False);
end;

procedure TServer.Exec(Id: Integer; Cmd: string; Log_Cmd: Boolean = True);
var
  Str: string;
begin
  if Connected = conNotConnected then Exit;
  if Hub <> nil then
  begin
    Hub.Relay(Id, Cmd, Server_Handle, MyServerHandle);
    Exit;
  end;
  if Out_List = nil then
    Out_List := CreateCmdList;
  if Log_Servercommands and Log_Cmd then
  begin
    if Id <> MSG_SRV_COMPRESSED then
      Str := 'Sending Server command [' + IntToStr(Id) + '] "' + Cmd + '" (' +
        Host + ')'
    else
      Str := 'Sending Compressed server Data (' + Host + ')';
    Log(0, Str, True);
  end;
  Str := '    ' + Cmd;
  Str[1] := Chr(Length(Cmd) div 256);
  Str[2] := Chr(Length(Cmd) and 255);
  Str[3] := Chr(Id div 256);
  Str[4] := Chr(Id and 255);
  if (Out_List.GetLength + Length(Str)) > MAX_SERVER_COMMAND then
    Compile;
  Out_List.AddCmd(GetTickCount, Str);
end;

procedure TServer.Connect;
begin
  Connected := conConnecting;
  CreateConsoleList;
  Thread := TServerThread.Create(Self);
end;

procedure TServer.AddToOutput(Str: string);
begin
  if Out_Buf = nil then
    Out_Buf := CreateCmdList;
  { if Length(Str) < 1 then Exit;
   while Length(Str) > 768 do
   begin
     Str1 := Copy(Str, 1, 512);
     Delete(Str, 1, 512);
     Out_Buf.AddCmd(GetTickCount, Str1);
   end;}
  Out_Buf.AddCmd(GetTickCount, Str);
end;

procedure TServer.Compile;
var
  I: Integer;
  Cmd: TNapCmd;
  Str: string;
  // Old_Mem, New_Mem: Integer;
  Adding: Boolean;
begin
{$IFDEF CHECK_LEAK}
  Old_Mem := AllocMemSize;
{$ENDIF}
  if Out_Buf = nil then
    Out_Buf := CreateCmdList;
  if (Out_List = nil) or (Out_List.Count < 1) then Exit;
  while Out_List.Count > 0 do
  begin
    if Logged and (Compress <> zcNone) then
    begin // try to Compress
      Str := '';
      I := 0;
      Adding := True;
      while (Out_List.Count > 0) and (I < MAX_SERVER_COMMAND) and adding do
      begin
        Cmd := Out_List.Cmd(0);
        if (I + Length(Cmd.Cmd)) < MAX_SERVER_COMMAND then
        begin
          Out_List.Delete(0);
          Str := Str + Cmd.Cmd;
          I := Length(Str);
        end
        else
          Adding := False;
      end;
{$I CheckSync.pas}
      Cmd.Cmd := '    ' + ZCompressStr(Str, Compress);
{$I CheckSync.pas}
      I := Length(Cmd.Cmd) - 4;
      Cmd.Cmd[1] := Chr(I div 256);
      Cmd.Cmd[2] := Chr(I and 255);
      Cmd.Cmd[3] := Chr(MSG_SRV_COMPRESSED div 256);
      Cmd.Cmd[4] := Chr(MSG_SRV_COMPRESSED and 255);
      if I > 0 then
        AddToOutput(Cmd.Cmd);
      SetLength(Cmd.Cmd, 0);
      SetLength(Str, 0);
{$IFDEF CHECK_LEAK}
      New_Mem := AllocMemSize;
      if (New_Mem - Old_Mem) > POSSIBLE_LEAK then
        DebugLog('Possible Leak in TServer.Compile (1): ' + IntToStr(New_Mem -
          Old_Mem) + ' Bytes allocated');
{$ENDIF}
    end
    else
    begin
      // Uncompressed
      while Out_List.Count > 0 do
      begin
        AddToOutput(Out_List.Str(0));
        Out_List.Delete(0);
      end;
{$IFDEF CHECK_LEAK}
      New_Mem := AllocMemSize;
      if (New_Mem - Old_Mem) > POSSIBLE_LEAK then
        DebugLog('Possible Leak in TServer.Compile (2): ' + IntToStr(New_Mem -
          Old_Mem) + ' Bytes allocated');
{$ENDIF}
      Flush;
      Exit;
    end;
  end;
  Flush;
end;

procedure TServer.Flush;
var
  Cmd: TNapCmd;
  // Old_Mem, New_Mem: Integer;
  Last_Error: Integer;
begin
  if Connected <> conConnected then Exit;
  if Hub <> nil then Exit;
{$IFDEF CHECK_LEAK}
  Old_Mem := AllocMemSize;
{$ENDIF}
  if (Out_Buf = nil) or (Out_Buf.Count = 0) then
    Compile;
  Lag := False;
  if Out_Buf = nil then Exit;
  if Out_Buf.Count < 1 then Exit;
  if Socket = INVALID_SOCKET then Exit;
  if not CanSend(True) then Exit;
  Last_Error := 0;
{$I CheckSync.pas}
  while (Last_Error = 0) and (Out_Buf.Count > 0) do
  begin
    Cmd := Out_Buf.Cmd(0);
    TCPSocket_SendString(Socket, Cmd.Cmd, Last_Error);
    if Last_Error = WSAEWOULDBLOCK then
    begin
      Lag := True;
{$IFDEF CHECK_LEAK}
      New_Mem := AllocMemSize;
      if (New_Mem - Old_Mem) > POSSIBLE_LEAK then
        DebugLog('Possible Leak in TServer.Flush (1): ' + IntToStr(New_Mem -
          Old_Mem) + ' Bytes allocated');
{$ENDIF}
      Exit;
    end;
    if Last_Error <> 0 then
    begin
      Wallop(MSG_SERVER_NOSUCH, wallopServer, GetLangT(LNG_DELINK2, Host,
        IntToStr(Last_Error), GetErrorDesc(Last_Error), 'Flush'), True);
      DisconnectServer(Self, True, False, 'Flush');
{$IFDEF CHECK_LEAK}
      New_Mem := AllocMemSize;
      if (New_Mem - Old_Mem) > POSSIBLE_LEAK then
        DebugLog('Possible Leak in TServer.Flush (2): ' + IntToStr(New_Mem -
          Old_Mem) + ' Bytes allocated');
{$ENDIF}
      Exit;
    end;
    Out_Buf.Delete(0);
    Inc(Bytes_Out, Length(Cmd.Cmd));
    Inc(Bandwidth_Up, Length(Cmd.Cmd));
    if not CanSend(True) then Exit;
    if Out_Buf.Count < 1 then
    begin
{$IFDEF CHECK_LEAK}
      New_Mem := AllocMemSize;
      if (New_Mem - Old_Mem) > POSSIBLE_LEAK then
        DebugLog('Possible Leak in TServer.Flush (3): ' + IntToStr(New_Mem -
          Old_Mem) + ' Bytes allocated');
{$ENDIF}
      Exit;
    end;
  end;
end;

procedure TServer.Receive(N: Integer);
var
  I: Integer;
  // Old_Mem, New_Mem: Integer;
  Last_Error: Integer;
begin
  if not Running then Exit;
  if Connected <> conConnected then Exit;
  if Hub <> nil then Exit;
  if Socket = INVALID_SOCKET then Exit;
  if not CanReceive(True) then Exit;
  if Length(Recv_Buf) < 4 then
    SetLength(Recv_Buf, RECV_BUF_SIZE_SERVER);
  if not TCPSocket_CanRead(Socket, 0, Last_Error) then
  begin
    if Last_Error = WSAEWOULDBLOCK then Exit;
    if Last_Error <> 0 then
    begin
      Wallop(MSG_SERVER_NOSUCH, wallopServer, GetLangT(LNG_DELINK2, Host,
        IntToStr(Last_Error), GetErrorDesc(Last_Error), 'Receive0'), True);
      DisconnectServer(Self, True, False, 'Receive0');
    end;
    Exit;
  end;
  I := RECV_BUF_SIZE_SERVER - Recv_Len;
  I := TCPSocket_RecvBuffer(Socket, @Recv_Buf[Recv_Len + 1], I, Last_Error);
  if Last_Error = WSAEWOULDBLOCK then Exit;
  if Last_Error <> 0 then
  begin
    Wallop(MSG_SERVER_NOSUCH, wallopServer, GetLangT(LNG_DELINK2, Host,
      IntToStr(Last_Error), GetErrorDesc(Last_Error), 'Receive'), True);
    DisconnectServer(Self, True, False, 'Receive');
    Exit;
  end;
{$I CheckSync.pas}
  if I < 1 then Exit;
  Inc(Bytes_in, I);
  Inc(Recv_Len, I);
  Inc(Bandwidth_Down, I);
  if Recv_Len < 4 then Exit;
  I := Ord(Recv_Buf[2]) + 256 * Ord(Recv_Buf[1]);
  SetLength(GCmd.Cmd, 0);
  while I < (Recv_Len - 3) do // Processing received Buffer
  begin
    GCmd.Id := Ord(Recv_Buf[4]) + 256 * Ord(Recv_Buf[3]);
    SetLength(GCmd.Cmd, I);
    if I > 0 then
      Move(Recv_Buf[5], GCmd.Cmd[1], I);
    Move(Recv_Buf[I + 5], Recv_Buf[1], Recv_Len - I - 4);
    Dec(Recv_Len, I + 4);
{$IFDEF CHECK_LEAK}
    Old_Mem := AllocMemSize;
{$ENDIF}
    ProcessServerCommand(Self);
{$IFDEF CHECK_LEAK}
    New_Mem := AllocMemSize;
    if (New_Mem - Old_Mem) > POSSIBLE_LEAK then
      DebugLog('Possible Leak in ProcessServerCommand (' + IntToStr(GCmd.Id) +
        ',' + GCmd.Cmd + '): ' + IntToStr(New_Mem - Old_Mem) +
        ' Bytes allocated');
{$ENDIF}
    if Connected <> conConnected then Exit;
    if Num_Processed > Max_Server_Commands then Exit;
    if Recv_Len > 3 then
      I := Ord(Recv_Buf[2]) + 256 * Ord(Recv_Buf[1])
    else
      I := Recv_Len;
  end;
  if Socket <> INVALID_SOCKET then
  begin
    Inc(N);
    if N >= SERVER_MAX_RECEIVE_RECURSE then Exit;
    Receive(N);
  end;
end;

function TServer.CountLag: Integer;
begin
  Result := 0;
  if Out_Buf <> nil then
    if Out_Buf.Count > 0 then
    begin
      Result := GetTickCount - Out_Buf.Id(0);
      Exit;
    end;
  if Out_List <> nil then
    if Out_List.Count > 0 then
    begin
      Result := GetTickCount - Out_List.Id(0);
      Exit;
    end;
end;

constructor TServerThread.Create(Server: TServer);
begin
  Srv := Server;
  Ip := Server.Forced_Ip;
  inherited Create(False);
end;

procedure TServerThread.ResolveIP;
var
  List: TMyStringList;
begin
  if Ip <> '' then Exit;
  List := CreateStringList;
  ResolveNameToIP(Srv.Host, List);
  if List.Count > 0 then
    Ip := List.Strings[0];
  FreeStringList(List);
end;

procedure TServerThread.Execute;
var
  Last_Error: Integer;
  // Alias_Str: string;
begin
  Linking := True;
  FreeOnTerminate := True;
  Priority := tpLower;
  if Srv.Socket <> INVALID_SOCKET then
  begin
    DoCloseSocket(Srv.Socket);
    Srv.Socket := INVALID_SOCKET;
  end;
  ResolveIP;
  if not Running then
    Terminate;
  if Terminated then Exit;
  if Ip = '' then
  begin
    Error(GetLangE(LNG_LINKERRRESOLVE, Srv.Host));
    Srv.Thread := nil;
    Srv.Connected := conNotConnected;
    Linking := False;
    Exit;
  end;
  if not Running then
    Terminate;
  if Terminated then Exit;
  Srv.Socket := SynSock.Socket(PF_INET, Integer(SOCK_StrEAM), IPPROTO_TCP);
  Srv.Login_Start := Current_Time;
  Inc(Sockets_Count);
  TCPSocket_Connect(Srv.Socket, Ip, IntToStr(Srv.Port), Last_Error);
  if not Running then
    Terminate;
  if Terminated then Exit;
  Srv.Login_Start := Current_Time;
  if Last_Error <> 0 then
  try
    Error(GetLangE(LNG_LINKERRCONNECT, Srv.Host, GetErrorDesc(Last_Error)));
    DoCloseSocket(Srv.Socket);
    Srv.Socket := INVALID_SOCKET;
    Srv.Thread := nil;
    Srv.Connected := conNotConnected;
    Linking := False;
    Exit;
  except
    Exit;
  end;
  TCPSocket_Block(Srv.Socket, False); // Make it Non-blocking
  TCPSocket_KeepAlive(Srv.Socket, True);
  if not Sockets_Servers_default then
  begin
    TCPSocket_SetSizeRecvBuffer(Srv.Socket, Sockets_Servers_Recv);
    TCPSocket_SetSizeSendBuffer(Srv.Socket, Sockets_Servers_Send);
  end;
  Srv.Exec(920, '00');
  Srv.Exec(MSG_SRV_LOGIN, AddStr(ServerName_T) + ' ' + IntToStr(Server_Port[0]) +
    ' ' + AddStr(SLAVANAP_FULL) + ' ' + AddStr(NET_BUILD) + ' ' +
    IntToStr(MyServerHandle) + ' ' + AddStr(Cons.Nick) + ' ' +
    AddStr(Cons_Reg_User) + ' ' + AddStr(Console_List) + ' ' +
    AddStr(Handle_List));
  Synchronize(SyncComplete);
  Srv.Thread := nil;
  Srv.Connected := conConnected;
  Srv.Incoming := False;
  Srv.Logged := False;
  Linking := True;
end;

destructor TServerThread.Destroy;
begin
  inherited Destroy;
end;

procedure TServerThread.SyncComplete;
begin
  if not Running then Exit;
  Cmd_List.AddDoubleCmd(MSG_CMD_WALLOP, 0, GetLangE(LNG_LINKSRVLOGGED, Srv.Host),
    '');
end;

procedure TServerThread.Error(Str: string);
begin
  Err_Msg := Str;
  Synchronize(SyncError);
end;

procedure TServerThread.SyncError;
begin
  if not Running then Exit;
  Cmd_List.AddDoubleCmd(MSG_CMD_WALLOP, 0, Err_Msg, '');
end;

end.
