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

 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: STypes (Was: Types.pas)

 SlavaNap types

*********************************************************}
unit STypes;

interface

uses
  Windows, Classes, Classes2, SysUtils, Constants, Md5, BlckSock, WinSock,
  Graphics, SlavaStrings, Class_Cmdlist, Class_CmdExList, Class_DoubleCmdList,
  StringResources;

{$I Defines.pas}

type
  Time_T = LongWord;
  Bool3 = (Unknown3, True3, False3);
  ConfigLoadMode = (lmInitial, lmRest, lmAll);
  TQuery = (queryNormal, queryOperServ, queryNickServ, queryChanServ,
    queryMsgServ, queryChannel, queryServer, queryRemoteUser);
  TUserState = set of (UserMuzzled, // User is Muzzled
    userChatting, // User is Chatting in Channel
    userHideErrors, // Usermode ERROR - Hide error Messages
    userHideAnnouncements, // Usermode ANNOUNCE - Hide announcements
    // for mods+ Only
    userCloaked, // User is Cloaked
    {userHideBans, // Usermode BAN - Hide ban/Unban messages}
    userHideSBans, // Usermode SBAN - Hide server Ban/unban Messages (Minsharing and Changed mx)
    userHideSBanConn, // Usermode SBANCONN - Hide messages About connection Attempts banned By server Bans
    userHideMBans, // Usermode MBAN - Hide ban/Unban messages About bans From mod+ (NOT Server)
    userHideMBanConn, // Usermode MBANCONN - Hide messages About connection Attempts banned By mod+ Bans
    userHideChange, // Usermode CHANGE - Hide messages About changes in user Info
    userHideKill, // Usermode KILL - Hide kill/Nuke messages
    userHideLevel, // Usermode LEVEL - Hide notifications About user's Level change
    userHideServer, // Usermode SERVER - Hide server Link messages
    userHideMuzzle, // Usermode MUZZLE - Hide muzzle/Unmuzzle notifications
    userHidePort, // Usermode PORT - Hide dataport Changes
    userHideWallop, // Usermode WALLOP - Hide wallop Messages
    userHideCloak, // Usermode CLOAK - Hide cloak/Uncloak messages
    userHideFlood, // Usermode FLOOD - Hide flood Protection messages
    userHidePM, // Usermode MSG - Hide private Messages
    userHideWhois, // Usermode WHOIS - Hide whois Notifications
    userHideFriends, // Usermode FRIEND - Hide messages About friends List
    userHideChannel, // Usermode CHANNEL - Hide messages About channel Bans, Clearing or Dropping channels, Op/deop...
    userHideRegister, // Usermode REGISTER - Hide notifications About new registrations on server
    userHideVar, // Usermode VAR - Hide changes in server Config
    userHideBrowse, // Usermode BROWSE - Hide browse Notifications
    userHidePing, // Usermode PING - Hide ping/Pong notifications
    userHideMotd, // Usermode MOTD - Hide MOTD
    userHideLeech, // Usermode LEECH - Hide notification of leech Logins
    userHideSameNic); // Usermode SAMENIC - Hide 748 Error
  TConsoleList = (consFriends, consIgnored, consBlocks);
  TBlockedMessageType = (blckNone, blckCustom, blckDefault);
  TWallopType = (wallopWallop, // userHideWallop
    {wallopBan, // userHideBans}
    wallopSBan, // userHideSBans
    wallopSBanConn, // userHideSBanConn
    wallopMBan, // userHideMBans
    wallopMBanConn, // userHideMBanConn
    wallopChange, // userHideChange
    wallopKill, // userHideKill
    wallopLevel, // userHideLevel
    wallopServer, // userHideServer
    wallopMuzzle, // userHideMuzzle
    wallopPort, // userHidePort
    wallopCloak, // userHideCloak
    wallopFlood, // userHideFlood
    wallopWhois, // userHideWhois
    wallopFriends, // userHideFriends
    wallopAnnouncement, // userHideAnnouncement
    wallopChannel, // userHideChannel
    wallopRegister, // userHideRegister
    wallopVar, // userHideVar
    wallopPing, // userHidePing
    wallopMotd, // userHideMotd
    wallopLeech); // userHideLeech
  TNapSpeed = (napSpeedUnknown, napSpeed14, napSpeed28,
    napSpeed33, napSpeed56, napSpeed64ISDN,
    napSpeed128ISDN, napSpeedCable, napSpeedDSL,
    napSpeedT1, napSpeedT3);
  TNapUserLevel = (napUserLeech, napUserUser, napUserModerator,
    napUserAdmin, napUserElite, napUserConsole);
  TNapCompare = (napNoCompare, napAtLeast, napEqual, napAtBest);
  TNapMimeType = (napTypeAny, napTypeMP3, napTypeAudio, napTypeVideo,
    napTypeText, napTypeImage, napTypeApplication);
  TSearchStruct = record
    Include: string;
    Exclude: string;
    Max: Word; // MAX_RESULTS
    Mime: Integer; // TYPE
    Speed_Cmp: TNapCompare; // LINESPEED
    Speed: TNapSpeed;
    Bitrate_Cmp: TNapCompare; // Bitrate
    Bitrate: Word;
    Frequency_Cmp: TNapCompare; // FREQ
    Frequency: Integer;
    Size_Cmp: TNapCompare; // SIZE
    Size: Int64;
    Time_Cmp: TNapCompare; // DURATION
    Time: Integer;
    Local: Boolean; // LOCAL_ONLY
    Nonmp3: Boolean; // for WinMX Search
    Ext_Soft: Boolean; // for GroundLevel Only - Add software to list
    Ext_Queue: Boolean; // for CQ_EX Only - Add queue Status to List
  end;

function CanReceive(Server: Boolean): Boolean;
function CanSend(Server: Boolean): Boolean;
procedure CheckBandwidthTime;
function HexData(Str: string): string;
function HexData2(Str: string): string;
function Hex2Str(Str: string): string;
function IsDigit(Str: string): Boolean;
function GetDigit(Str: string): string;
function Compare2Str(Cmp: TNapCompare): string;
function Str2Compare(Str: string): TNapCompare;
function ConvertCompare(Str: string): string;
function Speed2Str(Spd: TNapSpeed): string;
function Str2Speed(Str: string): TNapSpeed;
function MimeType2Str(Value: TNapMimeType): string;
function Str2MimeType(Str: string): TNapMimeType;
function TrimSpaces(Str: string): string;
function NextParam(Str: string; Num: Integer = 1): string;
function FirstParam(Str: string): string;
function SplitStringOld(Str: string; Lst: TMyStringList): Integer; overload;
function SplitString(Str: string; Lst: TMyStringList): Integer; overload;
function SplitString(Str: string; var Hash: TStringHash): Integer; overload;
function JoinString(List: TMyStringList): string; overload;
function JoinString(Hash: TStringHash): string; overload;
function NextParamEx(Str: string; Num: Integer = 1): string;
function GetMimeType(FileName: string): TNapMimeType;
function Level2Str(Value: TNapUserLevel): string;
function Str2Level(Value: string): TNapUserLevel;
function compare(Cmp: TNapCompare; Et: Integer; Src: Integer): Boolean;
function Decode_Ip(Value: string): string; overload;
function Decode_Ip(Value: Cardinal): string; overload;
function Encode_Ip_Str(Str: string): string;
function Encode_Ip(Str: string): LongWord;
function Encode_Ip_Rev(Str: string): string;
function Index_Ip(Ip: Cardinal): Integer;
function IPisLocal(Str: string): Boolean;
function UnixTimeToDateTime(Time: Time_T): TDateTime;
function DateTimeToUnixTime(Time: TDateTime): Time_T;
function UnixTimeToStr(T: Time_T): string;
function StrToUnixTime(Str: string): Time_T;
function StrToUnixTimeDef(Str: string; Def: Time_T): Time_T;
function GetTickCountT: Time_T;
function FileSize(const FileName: string): Integer;
function NapCmd(Id: Integer; Cmd: string): TNapCmd;
function NapCmdEx(Id: Integer; Cmd: string; Data: string): TNapCmdEx;
function NapDoubleCmd(Id1, Id2: Integer; Cmd1, Cmd2: string): TNapDoubleCmd;
function GetLogTime: string;
procedure Log(Id: Integer; Cmd: string; File_Only: Boolean = False);
procedure DebugLog(Cmd: string; File_Only: Boolean = False);
procedure LogConsole(Id: Integer; Cmd: string);
function IntToStrDot(Value: Integer): string; overload;
function IntToStrDot(Value: Int64): string; overload;
procedure ReplaceString(var Source: string; Old, New: string);
function StrIpString(Str: string; Minus: Boolean = False): string;
function MatchesList(List: TMyStringList; Str: string): Integer;
function Encode(Src: string; Complete: Boolean = True): string;
function Null_Pass: string;
function Check_Name(Str: string; Ignore: Boolean = False): Boolean;
function Check_Software(Str: string): Boolean;
function Check_Port(Port: Integer): Boolean;
function ChannelName(Str: string): string;
function GetIndex(List: TMyStringList; Str: string;
  Ignore_case: Boolean): Integer;
procedure Check_Name_Key(var Key: Char);
function SocketError(I: Integer): string;
function IsLevel(Value: string): Boolean;
function Min(A, B: Integer): Integer; overload;
function Min(A, B: Int64): Int64; overload;
function Max(A, B: Integer): Integer; overload;
function Max(A, B: Int64): Int64; overload;
function CountColor(C1, C2: TColor; N1, N2: Integer): TColor;
function GetRealString(Text: string): string;
procedure SlavaDrawText(Canvas: TCanvas; Text: string; P: TPoint; Color1,
  Color2, Color3: TColor);
procedure GradientFill(DC: HDC; R: TRect; StartColor, EndColor: TColor;
  Steps: Integer; Vertical: Boolean);
function AddStr(Str: string): string;
function Str2Bool(Str: string; Def: Boolean): Boolean;
function GetSoftware(Str: string): Integer;
function MatchesMaskEx(S, Mask: string): Boolean;
// function  MatchesMaskEx(const FileName, Mask: string): Boolean;
function UserState2Int(State: TUserState): Integer;
function Int2UserState(N: Integer; Chatting: Boolean): TUserState;
function Color2HTML(C: Integer): string;
procedure StartLogStartup;
procedure LogStartup(Str: string);
function Time2Str(T: Time_T): string;
function Str2Time(Str: string): Time_T;
function SetSocketCloseTime: Cardinal;
function StringCRC(Str: string; Lower_Case: Boolean): Word;
procedure MataParaEncrypt(var DB: TStringHash;
  const PassWord, FileName: string);
procedure MataParaDecrypt(var DB: TStringHash;
  const PassWord, FileName: string);
function IsCrypted(FileName: string): Boolean;

var
  Levels: array[0..5] of string;
  Bandwidth_Checkcount: Integer;
  StartupLogFile: string;

implementation

uses
  Vars, Thread, SlavaMasks, Memory_Manager, Crypt;

{* * * * * Other Functions * * * * *}

function ConvertCompare(Str: string): string;
begin
  Result := Compare2Str(Str2Compare(Str));
end;

function Compare2Str(Cmp: TNapCompare): string;
begin
  case Cmp of
    napAtLeast: Compare2Str := ' "AT LEAST" ';
    napEqual: Compare2Str := ' "EQUAL TO" ';
    napAtBest: Compare2Str := ' "AT BEST" ';
    napNoCompare: Compare2Str := '';
  end;
end;

function Str2Compare(Str: string): TNapCompare;
begin
  Result := napNoCompare;
  Str := UpperCase(Str);
  if Pos('LEAST', Str) <> 0 then
    Result := napAtLeast;
  if Pos('EQUAL', Str) <> 0 then
    Result := napEqual;
  if Pos('BEST', Str) <> 0 then
    Result := napAtBest;
end;

function Speed2Str(Spd: TNapSpeed): string;
begin
  case Spd of
    napSpeed14: Result := '14.4';
    napSpeed28: Result := '28.8';
    napSpeed33: Result := '33.6';
    napSpeed56: Result := '56K';
    napSpeed64ISDN: Result := 'ISDN-64K';
    napSpeed128ISDN: Result := 'ISDN-128K';
    napSpeedCable: Result := 'Cable';
    napSpeedDSL: Result := 'DSL';
    napSpeedT1: Result := 'T1';
    napSpeedT3: Result := 'T3';
  else
    Result := 'Unknown';
  end;
end;

function Str2Speed(Str: string): TNapSpeed;
begin
  Result := napSpeedUnknown;
  Str := LowerCase(Str);
  if IsDigit(Str) then
  begin
    Result := TNapSpeed(StrToIntDef(Str, 0));
    Exit;
  end;
  if Pos('14', Str) <> 0 then
    Result := napSpeed14;
  if Pos('28', Str) <> 0 then
    Result := napSpeed28;
  if Pos('33', Str) <> 0 then
    Result := napSpeed33;
  if Pos('56', Str) <> 0 then
    Result := napSpeed56;
  if Pos('64', Str) <> 0 then
    Result := napSpeed64ISDN;
  if Pos('128', Str) <> 0 then
    Result := napSpeed128ISDN;
  if Pos('cable', LowerCase(Str)) <> 0 then
    Result := napSpeedCable;
  if Pos('dsl', LowerCase(Str)) <> 0 then
    Result := napSpeedDSL;
  if Pos('t1', LowerCase(Str)) <> 0 then
    Result := napSpeedT1;
  if Pos('t3', LowerCase(Str)) <> 0 then
    Result := napSpeedT3;
end;

function MimeType2Str(Value: TNapMimeType): string;
begin
  case Value of
    napTypeAny: Result := 'any';
    napTypeMP3: Result := 'mp3';
    napTypeAudio: Result := 'audio';
    napTypeVideo: Result := 'video';
    napTypeText: Result := 'text';
    napTypeImage: Result := 'image';
    napTypeApplication: Result := 'application';
  end;
end;

function Str2MimeType(Str: string): TNapMimeType;
begin
  Str := LowerCase(Str);
  Result := napTypeMP3;
  if Pos('audio', Str) <> 0 then
    Result := napTypeAudio;
  if Pos('video', Str) <> 0 then
    Result := napTypeVideo;
  if Pos('text', Str) <> 0 then
    Result := napTypeText;
  if Pos('image', Str) <> 0 then
    Result := napTypeImage;
  if Pos('app', Str) <> 0 then
    Result := napTypeApplication;
  if Pos('any', Str) <> 0 then
    Result := napTypeAny;
end;

function Level2Str(Value: TNapUserLevel): string;
begin
  Result := Levels[Ord(Value)];
end;

function IsLevel(Value: string): Boolean;
var
  I: Integer;
begin
  Result := True;
  Value := AnsiLowerCase(Value);
  if Length(Value) = 1 then
  begin
    I := StrToIntDef(Value, -1);
    if (I < 0) or (I > 5) then
      Result := False;
    Exit;
  end;
  for I := 0 to 4 do
    if AnsiLowerCase(Levels[I]) = Value then Exit;
  if Value = 'leech' then Exit;
  if Value = 'moderator' then Exit;
  if Value = 'admin' then Exit;
  if Value = 'administrator' then Exit;
  if Value = 'elite' then Exit;
  if Value = 'console' then Exit;
  Result := False;
end;

function Str2Level(Value: string): TNapUserLevel;
begin
  Result := napUserUser;
  Value := AnsiLowerCase(Value);
  if Length(Value) = 1 then
  begin
    Result := TNapUserLevel(StrToIntDef(Value, 1));
    Exit;
  end;
  if AnsiLowerCase(Levels[5]) = Value then
    Result := napUserConsole;
  if AnsiLowerCase(Levels[4]) = Value then
    Result := napUserElite;
  if AnsiLowerCase(Levels[3]) = Value then
    Result := napUserAdmin;
  if AnsiLowerCase(Levels[2]) = Value then
    Result := napUserModerator;
  if AnsiLowerCase(Levels[0]) = Value then
    Result := napUserLeech;
  if Result = napUserUser then
  begin
    if AnsiLowerCase(Levels[1]) = Value then Exit;
    if Value = 'leech' then
      Result := napUserLeech;
    if Value = 'moderator' then
      Result := napUserModerator;
    if Value = 'admin' then
      Result := napUserAdmin;
    if Value = 'administrator' then
      Result := napUserAdmin;
    if Value = 'elite' then
      Result := napUserElite;
    if Value = 'console' then
      Result := napUserConsole;
  end;
end;

function GetMimeType(FileName: string): TNapMimeType;
begin
  // default value.  Extensions .mp3, Mp2 Are not Checked
  Result := napTypeMP3;
  FileName := LowerCase(ExtractFileExt(FileName));
  // Audio
  if FileName = '.wav' then
    Result := napTypeAudio;
  // if FileName = '.wma' then
  //   Result := napTypeAudio; // napTypeMP3
  // if FileName = '.ogg' then
  //   Result := napTypeAudio; // napTypeMP3
  if FileName = '.mid' then
    Result := napTypeAudio;
  if FileName = '.voc' then
    Result := napTypeAudio;
  if FileName = '.mod' then
    Result := napTypeAudio;
  if FileName = '.ra' then
    Result := napTypeAudio;
  if FileName = '.rmp' then
    Result := napTypeAudio;
  if FileName = '.rm' then
    Result := napTypeAudio;
  if FileName = '.rv' then
    Result := napTypeAudio;
  // Video
  if FileName = '.mpeg' then
    Result := napTypeVideo;
  if FileName = '.mpg' then
    Result := napTypeVideo;
  if FileName = '.avi' then
    Result := napTypeVideo;
  if FileName = '.asf' then
    Result := napTypeVideo;
  if FileName = '.mov' then
    Result := napTypeVideo;
  if FileName = '.fli' then
    Result := napTypeVideo;
  if FileName = '.flc' then
    Result := napTypeVideo;
  if FileName = '.lsf' then
    Result := napTypeVideo;
  if FileName = '.wm' then
    Result := napTypeVideo;
  if FileName = '.wmv' then
    Result := napTypeVideo;
  if FileName = '.qt' then
    Result := napTypeVideo;
  if FileName = '.viv' then
    Result := napTypeVideo;
  if FileName = '.vivo' then
    Result := napTypeVideo;
  // Text
  if FileName = '.txt' then
    Result := napTypeText;
  if FileName = '.pdf' then
    Result := napTypeText;
  if FileName = '.doc' then
    Result := napTypeText;
  if FileName = '.wri' then
    Result := napTypeText;
  // Images
  if FileName = '.gif' then
    Result := napTypeImage;
  if FileName = '.png' then
    Result := napTypeImage;
  if FileName = '.jpg' then
    Result := napTypeImage;
  if FileName = '.jpe' then
    Result := napTypeImage;
  if FileName = '.jpeg' then
    Result := napTypeImage;
  if FileName = '.psd' then
    Result := napTypeImage;
  if FileName = '.tga' then
    Result := napTypeImage;
  if FileName = '.bmp' then
    Result := napTypeImage;
  if FileName = '.fla' then
    Result := napTypeImage;
  if FileName = '.swf' then
    Result := napTypeImage;
  if FileName = '.tif' then
    Result := napTypeImage;
  if FileName = '.tiff' then
    Result := napTypeImage;
  // Applications
  if FileName = '.exe' then
    Result := napTypeApplication;
  if FileName = '.zip' then
    Result := napTypeApplication;
  if FileName = '.gz' then
    Result := napTypeApplication;
  if FileName = '.tgz' then
    Result := napTypeApplication;
  if FileName = '.hqx' then
    Result := napTypeApplication;
  if FileName = '.sit' then
    Result := napTypeApplication;
end;

function TrimSpaces(Str: string): string;
begin
  if Length(Str) > 0 then
    if Str[1] = #32 then
      Delete(Str, 1, 1);
  if Length(Str) > 0 then
    if Str[Length(Str)] = #32 then
      Delete(Str, Length(Str), 1);
  if Length(Str) > 0 then
    if Str[Length(Str)] = #10 then
      Delete(Str, Length(Str), 1);
  if Length(Str) > 0 then
    if Str[Length(Str)] = #13 then
      Delete(Str, Length(Str), 1);
  Result := Str;
end;

function NextParamEx(Str: string; Num: Integer = 1): string;
var
  C: Char;
  Startpos, Pos, Len, Count: Integer;
  Quoted, Start, Valid: Boolean;
begin
  Pos := 1;
  Len := Length(Str);
  Quoted := False;
  Start := True;
  Startpos := 1;
  Count := 0;
  while Pos <= Len do
  begin
    C := Str[Pos];
    case Ord(C) of
      32:
        begin // Space
          if (not Start) and (not Quoted) then
          begin // New string Start
            Inc(Count);
            if Count > Num then
            begin
              Result := Copy(Str, Startpos, Len - Startpos + 1);
              Exit;
            end;
            Start := True;
            Startpos := Pos + 1;
          end
          else
            Start := False;
        end;
      34:
        begin // Quote
          if Quoted then
          begin // end of Quote if end of string
            Valid := False;
            if Len < (Pos + 1) then
              Valid := True
            else if Ord(Str[Pos + 1]) = 32 then
              Valid := True;
            if Valid then // end of Line
            begin
              Inc(Count);
              if Count > Num then
              begin
                if Pos = Len then
                  Dec(Len)
                else if Startpos > 1 then
                  Dec(Startpos);
                Result := Copy(Str, Startpos, Len - Startpos + 1);
                Exit;
              end;
              Quoted := False;
              Start := True;
              Inc(Pos);
              Startpos := Pos + 1;
            end;
          end
          else if Start then
          begin
            Quoted := True;
            Start := True;
            Startpos := Pos + 1;
          end;
        end;
    else
      Start := False;
    end;
    Inc(Pos);
  end;
  if not Start then
    if (Count + 1) > Num then
    begin
      if Quoted then
        Result := Copy(Str, Startpos, Len - Startpos)
      else
        Result := Copy(Str, Startpos, Len - Startpos + 1);
      Exit;
    end;
  Result := '';
end;

{function  NextParamEx(Str:string; Num: Integer = 1): string;
var
  J: Integer;
begin
  // J := Pos(#9, Str);
  Str := TrimSpaces(Str);
  if Length(Str) > 0 then
  if Str[1] = '"' then
  begin
    Str := Copy(Str, 2, Length(Str));
    J := Pos('"', Str);
    if J < 1 then
    begin
      Result := '';
      Exit;
    end;
    Str := TrimSpaces(Copy(Str, J + 1, Length(Str)));
  end
  else
  begin
    J := Pos(' ', Str);
    if J < 1 then
    begin
      Result := '';
      Exit;
    end;
    Str := TrimSpaces(Copy(Str, J + 1, Length(Str)));
  end;
  if Num > 1 then
    Result := NextParamEx(Str, Num - 1)
  else
  Result := Str;
end;}

function JoinString(List: TMyStringList): string;
var
  Str: string;
  I: Integer;
begin
  Str := '';
  for I := 0 to List.Count - 1 do
  begin
    if I > 0 then
      Str := Str + ' ';
    if (Pos(' ', List.Strings[I]) > 0) or (Length(List.Strings[I]) < 1) then
      Str := Str + '"' + List.Strings[I] + '"'
    else
      Str := Str + List.Strings[I];
  end;
  Result := Str;
end;

function JoinString(Hash: TStringHash): string;
var
  Str: string;
  P: PStringHashItem;
begin
  Str := '';
  P := Hash.First;
  while P <> nil do
  begin
    if Str <> '' then
      Str := Str + ' ';
    if (Pos(' ', P^.Data) > 0) or (Length(P^.Data) < 1) then
      Str := Str + '"' + P^.Data + '"'
    else
      Str := Str + P^.Data;
    P := P^.Next;
  end;
  Result := Str;
end;

function FirstParam(Str: string): string;
var
  Quotes: Boolean;
  I: Integer;
begin
  Result := '';
  Str := Trim(Str);
  if Length(Str) < 1 then Exit;
  Quotes := Str[1] = '"';
  if Quotes then
  begin
    Str := Copy(Str, 2, Length(Str));
    if Length(Str) < 1 then Exit;
    I := Pos('"', Str);
  end
  else
    I := Pos(' ', Str);
  if I < 1 then
    Result := Str
  else
    Result := Copy(Str, 1, I - 1);
end;

function NextParam(Str: string; Num: Integer = 1): string;
var
  J: Integer;
begin
  // J := Pos(#9, Str);
 { while J > 0 do
  begin // replacing <TAB> with <SPACE>
    Str[J] := #32;
    J := Pos(#9, Str);
  end;}
  Str := Trim(Str);
  if Length(Str) = 0 then
    Str := 'help';
  if Str[1] = '"' then
  begin
    Str := Copy(Str, 2, Length(Str));
    J := Pos('"', Str);
    if J < 1 then
    begin
      Result := '';
      Exit;
    end;
    Str := Trim(Copy(Str, J + 1, Length(Str)));
  end
  else
  begin
    J := Pos(' ', Str);
    if J < 1 then
    begin
      Result := '';
      Exit;
    end;
    Str := Trim(Copy(Str, J + 1, Length(Str)));
  end;
  if Num > 1 then
    Result := NextParam(Str, Num - 1)
  else
    Result := Str;
end;

function SplitString(Str: string; Lst: TMyStringList): Integer;
var
  C: Char;
  Startpos, Pos, Len: Integer;
  Quoted, Start, Valid: Boolean;
begin
  Lst.Clear;
  Pos := 1;
  Len := Length(Str);
  Quoted := False;
  Start := True;
  Startpos := 1;
  // Lst.Add('Debug: StartPos=1. Data=' + Copy(Str, Pos, 256));
  while Pos <= Len do
  begin
    C := Str[Pos];
    case Ord(C) of
      32:
        begin // Space
          //           Lst.Add('Debug: Space. Pos=' + IntToStr(Pos) + ' StartPos=' + IntToStr(StartPos) + ' Quoted=' + IntToStr(Ord(Quoted)) + ' Start=' + IntToStr(Ord(Start)) + ' Data=' + Copy(Str, Pos, 256));
          if (not Start) and (not Quoted) then
          begin // New string Start
            Lst.Add(Copy(Str, Startpos, Pos - Startpos));
            Start := True;
            Startpos := Pos + 1;
          end
          else
            Start := False;
        end;
      34:
        begin // Quote
          //           Lst.Add('Debug: Quote. Pos=' + IntToStr(Pos) + ' StartPos=' + IntToStr(StartPos) + ' Quoted=' + IntToStr(Ord(Quoted)) + ' Start=' + IntToStr(Ord(Start)) + ' Data=' + Copy(Str, Pos, 256));
          if Quoted then
          begin // end of Quote if end of string
            Valid := False;
            if Len < (Pos + 1) then
              Valid := True
            else if Ord(Str[Pos + 1]) = 32 then
              Valid := True;
            if Valid then // end of Line
            begin
              Lst.Add(Copy(Str, Startpos, Pos - Startpos));
              Quoted := False;
              Start := True;
              Inc(Pos);
              Startpos := Pos + 1;
            end;
          end
          else if Start then
          begin
            Quoted := True;
            Start := True;
            Startpos := Pos + 1;
          end;
        end;
    else
      Start := False;
    end;
    Inc(Pos);
  end;
  // Lst.Add('Debug: end. Pos=' + IntToStr(Pos) + ' StartPos=' + IntToStr(StartPos) + ' Quoted=' + IntToStr(Ord(Quoted)) + ' Start=' + IntToStr(Ord(Start)));
  if not Start then
  begin
    if Quoted then
      Lst.Add(Copy(Str, Startpos, Len - Startpos))
    else
      Lst.Add(Copy(Str, Startpos, Len - Startpos + 1));
  end
  else if Start and Quoted then
    Lst.Add('');
  // Lst.Add('Debug: end.');
  Result := Lst.Count;
end;

function SplitStringOld(Str: string; Lst: TMyStringList): Integer;
var
  C: Char;
  Str1, Str2: string;
  J, Num, Max: Integer;
  B: Boolean;
begin
  Lst.Clear;
  Str1 := '';
  Str2 := Trim(Str);
  if Str2 = '' then
  begin
    Result := 0;
    Exit;
  end;
  { J := Pos(#9, Str2);
   while J > 0 do
   begin // replacing <TAB> with <SPACE>
     Str2[J] := #32;
     J := Pos(#9, Str2);
   end;}
  Max := Length(Str) + 128;
  Num := 0;
  J := 0;
  B := False; // Makes compiler Happy
  repeat
    if Length(Str2) > 0 then
    begin
      B := False;
      Str2 := Trim(Str2);
      J := Pos(' ', Str2);
      C := Str2[1];
      if C = '"' then
      begin
        J := Pos('"', Copy(Str2, 2, Max)) + 2;
        B := True;
      end;
      if J = 0 then
        J := Length(Str2)
      else
      begin
        Str := Trim(Copy(Str2, 1, J));
        if Str[1] = '"' then
          if Str[Length(Str)] = '"' then
            Str := Copy(Str, 2, Length(Str) - 2);
        Lst.Add(Str);
        Str2 := Trim(Copy(Str2, J, Max));
        Inc(Num);
        J := 0;
      end;
    end
    else
      Break;
  until J = Length(Str2);
  if not B then
    Lst.Add(Trim(Str2));
  Result := Num + 1;
end;

function SplitString(Str: string; var Hash: TStringHash): Integer; overload;
var
  C: Char;
  Str1, Str2: string;
  J, Num, Max: Integer;
  B: Boolean;
begin
  StrHash_Clear(Hash);
  Str1 := '';
  Str2 := Trim(Str);
  if Str2 = '' then
  begin
    Result := 0;
    Exit;
  end;
  Max := Length(Str) + 128;
  Num := 0;
  J := 0;
  B := False; // Makes compiler Happy
  repeat
    if Length(Str2) > 0 then
    begin
      B := False;
      Str2 := Trim(Str2);
      J := Pos(' ', Str2);
      C := Str2[1];
      if C = '"' then
      begin
        J := Pos('"', Copy(Str2, 2, Max)) + 2;
        B := True;
      end;
      if J = 0 then
        J := Length(Str2)
      else
      begin
        Str := Trim(Copy(Str2, 1, J));
        if Str[1] = '"' then
          if Str[Length(Str)] = '"' then
            Str := Copy(Str, 2, Length(Str) - 2);
        StrHash_AddEx(Hash, Str);
        Str2 := Trim(Copy(Str2, J, Max));
        Inc(Num);
        J := 0;
      end;
    end
    else
      Break;
  until J = Length(Str2);
  if not B then
    StrHash_AddEx(Hash, Trim(Str2));
  Result := Num + 1;
end;

function compare(Cmp: TNapCompare; Et: Integer; Src: Integer): Boolean;
begin
  case Cmp of
    napAtLeast: Result := Src >= Et;
    napEqual: Result := Src = Et;
    napAtBest: Result := Src <= Et;
  else
    Result := True;
  end;
end;

function Decode_Ip(Value: Cardinal): string;
var
  Str: string;
  I: Cardinal;
  J: Cardinal;
  C: array[0..3] of Byte;
begin
  for I := 0 to 3 do
  begin
    J := (Value and (256 shl (I * 8) - 1));
    if I <> 0 then
      J := J div (1 shl (I * 8));
    C[I] := J;
  end;
  Str := '';
  for I := 0 to 3 do
  begin
    Str := Str + IntToStr(C[I]);
    if I <> 3 then
      Str := Str + '.';
  end;
  Result := Str;
end;

function Decode_Ip(Value: string): string; overload;
begin
  Result := Decode_Ip(Cardinal(StrToInt64(Value)));
end;

function Encode_Ip_Str(Str: string): string;
var
  Num: array[0..3] of Int64;
  List: TMyStringList;
  I: Integer;
  N: Int64;
begin
  Result := '0';
  I := Pos('.', Str);
  while I <> 0 do
  begin
    Str[I] := ' ';
    I := Pos('.', Str);
  end;
  List := CreateStringList;
  SplitString(Str, List);
  if List.Count < 4 then Exit; // Error
  for I := 0 to 3 do
    Num[I] := StrToInt64Def(List.Strings[I], 0);
  FreeStringList(List);
  N := ((Num[3] * 256 + Num[2]) * 256 + Num[1]) * 256 + Num[0];
  Result := IntToStr(N);
end;

function Encode_Ip(Str: string): LongWord;
var
  Num: array[0..3] of LongWord;
  List: TMyStringList;
  I: Integer;
  N: LongWord;
begin
  Result := 0;
  I := Pos('.', Str);
  while I <> 0 do
  begin
    Str[I] := ' ';
    I := Pos('.', Str);
  end;
  List := CreateStringList;
  SplitString(Str, List);
  if List.Count < 4 then Exit; // Error
  for I := 0 to 3 do
    Num[I] := StrToInt64Def(List.Strings[I], 0);
  FreeStringList(List);
  N := ((Num[3] * 256 + Num[2]) * 256 + Num[1]) * 256 + Num[0];
  Result := N;
end;

function Encode_Ip_Rev(Str: string): string;
var
  Num: array[0..3] of Int64;
  I, J: Integer;
  N: Int64;
  Data: array[0..15] of string;
begin
  Result := '0';
  I := Pos('.', Str);
  J := 0;
  while I <> 0 do
  begin
    Data[J] := Copy(Str, 1, I - 1);
    Str := Copy(Str, I + 1, 64);
    I := Pos('.', Str);
    Inc(J);
  end;
  for I := 0 to 3 do
    Num[I] := StrToInt64Def(Data[I], 0);
  N := ((Num[0] * 256 + Num[1]) * 256 + Num[2]) * 256 + Num[3];
  Result := IntToStr(N);
end;

function Index_Ip(Ip: Cardinal): Integer;
begin
  Result := (Ip shr 12) and 1023;
end;

function UnixTimeToDateTime(Time: Time_T): TDateTime;
var
  T: TTimeZoneInformation;
  I: TDateTime;
  Day: Integer;
  Hour, Min, Sec: Word;
begin
  GetTimeZoneInformation(T);
  Time := Time - T.Bias * 60; // GMT -> Local Time
  Sec := Time mod 60;
  Min := (Time div 60) mod 60;
  Hour := (Time div 3600) mod 24;
  Day := Time div 86400;
  I := EncodeTime(Hour, Min, Sec, 0);
  I := I + Day + 25569;
  Result := I;
end;

function DateTimeToUnixTime(Time: TDateTime): Time_T;
// Converts TDateTime to C/C++ Time_T (Time - Local time, Result - GMT)
var
  T: TTimeZoneInformation;
  I: Time_T;
  H, M, S, X: Word;
  N: Integer;
begin
  GetTimeZoneInformation(T);
  I := Trunc(Time) - 25569;
  DecodeTime(Time, H, M, S, X);
  N := M + T.Bias;
  I := S + 60 * (N + 60 * (H + 24 * I));
  Result := I;
end;

function UnixTimetoStr(T: Time_T): string;
begin
  Result := DateTimeToStr(UnixTimeToDateTime(T));
end;

function StrToUnixTime(Str: string): Time_T;
begin
  Result := DateTimeToUnixTime(StrToDateTime(Str));
end;

function StrToUnixTimeDef(Str: string; Def: Time_T): Time_T;
begin
  try
    Result := StrToUnixTime(Str)
  except
    Result := Def;
  end;
end;

function GetTickCountT: Time_T;
begin
  Result := DateTimeToUnixTime(Now);
end;

function FileSize(const FileName: string): Integer;
var
  Handle: THandle;
  FindData: TWin32FindData;
begin
  Result := -1;
  Handle := FindFirstFile(PChar(FileName), FindData);
  if Handle <> INVALID_HANDLE_VALUE then
  begin
    Windows.FindClose(Handle);
    if (FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0 then
      Result := (FindData.nFileSizeHigh * MAXDWORD) + FindData.nFileSizeLow;
  end;
end;

function NapCmd(Id: Integer; Cmd: string): TNapCmd;
var
  A: TNapCmd;
begin
  A.Id := Id;
  A.Cmd := Cmd;
  Result := A;
end;

function NapDoubleCmd(Id1, Id2: Integer; Cmd1, Cmd2: string): TNapDoubleCmd;
var
  A: TNapDoubleCmd;
begin
  A.Id1 := Id1;
  A.Id2 := Id2;
  A.Cmd1 := Cmd1;
  A.Cmd2 := Cmd2;
  Result := A;
end;

function NapCmdEx(Id: Integer; Cmd: string; Data: string): TNapCmdEx;
var
  T: TNapCmdEx;
begin
  T.Id := Id;
  T.Cmd := Cmd;
  T.Data := Data;
  Result := T;
end;

function IPisLocal(Str: string): Boolean;
begin
  Result := False;
  if (Copy(Str, 1, 4) = '127.') or (Copy(Str, 1, 3) = '10.') or (Copy(Str, 1, 8)
    = '192.168.') then
    Result := True;
end;

function IsDigit(Str: string): Boolean;
var
  I: Integer;
begin
  Result := False;
  for I := 1 to Length(Str) do
    if not (Str[I] in ['0'..'9']) then Exit;
  Result := True;
end;

function GetDigit(Str: string): string;
var
  Str1: string;
  I: Integer;
begin
  Str1 := '';
  for I := 1 to Length(Str) do
  begin
    if IsDigit(Str[I]) then
      Str1 := Str1 + Str[I]
    else
    begin
      Result := Str1;
      Exit;
    end;
  end;
  Result := Str1;
end;

function GetLogTime: string;
begin
  Result := '[' + TimeToStr(Now) + ']: ';
end;

function IntToStrDot(Value: Integer): string; overload;
var
  Str, Str1: string;
  I: Integer;
begin
  Str := IntToStr(Value);
  Str1 := '';
  while Length(Str) > 3 do
  begin
    I := Length(Str) - 2;
    Str1 := '.' + Copy(Str, I, 3) + Str1;
    Str := Copy(Str, 1, I - 1);
  end;
  Str1 := Str + Str1;
  Result := Str1;
end;

function IntToStrDot(Value: Int64): string; overload;
var
  Str, Str1: string;
  I: Integer;
begin
  Str := IntToStr(Value);
  Str1 := '';
  while Length(Str) > 3 do
  begin
    I := Length(Str) - 2;
    Str1 := '.' + Copy(Str, I, 3) + Str1;
    Str := Copy(Str, 1, I - 1);
  end;
  Str1 := Str + Str1;
  Result := Str1;
end;

procedure ReplaceString(var Source: string; Old, New: string);
var
  Offset: Integer;
  NewStr: string;
begin
  NewStr := Source;
  Source := '';
  while NewStr <> '' do
  begin
    Offset := AnsiPos(Old, NewStr);
    if Offset = 0 then
    begin
      Source := Source + NewStr;
      Break;
    end;
    Source := Source + Copy(NewStr, 1, Offset - 1) + New;
    NewStr := Copy(NewStr, Offset + Length(Old), MaxInt);
  end;
end;

function StrIpString(Str: string; Minus: Boolean = False): string;
var
  I: Integer;
begin
  if not Minus then
    for I := 1 to Length(Str) do
      if not ((Str[I] in ['A'..'Z', 'a'..'z', '0'..'9']) or (Ord(Str[I]) > 127))
        then
        Str[I] := #32;
  if Minus then
    for I := 1 to Length(Str) do
    begin
      if not ((Str[I] in ['A'..'Z', 'a'..'z', '0'..'9']) or (Ord(Str[I]) > 127))
        then
        if Str[I] = '-' then
        begin
          if I > 1 then
            if Str[I - 1] <> ' ' then
              Str[I] := #32;
        end
        else
          Str[I] := #32;
    end;
  Result := Str;
end;

function MatchesList(List: TMyStringList; Str: string): Integer;
var
  I: Integer;
begin
  Result := -1;
  if List = nil then Exit;
  for I := 0 to List.Count - 1 do
    if MatchesMaskEx(Str, List.Strings[I]) then
    begin
      Result := I;
      Exit;
    end;
end;

function Encode(Src: string; Complete: Boolean = True): string;
var
  Str: string;
  I: Integer;
begin
  // This is a Simple function. This protection Works only Agains complete Fools.
  // I Switched to This function From usual MD5 Because some Admins tried to
  // Decode passwords Using tools That allows to crack Md5-Based passwords.
  if Complete then
    Str := LowerCase(StrMD5(Src))
  else
    Str := Src;
  I := ((Ord(Str[1]) xor 10) mod 10) + 15;
  Str := Copy(LowerCase(StrMd5('pass-' +
    Copy(StrMD5('pass-' + Str), 1, I))), 1, I);
  for I := 1 to Length(Str) do
    if Str[I] in ['0'..'9'] then
      Str[I] := Char(Ord(Str[I]) - 48 + 103);
  for I := 1 to Length(Src) do
    Src[I] := #0;
  Result := Str;
end;

function Null_Pass: string;
begin
  Result := Encode('');
end;

function Check_Name(Str: string; Ignore: Boolean = False): Boolean;
var
  I: Integer;
begin
  Result := False;
  if Length(Str) < Min_User_Name then Exit;
  if Length(Str) > Max_User_Name then Exit;
  Str := AnsiLowerCase(Str);
  // Reserved names
  if Str = 'operserv' then Exit;
  if Str = 'chanserv' then Exit;
  if Str = 'operator' then Exit;
  if Str = 'nickserv' then Exit;
  if Str = 'msgserv' then Exit;
  if Str = AnsiLowerCase(LoginIMBot) then Exit;
  if not Ignore then
  begin
    if Str = 'leech' then Exit;
    if Str = 'user' then Exit;
    if Str = 'moderator' then Exit;
    if Str = 'admin' then Exit;
    if Str = 'administrator' then Exit;
    if Str = 'elite' then Exit;
    if Str = 'server' then Exit;
    if Str = 'channel' then Exit;
    if Str = 'root' then Exit;
    for I := 0 to 4 do
      if Str = AnsiLowerCase(Levels[I]) then Exit;
  end;
  if Allow_2ByteName then
  begin
    if AnsiPos('　', Str) > 0 then Exit;
    if Pos('{\rtf', Str) > 0 then Exit;
    for I := 1 to Length(Str) do
      if (Str[I] <= '!') or (Str[I] = '.') or (Str[I] = #127) then Exit;
  end
  else
    for I := 1 to Length(Str) do
      if not (Str[I] in ['a'..'z', 'A'..'Z', '0'..'9', '_', '[', ']', '{', '}',
        '-', '@', '^', '$']) then Exit;
  Result := True;
end;

function Check_Software(Str: string): Boolean;
var
  I: Integer;
begin
  Result := False;
  Str := Trim(Str);
  if Length(Str) < 3 then Exit;
  for I := 1 to Length(Str) do
    if not (Str[I] in ['a'..'z', 'A'..'Z', '0'..'9', '-', '_', '.', '+', ' ',
      '[', ']', '(', ')', '{', '}']) then Exit;
  if LowerCase(Trim(Str)) = LowerCase(RS_STypes_ConsoleSoft) then Exit;
  Result := True;
end;

function Check_Port(Port: Integer): Boolean;
var
  P: PStringHashItem;
begin
  Result := False;
  if not Block_TransferPort then
  begin
    Result := True;
    Exit;
  end;
  P := Blocked_TransferPort_List.First;
  while P <> nil do
  begin
    if Port = StrToIntDef(P^.Data, -1) then Exit;
    P := P^.Next;
  end;
  Result := True;
end;

function ChannelName(Str: string): string;
var
  I: Integer;
begin
  Result := '';
  Str := Trim(Str);
  if Length(Str) > Max_Channel_Name then
    Str := '';
  if Length(Str) < 1 then Exit;
  if Str[1] <> '#' then
    if not (Str[1] in ['0'..'9', 'A'..'Z', 'a'..'z']) then
      Str := '#' + Str;
  I := Pos('"', Str);
  while I > 0 do
  begin
    Delete(Str, I, 1);
    I := Pos('"', Str);
  end;
  if Allow_2ByteName then
  begin
    for I := Length(Str) downto 1 do
      if Str[I] <= ' ' then
        Delete(Str, I, 1);
  end
  else
    for I := Length(Str) downto 1 do
      if (Ord(Str[I]) < 33) or (Ord(Str[I]) > 122) then
        Delete(Str, I, 1);
  if Length(Str) > 32 then
    Str := Copy(Str, 1, 32);
  if Pos('#', Copy(Str, 2, Max_Channel_Name)) > 0 then Exit;
  // if Str = '#' then
  //   Str := '';
  // if Channels_Irc then
  //   if Length(Str) > 0 then
  //     if Str[1] <> '#' then
  //       Str := '#' + Str;
  if Channels_Low then
    Str := AnsiLowerCase(Str);
  Result := Str;
end;

function GetIndex(List: TMyStringList; Str: string;
  Ignore_case: Boolean): Integer;
var
  I: Integer;
begin
  Result := -1;
  if List = nil then Exit;
  if Ignore_case then
    Str := AnsiLowerCase(Str);
  if Ignore_case then
    for I := 0 to List.Count - 1 do
      if AnsiLowerCase(List.Strings[I]) = Str then
      begin
        Result := I;
        Exit;
      end;
  if not Ignore_case then
    for I := 0 to List.Count - 1 do
      if List.Strings[I] = Str then
      begin
        Result := I;
        Exit;
      end;
end;

procedure Check_Name_Key(var Key: Char);
begin
  if not (Key in ['a'..'z', 'A'..'Z', '0'..'9', '_', '[', ']', '{', '}', '-',
    '@', '^', '$']) then
  begin
    if Key >= #32 then
      Key := #0;
    if Key = #13 then
      Key := #0;
  end;
end;

function Min(A, B: Integer): Integer;
begin
  if A > B then
    Result := B
  else
    Result := A;
end;

function Min(A, B: Int64): Int64;
begin
  if A > B then
    Result := B
  else
    Result := A;
end;

function Max(A, B: Integer): Integer;
begin
  if A < B then
    Result := B
  else
    Result := A;
end;

function Max(A, B: Int64): Int64;
begin
  if A < B then
    Result := B
  else
    Result := A;
end;

function HexData(Str: string): string;
var
  B: Boolean;
  Str1: string;
  I, J: Integer;
begin
  B := False;
  for I := 1 to Length(Str) do
    if (Ord(Str[I]) < 32) or (Ord(Str[I]) > 126) then
      B := True;
  if not B then
  begin
    Result := Str;
    Exit;
  end;
  Str1 := 'Hex:';
  for I := 1 to Length(Str) do
  begin
    J := Ord(Str[I]);
    Str1 := Str1 + IntToHex(J, 2);
  end;
  Result := Str1 + ' ';
end;

function HexData2(Str: string): string;
var
  B: Boolean;
  Str1: string;
  I, J: Integer;
begin
  B := False;
  for I := 1 to Length(Str) do
    if (Ord(Str[I]) < 32) or (Ord(Str[I]) > 126) then
      B := True;
  if not B then
  begin
    Result := Str;
    Exit;
  end;
  Str1 := 'Hex:';
  for I := 1 to Length(Str) do
  begin
    J := Ord(Str[I]);
    Str1 := Str1 + IntToHex(J, 2) + ' ';
    if Str[I] in [#32..#126] then
      Str1 := Str1 + #39 + Str[I] + #39' ';
  end;
  Result := Str1 + ' ';
end;

function Hex2Str(Str: string): string;
var
  Str1, Str2: string;
  I, J: Integer;
begin
  if Copy(Str, 1, 4) <> 'hex:' then
  begin
    Result := Str;
    Exit;
  end;
  Str := Copy(Str, 5, Length(Str) - 4);
  Str1 := '';
  Str2 := '$  ';
  for I := 0 to (Length(Str) - 1) div 2 do
  begin
    Str2[2] := Str[I * 2 + 1];
    Str2[3] := Str[I * 2 + 2];
    J := StrToInt(Str2);
    Str1 := Str1 + Chr(J);
  end;
  Result := Str1;
end;

function Str2Bool(Str: string; Def: Boolean): Boolean;
begin
  Str := Trim(LowerCase(Str));
  Result := True;
  if (Str = '1') or (Str = '"1"') then Exit;
  if (Str = 'on') or (Str = '"on"') then Exit;
  if (Str = 'true') or (Str = '"true"') then Exit;
  Result := False;
  if (Str = '0') or (Str = '"0"') then Exit;
  if (Str = 'off') or (Str = '"off"') then Exit;
  if (Str = 'false') or (Str = '"false"') then Exit;
  Result := Def;
end;

function CountColor(C1, C2: TColor; N1, N2: Integer): TColor;
var
  R1, R2, G1, G2, B1, B2, R, G, B, N: Integer;
begin
  if C1 > $00FFFFFF then
    C1 := GetSysColor(C1);
  if C1 > $00FFFFFF then
    C2 := GetSysColor(C2);
  R1 := C1 and 255;
  R2 := C2 and 255;
  G1 := (C1 div 256) and 255;
  G2 := (C2 div 256) and 255;
  B1 := (C1 div 65536) and 255;
  B2 := (C2 div 65536) and 255;
  N := N1 + N2;
  R := (R1 * N1 + R2 * N2) div N;
  G := (G1 * N1 + G2 * N2) div N;
  B := (B1 * N1 + B2 * N2) div N;
  if R < 0 then
    R := 0;
  if G < 0 then
    G := 0;
  if B < 0 then
    B := 0;
  if R > 255 then
    R := 255;
  if G > 255 then
    G := 255;
  if B > 255 then
    B := 255;
  Result := RGB(R, G, B);
end;

function GetRealString(Text: string): string;
var
  Str, Str1, Str2: string;
  I: Integer;
begin
  Str := '';
  Str2 := '';
  Str1 := Text;
  I := Pos('&', Str1);
  while I <> 0 do
  begin
    Str := Copy(Str1, 1, I - 1);
    Str2 := Str2 + Str;
    Str1 := Copy(Str1, I + 1, 255);
    if Length(Str1) > 0 then
      if Str1[1] = '&' then
      begin
        Str2 := Str2 + '&';
        Str1 := Copy(Str1, 2, 255);
      end;
    I := Pos('&', Str1);
  end;
  Str2 := Str2 + Str1;
  GetRealString := Str2;
end;

procedure SlavaDrawText(Canvas: TCanvas; Text: string; P: TPoint; Color1,
  Color2, Color3: TColor);
var
  Str, Str1: string;
  I, J: Integer;
  B: Boolean;
begin
  Str := '';
  Canvas.Brush.Style := bsClear;
  Str1 := Text;
  J := 0;
  I := Pos('&', Str1);
  while I <> 0 do
  begin
    Str := Str + Copy(Str1, 1, I - 1);
    Str1 := Copy(Str1, I + 1, 255);
    B := True;
    if Length(Str1) > 0 then
      if Str1[1] = '&' then
      begin
        Str := Str + '&';
        Str1 := Copy(Str1, 2, 255);
        B := False;
      end;
    if B then
    begin
      Canvas.Font.Color := Color1;
      Canvas.Font.Style := Canvas.Font.Style - [fsUnderline];
      Canvas.TextOut(P.X + J, P.Y, Str);
      Inc(J, Canvas.Textwidth(Str));
      Str := '';
      if Length(Str1) > 0 then
      begin
        Canvas.Font.Style := Canvas.Font.Style + [fsUnderline];
        Canvas.Font.Color := Color2;
        Canvas.TextOut(P.X + J, P.Y, Str1[1]);
        Canvas.Font.Style := Canvas.Font.Style - [fsUnderline];
        Canvas.Font.Color := Color3;
        Canvas.TextOut(P.X + J, P.Y, Str1[1]);
        Inc(J, Canvas.TextWidth(Str1[1]));
        Str1 := Copy(Str1, 2, 255);
      end;
      Canvas.Font.Color := Color1;
    end;
    I := Pos('&', Str1);
  end;
  Canvas.Font.Color := Color1;
  Canvas.TextOut(P.X + J, P.Y, Str1);
  Canvas.Brush.Style := bsSolid;
end;

procedure GradientFill(DC: HDC; R: TRect; StartColor, EndColor: TColor;
  Steps: Integer; Vertical: Boolean);
var // This procedure is taken from unit "ElVCLUtils" from freeware package "ElTree Lite"
  I: Integer;
  RBeg, RDif, Rc,
    GBeg, GDif, Gc,
    BBeg, BDif, Bc: Integer;
  Brush, OldBrush: HBrush;
  R1: TRect;
begin
  if StartColor = EndColor then
  begin
    Brush := CreateSolidBrush(ColorToRGB(StartColor));
    FillRect(DC, R, Brush);
    DeleteObject(Brush);
  end
  else
  begin
    RBeg := GetRValue(ColorToRGB(StartColor));
    GBeg := GetGValue(ColorToRGB(StartColor));
    BBeg := GetBValue(ColorToRGB(StartColor));
    RDif := GetRValue(ColorToRGB(EndColor)) - RBeg;
    GDif := GetGValue(ColorToRGB(EndColor)) - GBeg;
    BDif := GetBValue(ColorToRGB(EndColor)) - BBeg;
    R1 := R;
    for I := 0 to Steps - 1 do // Iterate
    begin
      if Vertical then
      begin
        R1.Top := R.Top + MulDiv(I, R.Bottom - R.Top, Steps);
        R1.Bottom := R.Top + MulDiv(I + 1, R.Bottom - R.Top, Steps);
      end
      else
      begin
        R1.Left := R.Left + MulDiv(I, R.Right - R.Left, Steps);
        R1.Right := R.Left + MulDiv(I + 1, R.Right - R.Left, Steps);
      end;

      Rc := RBeg + MulDiv(I, RDif, Steps - 1);
      Gc := GBeg + MulDiv(I, GDif, Steps - 1);
      Bc := BBeg + MulDiv(I, BDif, Steps - 1);

      Brush := CreateSolidBrush(RGB(Rc, Gc, Bc));
      OldBrush := SelectObject(DC, Brush);
      PatBlt(DC, R1.Left, R1.Top, R1.Right - R1.Left, R1.Bottom - R1.Top,
        PATCOPY);
      SelectObject(DC, OldBrush);
      DeleteObject(Brush);
    end; // for
  end;
end;

function AddStr(Str: string): string;
begin
  Result := Str;
  if Pos(' ', Str) > 0 then
    Result := '"' + Str + '"';
  if Trim(Str) = '' then
    Result := '""';
end;

function SocketError(I: Integer): string;
begin
  Result := Format(RS_STypes_SocketError, [I, GetErrorDesc(I)]);
end;

procedure Log(Id: Integer; Cmd: string; File_Only: Boolean = False);
var
  LogFileName: string;
begin
  if Running or File_Only then
  try
    Cmd := GetLogTime + Cmd;
    if not File_Only then
      if Sync_Reply_List <> nil then
        Sync_Reply_List.AddDoubleCmd(MSG_SR_LOG, Id, '', Cmd);
    if Log_File = nil then Exit;
    if not Log_To_File then Exit;
    Cmd := Cmd + #13#10;
    ShortDateFormat := 'yyyymmdd';
    // LogFileName := ApplicationDir + 'server-' + DateToStr(Now) + '.log';
    LogFileName := Log_Folder + 'server-' + DateToStr(Now) + '.log';
    ShortDateFormat := 'yyyy/mm/dd';
    if not FileExists(LogFileName) then
    begin
      try
        Log_File.Free;
        Log_File := TFileStream.Create(LogFileName, fmCreate);
        Log_File.Free;
      except
      end;
      Log_File := nil;
      try
        Log_File := TFileStream.Create(LogFileName, fmOpenWrite or
          fmShareDenyWrite);
      except
        Log_File := nil;
        DebugLog('Error: Cannot open File ' + LogFileName);
      end;
    end;
    Log_File.write(Cmd[1], Length(Cmd));
  except
  end;
end;

procedure DebugLog(Cmd: string; File_Only: Boolean = False);
begin
  if Running or File_Only then
  try
    if not Log_To_File then Exit;
    if not File_Only then
      Log(slDebugData, Cmd, False);
    if Debug_File = nil then Exit;
    Cmd := GetLogTime + Cmd + #13#10;
    Debug_File.write(Cmd[1], Length(Cmd));
  except
  end;
end;

procedure LogConsole(Id: Integer; Cmd: string);
var
  Str, LogFileName: string;
begin
  if not Running then Exit;
  try
    Str := GetLogTime + Cmd;
    Sync_Reply_List.AddDoubleCmd(MSG_SR_CONSOLELOG, Id, '', Str);
    if Log_File = nil then Exit;
    Cmd := GetLogTime + 'Console: ' + Cmd + #13#10;
    if Log_To_File then
    begin
      ShortDateFormat := 'yyyymmdd';
      // LogFileName := ApplicationDir + 'server-' + DateToStr(Now) + '.log';
      LogFileName := Log_Folder + 'server-' + DateToStr(Now) + '.log';
      ShortDateFormat := 'yyyy/mm/dd';
      if not FileExists(LogFileName) then
      begin
        try
          Log_File.Free;
          Log_File := TFileStream.Create(LogFileName, fmCreate);
          Log_File.Free;
        except
        end;
        Log_File := nil;
        try
          Log_File := TFileStream.Create(LogFileName, fmOpenWrite or
            fmShareDenyWrite);
        except
          Log_File := nil;
          DebugLog('Error: Cannot open File ' + LogFileName);
        end;
      end;
      Log_File.write(Cmd[1], Length(Cmd));
    end;
  except
  end;
end;

function GetSoftware(Str: string): Integer;
var
  I: Integer;
begin
  Str := AnsiLowerCase(Str);
  for I := 1 to Length(Str) do
  begin
    if Ord(Str[I]) < 32 then
      Str[I] := #32;
    if Ord(Str[I]) > 127 then
      Str[I] := #32;
  end;
  Str := Trim(Str);
  if Copy(Str, 1, 9) = 'v2.0 Beta' then
    Result := softNapster
  else if Str = 'audiognome' then
    Result := softAudioGnome
  else if (Copy(Str, 1, 5) = 'winmx') and (Pos('j', Str) > 0) then
    Result := softWinMXJap
  else if Copy(Str, 1, 5) = 'winmx' then
    Result := softWinMXNormal
  else if Copy(Str, 1, 8) = 'trippymx' then
    Result := softWinMXNormal
  else if Str = 'gnomeplus+' then
    Result := softGnomePlus
  else if Copy(Str, 1, 6) = 'teknap' then
    Result := softTekNap
  else if Str = 'dagsta' then
    Result := softDagsta
  else if Copy(Str, 1, 9) = 'floodster' then
    Result := softFloodster
  else if Copy(Str, 1, 6) = 'amster' then
    Result := softAmster
  else if Copy(Str, 1, 13) = 'filenavigator' then
    Result := softFileNavigator
  else if Copy(Str, 1, 9) = 'rapigator' then
    Result := softRapigator
  else if Copy(Str, 1, 7) = 'swaptor' then
    Result := softSwaptor
  else if Copy(Str, 1, 5) = 'cq_ex' then
    Result := softCQEX
  else if Copy(Str, 1, 10) = 'sunshineun' then
    Result := softSunshineUN
  else if Copy(Str, 1, 8) = 'nap v0.8' then
    Result := softNap08
  else if Copy(Str, 1, 9) = 'napigator' then
    Result := softNapigator
  else if Copy(Str, 1, 4) = 'nap ' then
    Result := softNap
  else if Copy(Str, 1, 6) = 'macnap' then
    Result := softMacNap
  else if Copy(Str, 1, 8) = 'mp3 rage' then
    Result := softMP3Rage
  else if Copy(Str, 1, 4) = 'xnap' then
    Result := softXNap
  else if Copy(Str, 1, 9) = 'spotlight' then
    Result := softSpotlight
  else if Copy(Str, 1, 3) = 'nfs' then
    Result := softNFS
  else if Copy(Str, 1, 9) = 'dmnapster' then
    Result := softDMNapster
  else if Copy(Str, 1, 8) = 'knapster' then
    Result := softKnapster
  else if Copy(Str, 1, 7) = 'lopster' then
    Result := softLopster
  else if Copy(Str, 1, 8) = 'drumbeat' then
    Result := softDrumbeat
  else if Copy(Str, 1, 8) = 'mldonkey' then
    Result := softMlDonkey
  else if Copy(Str, 1, 7) = 'opennap' then
    Result := softOpennap
  else if Copy(Str, 1, 7) = 'napchan' then
    Result := softNapchan
  else if Copy(Str, 1, 7) = 'utatane' then
    Result := softUtatane
  else if Copy(Str, 1, 4) = '2get' then
    Result := Soft2get
  else if Copy(Str, 1, 4) = 'regnessem' then
    Result := softRegnessem
  else
    Result := softUnknown;
end;

{function MatchesMaskEx(S, Mask: string): Boolean;
var
 I, Mi: Integer;
begin
  // Taken from post by Timur Shemsedinov
  // (Timur.Shemsedinov@p88.f482.n463.z2.Fidonet.org)
  // in news://ddt.demos.su/fido7.ru.delphi
  Result := True;
  I := 0;
  Mi := 0;
  while (Result) and (not((I > Length(S)) and (Mi > Length(Mask)))) and
    (not((Mi = Length(Mask)) and (Mask[Mi] = '*'))) do
  begin
    if I = Length(S) then
    begin
      while mask[Mi]='*' do
        Inc(Mi);
      Result := Mi > Length(Mask);
      Exit;
    end;
    Result := Mi <= Length(Mask);
    if not Result then Exit;
    if Mask[Mi]='*' then
    begin;
      Inc(Mi);
      while I > Length(S) do
      begin
        Result := MatchesMaskEx(Copy(S, I, Length(S) - I + 1),
          Copy(Mask, Mi, Length(Mask) - Mi + 1));
        if not Result then
          Inc(I)
        else Exit;
      end;
      Exit;
    end
    else
    begin;
      if Mask[Mi]<>'?' then
      begin
        Result := S[I] = Mask[Mi];
        if not Result then Exit;
      end;
      Inc(I);
      Inc(Mi);
    end;
  end;
end;}

function MatchesMaskEx(S, Mask: string): Boolean;
begin
  try
    Result := MatchesMaskS(S, Mask)
  except
    Result := S = Mask;
  end;
end;

function CanReceive(Server: Boolean): Boolean;
begin
  Result := True;
  if Bandwidth_MaxDown = 0 then Exit;
  if (not Server) or Bandwidth_Limitservers then
    Result := Bandwidth_Down < Bandwidth_Alloweddown;
  Inc(Bandwidth_Checkcount);
  if Bandwidth_Checkcount >= BANDWIDTH_CYCLE then
    CheckBandwidthTime;
end;

function CanSend(Server: Boolean): Boolean;
begin
  Result := True;
  if Bandwidth_MaxUp = 0 then Exit;
  if (not Server) or Bandwidth_Limitservers then
    Result := Bandwidth_Up < Bandwidth_Allowedup;
  Inc(Bandwidth_Checkcount);
  if Bandwidth_Checkcount >= BANDWIDTH_CYCLE then
    CheckBandwidthTime;
end;

procedure CheckBandwidthTime;
var
  T: Cardinal;
  A: Integer;
begin
  T := GetTickCount;
  if Bandwidth_Checkcount >= BANDWIDTH_CYCLE then
    Dec(Bandwidth_Checkcount, BANDWIDTH_CYCLE);
  if (T - Bandwidth_Lastcheck) > BANDWIDTH_TIMEOUT then
  begin
    Inc(Bandwidth_Lastcheck, BANDWIDTH_TIMEOUT);
    Bandwidth_Limited := False;
    Bandwidth_Up := 0;
    Bandwidth_Down := 0;
    Bandwidth_Allowedup := 0;
    Bandwidth_Alloweddown := 0;
    Exit;
  end;
  if Bandwidth_MaxUp > 0 then
  begin
    Bandwidth_Allowedup := (T - Bandwidth_Lastcheck) * Bandwidth_MaxUp div 1000;
    A := Bandwidth_Up * 3 div 2;
    if A > Bandwidth_Allowedup then
      Bandwidth_Limited := True;
  end;
  if Bandwidth_MaxDown > 0 then
  begin
    Bandwidth_Alloweddown := (T - Bandwidth_Lastcheck) * Bandwidth_MaxDown div
      1000;
    A := Bandwidth_Down * 3 div 2;
    if A > Bandwidth_Alloweddown then
      Bandwidth_Limited := True;
  end;
end;

function SetSocketCloseTime: Cardinal;
begin
  Result := GetTickCount - TimeOut_Login + 2000; // 2 Seconds TimeOut
end;

function UserState2Int(State: TUserState): Integer;
var
  I: Integer;
begin
  I := 0;
  if userMuzzled in State then
    Inc(I, 1);
  // Skip userChatting
  if userHideErrors in State then
    Inc(I, 2);
  if userHideAnnouncements in State then
    Inc(I, 4);
  if userCloaked in State then
    Inc(I, 8);
  if userHideMBans in State then
    Inc(I, 16);
  if userHideMBanConn in State then
    Inc(I, 32);
  if userHideSBans in State then
    Inc(I, 64);
  if userHideSBanConn in State then
    Inc(I, 128);
  if userHideChange in State then
    Inc(I, 256);
  if userHideKill in State then
    Inc(I, 512);
  if userHideLevel in State then
    Inc(I, 1024);
  if userHideServer in State then
    Inc(I, 2048);
  if userHideMuzzle in State then
    Inc(I, 4096);
  if userHidePort in State then
    Inc(I, 8192);
  if userHideWallop in State then
    Inc(I, 16384);
  if userHideCloak in State then
    Inc(I, 32768);
  if userHideFlood in State then
    Inc(I, 65536);
  if userHidePM in State then
    Inc(I, 131072);
  if userHideWhois in State then
    Inc(I, 262144);
  if userHideFriends in State then
    Inc(I, 524288);
  if userHideChannel in State then
    Inc(I, 1048576);
  if userHideRegister in State then
    Inc(I, 2097152);
  if userHideVar in State then
    Inc(I, 2097152 * 2);
  if userHideBrowse in State then
    Inc(I, 2097152 * 4);
  if userHideMotd in State then
    Inc(I, 2097152 * 8);
  if userHidePing in State then
    Inc(I, 2097152 * 16);
  if userHideLeech in State then
    Inc(I, 2097152 * 32);
  if userHideSameNic in State then
    Inc(I, 2097152 * 64);
  Result := I;
end;

function Int2UserState(N: Integer; Chatting: Boolean): TUserState;
var
  St: TUserState;
begin
  St := [];
  if Chatting then
    St := [userChatting];
  if (N and 1) <> 0 then
    St := St + [userMuzzled];
  if (N and 2) <> 0 then
    St := St + [userHideErrors];
  if (N and 4) <> 0 then
    St := St + [userHideAnnouncements];
  if (N and 8) <> 0 then
    St := St + [userCloaked];
  if (N and 16) <> 0 then
    St := St + [userHideMBans];
  if (N and 32) <> 0 then
    St := St + [userHideMBanConn];
  if (N and 64) <> 0 then
    St := St + [userHideSBans];
  if (N and 128) <> 0 then
    St := St + [userHideSBanConn];
  if (N and 256) <> 0 then
    St := St + [userHideChange];
  if (N and 512) <> 0 then
    St := St + [userHideKill];
  if (N and 1024) <> 0 then
    St := St + [userHideLevel];
  if (N and 2048) <> 0 then
    St := St + [userHideServer];
  if (N and 4096) <> 0 then
    St := St + [userHideMuzzle];
  if (N and 8192) <> 0 then
    St := St + [userHidePort];
  if (N and 16384) <> 0 then
    St := St + [userHideWallop];
  if (N and 32768) <> 0 then
    St := St + [userHideCloak];
  if (N and 65536) <> 0 then
    St := St + [userHideFlood];
  if (N and 131072) <> 0 then
    St := St + [userHidePM];
  if (N and 262144) <> 0 then
    St := St + [userHideWhois];
  if (N and 524288) <> 0 then
    St := St + [userHideFriends];
  if (N and 1048576) <> 0 then
    St := St + [userHideChannel];
  if (N and 2097152) <> 0 then
    St := St + [userHideRegister];
  if (N and (2097152 * 2)) <> 0 then
    St := St + [userHideVar];
  if (N and (2097152 * 4)) <> 0 then
    St := St + [userHideBrowse];
  if (N and (2097152 * 8)) <> 0 then
    St := St + [userHideMotd];
  if (N and (2097152 * 16)) <> 0 then
    St := St + [userHidePing];
  if (N and (2097152 * 32)) <> 0 then
    St := St + [userHideLeech];
  if (N and (2097152 * 64)) <> 0 then
    St := St + [userHideSameNic];
  Result := St;
end;

{function  UserState2Int(State: TUserState): Integer;
var
 I: Integer;
begin
  I := 0;
  if userMuzzled in State then Inc(I, 1);
  // Skip userChatting
  if userHideErrors in State then Inc(I, 2);
  if userHideAnnouncements in State then Inc(I, 4);
  if userCloaked in State then Inc(I, 8);
  if userHideBans in State then Inc(I, 16);
  if userHideChange in State then Inc(I, 32);
  if userHideKill in State then Inc(I, 64);
  if userHideLevel in State then Inc(I, 128);
  if userHideServer in State then Inc(I, 256);
  if userHideMuzzle in State then Inc(I, 512);
  if userHidePort in State then Inc(I, 1024);
  if userHideWallop in State then Inc(I, 2048);
  if userHideCloak in State then Inc(I, 4096);
  if userHideFlood in State then Inc(I, 8192);
  if userHidePM in State then Inc(I, 16384);
  if userHideWhois in State then Inc(I, 32768);
  if userHideFriends in State then Inc(I, 65536);
  if userHideChannel in State then Inc(I, 131072);
  if userHideRegister in State then Inc(I, 262144);
  if userHideVar in State then Inc(I, 524288);
  if userHideBrowse in State then Inc(I, 1048576);
  if userHideMotd in State then Inc(I, 2097152);
  if userHidePing in State then Inc(I, 2097152*2);
  Result := I;
end;

function  Int2UserState(N: Integer; Chatting: Boolean): TUserState;
var
 St: TUserState;
begin
  St := [];
  if Chatting then St := [userChatting];
  if (N and 1) <> 0 then St := St + [userMuzzled];
  if (N and 2) <> 0 then St := St + [userHideErrors];
  if (N and 4) <> 0 then St := St + [userHideAnnouncements];
  if (N and 8) <> 0 then St := St + [userCloaked];
  if (N and 16) <> 0 then St := St + [userHideBans];
  if (N and 32) <> 0 then St := St + [userHideChange];
  if (N and 64) <> 0 then St := St + [userHideKill];
  if (N and 128) <> 0 then St := St + [userHideLevel];
  if (N and 256) <> 0 then St := St + [userHideServer];
  if (N and 512) <> 0 then St := St + [userHideMuzzle];
  if (N and 1024) <> 0 then St := St + [userHidePort];
  if (N and 2048) <> 0 then St := St + [userHideWallop];
  if (N and 4096) <> 0 then St := St + [userHideCloak];
  if (N and 8192) <> 0 then St := St + [userHideFlood];
  if (N and 16384) <> 0 then St := St + [userHidePM];
  if (N and 32768) <> 0 then St := St + [userHideWhois];
  if (N and 65536) <> 0 then St := St + [userHideFriends];
  if (N and 131072) <> 0 then St := St + [userHideChannel];
  if (N and 262144) <> 0 then St := St + [userHideRegister];
  if (N and 524288) <> 0 then St := St + [userHideVar];
  if (N and 1048576) <> 0 then St := St + [userHideBrowse];
  if (N and 2097152) <> 0 then St := St + [userHideMotd];
  if (N and (2097152*2)) <> 0 then St := St + [userHidePing];
  Result := St;
end;}

function Color2HTML(C: Integer): string;
var
  R, G, B: Integer;
begin
  R := C and 255;
  G := (C div 256) and 255;
  B := (C div 65536) and 255;
  Result := '#' + IntToHex(R, 2) + IntToHex(G, 2) + IntToHex(B, 2);
end;

function Time2Str(T: Time_T): string;
var
  Hours, Min, Sec: Integer;
  Str: string;
begin
  Sec := T mod 60;
  T := T div 60;
  Min := T mod 60;
  T := T div 60;
  Hours := T mod 24;
  Str := '';
  if Hours > 0 then
  begin
    Str := IntToStr(Hours) + ':';
    if Min < 10 then
      Str := Str + '0';
  end;
  Str := Str + IntToStr(Min) + ':';
  if Sec < 10 then
    Str := Str + '0';
  Str := Str + IntToStr(Sec);
  Result := Str;
end;

function Str2Time(Str: string): Time_T;
var
  Sec, Min, Hours: Integer;
  Str1: string;
begin
  Hours := 0;
  Min := 0;
  if Length(Str) > 6 then
  begin
    // 1:00:00
    Str1 := Copy(Str, 1, Length(Str) - 6);
    Hours := StrToIntDef(Str1, 0);
  end;
  if Length(Str) > 5 then
    Str := Copy(Str, Length(Str) - 4, 5);
  if Length(Str) > 3 then
  begin
    // 1:00
    Str1 := Copy(Str, 1, Length(Str) - 3);
    Min := StrToIntDef(Str1, 0);
  end;
  Str1 := Copy(Str, Length(Str) - 1, 2);
  Sec := StrToIntDef(Str1, 0);
  Result := Sec + 60 * Min + 3600 * Hours;
end;

function StringCRC(Str: string; Lower_Case: Boolean): Word;
var
  C: Char;
begin // Counts 2-Byte CRC of string. Used for Faster comparison
  Result := Length(Str);
  if Result > 0 then
  begin
    C := Str[Result];
    if Lower_Case then
      if (C >= 'A') and (C <= 'Z') then
        Inc(C, 32);
    Result := Ord(C) + 256 * Result;
  end;
  // Using last Character of string instead of first Because almost All databases Are already Sorted by First character
end;

procedure StartLogStartup;
var
  F: TFileStream;
begin
  try
    StartupLogFile := ApplicationDir + 'startup.log';
    if FileExists(StartupLogFile) then
      DeleteFile(StartupLogFile);
    F := TFileStream.Create(StartupLogFile, fmCreate);
    F.Free;
  except
  end;
end;

procedure LogStartup(Str: string);
var
  F: TFileStream;
begin
  try
    F := TFileStream.Create(StartupLogFile, fmOpenWrite or fmShareDenyWrite);
    F.Seek(0, 2);
    Str := Str + #13#10;
    F.write(Str[1], Length(Str));
    F.Free;
  except
  end;
end;

// DB_MsgServを暗号化してdengonファイルに保存する
// Crypt.pasのEncryptsを呼ぶ

procedure MataParaEncrypt(var DB: TStringHash;
  const PassWord, FileName: string);
const
  BuffSize = 1024;
  HeaderSize = 44;
var
  I, Blk, MLen, FTime: Integer;
  Buff: PChar;
  FS, MS: TStream;
  List: TMyStringList;
begin
  FS := TFileStream.Create(FileName, fmCreate);
  MS := TMemoryStream.Create;
  Buff := AllocMem(BuffSize);
  List := TMyStringList.Create;
  try
    StrHash_CopyToStringList(DB, List);
    List.SaveToStream(MS);
    FTime := FileAge(FileName);
    MS.Seek(0, soFromBeginning);
    Blk := MS.Size div BuffSize;
    SetPassWord(Buff, PassWord, FTime);
    FS.WriteBuffer(Buff^, HeaderSize);
    for I := 0 to Blk - 1 do
    begin
      MS.ReadBuffer(Buff^, BuffSize);
      Encrypts(Buff, BuffSize, PassWord);
      FS.WriteBuffer(Buff^, BuffSize);
    end;
    MLen := MS.Size mod BuffSize;
    if MLen > 0 then
    begin
      MS.ReadBuffer(Buff^, MLen);
      Encrypts(Buff, MLen, PassWord);
      FS.WriteBuffer(Buff^, MLen);
    end;
  finally
    FS.Free;
    MS.Free;
    FreeMem(Buff);
    List.Free;
  end;
end;

// 暗号化されたDengonファイルを内部で複号してDB_MsgServに格納する
// Crypt.pasのDecryptsを呼ぶ

procedure MataParaDecrypt(var DB: TStringHash;
  const PassWord, FileName: string);
const
  BuffSize = 1024;
  HeaderSize = 44;
var
  I, Blk, MLen, FTime: Integer;
  Buff: PChar;
  FS, MS: TStream;
  List: TMyStringList;
  PW: string;
begin
  // 暗号化されていなければ暗号化
  if not IsCrypted(FileName) then
    EncryptsFile(FileName, PassWord, False);
  FS := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
  MS := TMemoryStream.Create;
  Buff := AllocMem(BuffSize);
  try
    if FS.Size < HeaderSize - 9 then Exit;
    Blk := (FS.Size - HeaderSize) div BuffSize;
    FS.ReadBuffer(Buff^, HeaderSize);
    PW := GetPassWord(Buff, FTime);
    if PassWord <> PW then Exit;
    for I := 0 to Blk - 1 do
    begin
      FS.ReadBuffer(Buff^, BuffSize);
      Decrypts(Buff, BuffSize, PassWord);
      MS.WriteBuffer(Buff^, BuffSize);
    end;
    MLen := (FS.Size - HeaderSize) mod BuffSize;
    if MLen > 0 then
    begin
      FS.ReadBuffer(Buff^, MLen);
      Decrypts(Buff, MLen, PassWord);
      MS.WriteBuffer(Buff^, MLen);
    end;
    List := TMyStringList.Create;
    try
      MS.Seek(0, soFromBeginning);
      List.LoadFromStream(MS);
      StrHash_Clear(DB);
      for I := List.Count - 1 downto 0 do
        StrHash_Add(DB, List[I]);
    finally
      List.Free;
    end;
  finally
    FreeMem(Buff);
    FS.Free;
    MS.Free;
  end;
end;

function IsCrypted(FileName: string): Boolean;
const
  HeaderSize = 44;
  BuffSize = 1024;
var
  FTime: Integer;
  Buff: PChar;
  FS: TStream;
  PW: string;
begin
  Result := False;
  // Dengonファイルがなければ生成する
  if not FileExists(FileName) then
  try
    FS := TFileStream.Create(FileName, fmCreate);
    FS.Free;
  except
  end;
  FS := TFileStream.Create(FileName, fmOpenRead);
  Buff := AllocMem(BuffSize);
  try
    if FS.Size < HeaderSize - 9 then Exit;
    FS.ReadBuffer(Buff^, HeaderSize);
    PW := GetPassWord(Buff, FTime);
    if PW <> '' then
      Result := True;
  finally
    FreeMem(Buff);
    FS.Free;
  end;
end;

begin
  Levels[0] := 'Leech';
  Levels[1] := 'User';
  Levels[2] := 'Moderator';
  Levels[3] := 'Admin';
  Levels[4] := 'Elite';
  Levels[5] := 'Console';
  Bandwidth_Checkcount := 0;
end.
