// vim:ft=pascal

unit YTools;

{===============================================================================

   cYcnus.YTools 1.0.3 Beta for Delphi 4+
   by licenser and Murphy

   �2000-2003 by cYcnus
   visit www.cYcnus.de

   licenser@cYcnus.de (Heinz N. Gies)
   murphy@cYcnus.de (Kornelius Kalnbach)
   
   this unit is published under the terms of the GPL

===============================================================================}

interface

uses
  Windows, SysUtils, Classes, YTypes;

const
  BackSpace = #8;
  Tab = #9;
  LF = #10; //Line Feed
  CR = #13; //Carriage Return
  Space = #32;
  EOLChars = [CR, LF];
{$IFNDEF VER140}
  sLineBreak = #13#10;
  SwitchChars = ['/', '-'];
{$ENDIF}
  EOL = sLineBreak;
  MaxCard = High(Cardinal);
  AllChars = [#0..#255];
  Alphabetical = ['A'..'Z', 'a'..'z'];
  DecimalChars = ['0'..'9'];
  AlphaNumerical = Alphabetical + DecimalChars;
  StrangeChars = [#0..#31, #127, #129, #141..#144, #157, #158];

  HexadecimalChars = DecimalChars + ['A'..'F', 'a'..'f'];
  OctalChars = ['0'..'7'];
  BinaryChars = ['0', '1'];

  QuoteChars = ['''', '"'];
  WildCards = ['*', '?'];
  FileNameEnemies = WildCards + ['\', '/', ':', '<', '>', '|'];

  HexChar: array[THex] of Char = (
    '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
  LowerHexChar: array[THex] of Char = (
    '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f');
  BaseNChar: array[TBaseN] of Char = (
    '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H',
    'I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z');

  cYcnusOverlayColor = $050001;

  faFindEveryFile = faReadOnly + faHidden + faSysFile + faArchive;

  platWin9x = [VER_PLATFORM_WIN32s, VER_PLATFORM_WIN32_WINDOWS];


{ Debugging }
procedure ClearReport(const ReportName: string);
procedure Report(const ReportName, Text: string);
procedure ReportFmt(const ReportName, Fmt: string; const Args: array of const);

{ Params }
procedure GetParams(Strings: TStrings); overload;
function GetParams(const Separator: string = ' '): string; overload;

function ParamNum(const S: string): Integer;
function ParamPrefixNum(const Prefix: string): Integer;
function Param(const S: string): Boolean;
function ParamPrefix(const Prefix: string): Boolean;

function Switch(const Switch: string; const PrefixChars: TCharSet = SwitchChars;
  IgnoreCase: Boolean = True): Boolean;
function GetParam(const Prefix: string = ''; const Default: string = ''): string;

{ Dirs & UserName}
function GetMyDir(FullPath: Boolean = False): string;
function WinDir: string;
function SysDir: string;
function UserName: string;

{ Strings & Chars}
function FirstChar(const S: string): Char;
function LastChar(const S: string): Char;

function CharPos(C: Char; const S: string; Offset: Integer = 1): Integer; overload;
function CharPos(C: TCharSet; const S: string; Offset: Integer = 1): Integer; overload;
function CharPosR(C: Char; const S: string; Offset: Integer = -1): Integer;
function PosEx(const SubStr, S: string; Offset: Integer = 1): Integer;
function PosExText(const SubStr, S: string; Offset: Integer = 1): Integer;
function PosExAnsiText(const SubStr, S: string; Offset: Integer = 1): Integer;

function UntilChar(const S: string; Brake: Char): string; overload;
function UntilChar(const S: string; Brake: TCharSet): string; overload;
function UntilLastChar(const S: string; Brake: Char;
  IgnoreNoBrake: Boolean = True): string;

function FromChar(const S: string; Brake: Char): string; overload;
function FromChar(const S: string; Brake: TCharSet): string; overload;
function FromLastChar(const S: string; Brake: Char;
  IgnoreNoBrake: Boolean = False): string;

function BetweenChars(const S: string; Start, Finish: Char;
  Inclusive: Boolean = False): string;

function UntilStr(const S: string; Brake: string): string;
function FromStr(const S: string; Brake: string): string;

function StringWrap(const S: string; Width: Integer; const LineEnd: string = EOL): string;

{ Splitting & Combining }
function Split(const S, Separator: string; IgnoreMultiSep: Boolean = True;
  MinCount: Integer = 0): TStrA; overload;
procedure Split(const S, Separator: string; Strings: TStrings;
  IgnoreMultiSep: Boolean = True); overload;
function Split(const S: string; Separators: TCharSet;
  IgnoreMultiSep: Boolean = True; MinCount: Integer = 0): TStrA; overload;

procedure TileStr(const S: string; BrakeStart: Integer; BrakeEnd: Integer;
  out Left, Right: string);

function Join(Strings: TStrings; Separator: string = ' '): string; overload;
function Join(StrA: TStrA; Separator: string = ' '): string; overload;

function MulStr(const S: string; Count: Integer): string;

{ Strings ausrichten }
function AlignR(const S: string; Width: Integer; Filler: Char = ' '): string;
function MaxStr(const S: string; MaxLen: Integer): string;

{ Stringing }
function TrimAll(const S: string): string;

function ControlChar(C: Char): Boolean;
function FriendlyChar(C: Char): Char;

function FriendlyStr(const S: string): string; overload;
function FriendlyStr(a: TByteA): string; overload;

function Quote(const S: string; Quoter: Char = '"'): string;
function UnQuote(const S: string): string;
function DeQuote(const S: string): string;

function StrNumerus(const Value: Integer; const Singular, Plural: string;
  const Zero: string = '0'): string;

function MakeStr(const Items: array of const; Separator: string = ''): string;
procedure ShowText(const Items: array of const; Separator: string = '');

{ Delete }
function DeleteChars(const S: string; C: Char): string; overload;
function DeleteChars(const S: string; C: TCharSet): string; overload;
function ExtractChars(const S: string; C: TCharSet): string;

{ Find }
function CharCount(const S: string; C: Char): Integer;

function CharIn(const S: string; C: Char): Boolean; overload;
function CharIn(const S: string; C: TCharSet): Boolean; overload;

function StrAtPos(const S: string; Pos: Integer; const Str: string): Boolean;
function StrAtBegin(const S, Str: string): Boolean;
function StrIn(const S, SubStr: string): Boolean; overload;
function StrIn(A: TStrA; const S: string): Boolean; overload;
function StrIn(SL: TStrings; const S: string): Boolean; overload;
function StrIndex(A: TStrA; const S: string): Integer; overload;
function StrIndex(SL: TStrings; const S: string): Integer; overload;

function TextAtPos(const S: string; Pos: Integer; const Text: string): Boolean;
function TextAtBegin(const S, Text: string): Boolean;
function TextIn(const S, Text: string): Boolean; overload;
function TextIn(A: TStrA; const Text: string): Boolean; overload;
function TextIn(SL: TStrings; const Text: string): Boolean; overload;
function TextIndex(A: TStrA; const Text: string): Integer; overload;
function TextIndex(SL: TStrings; const Text: string): Integer; overload;

{ Replace }
function ReplaceChars(const S: string; Old, New: Char): string; overload;
function ReplaceChars(const S: string; Old: TCharSet; New: Char): string; overload;

function Replace(const S, Old, New: string): string;

{ TStrings }
function SLOfFile(const FileName: string): TStringList;
function ContainsEmptyLines(SL: TStrings): Boolean;
procedure DeleteEmptyLines(SL: TStrings);
procedure DeleteCommentLines(SL: TStrings; const CommentSign: string = '//');
procedure WriteSL(Strings: TStrings; const Prefix: string = '';
  const Suffix: string = '');

function FindLine(SL: TStrings; const S: string): Integer;

procedure QuickSortSL(SL: TStringList);

{ TStrA }
function IncStrA(StrA: TStrA): Integer;

{ TByteA }
function StrOfByteA(a: TByteA): string;
function ByteAOfStr(const S: string): TByteA;
function ByteAOfInt(i: Integer): TByteA;
function IntOfByteA(A: TByteA): Integer;
function ByteAOfHex(const Hex: string): TByteA;

function SameByteA(const A, B: TByteA): Boolean;
function Reverse(a: TByteA): TByteA;
function SaveByteA(Data: TByteA; const FileName: string; Overwrite: Boolean = True): Boolean;
function LoadByteA(const FileName: string): TByteA;

function Endian(i: Integer): Integer;

{ Files }
function SizeOfFile(const FileName: string): Integer;
function FileEx(const FileName: string; AllowFolders: Boolean = False): Boolean;
function LWPSolve(const Dir: string): string;
function LWPSlash(const Dir: string): string;

function ExtractDrive(const FileName: string): string;
function ExtractPath(const FileName: string): string;
function ExtractPrefix(const FileName: string): string;
function ExtractSuffix(const FileName: string): string;

function IsValidFileName(const FileName: string): Boolean;
function MakeValidFileName(FileName: string; const Default: string = 'File'): string;

{ Converting }
function IsValidInteger(const S: string): Boolean;
function IsValidCardinal(const S: string): Boolean;

function StrOfBool(flag: Boolean; const TrueStr: string = 'True';
  const FalseStr: string = 'False'): string;
function StrOfInt(i: Integer): string;
function CardOfStr(const S: string): Cardinal;

function HexOrd(Hex: Char): THex;
function ByteOfHex(Hex: THexByteStr): Byte;

function DecOfHex(const Hex: string): string;
function HexOfByte(b: Byte): THexByteStr;
function HexOfCard(i: Cardinal): string; overload;
function HexOfCard(i: Cardinal; Digits: Integer): string; overload;

function PascalHexArray(a: TByteA; Name: string): string;

function HexOfByteA(a: TByteA; Blocks: Integer = 1;
  const Splitter: string = ' '): string;
function BinOfByteA(a: TByteA; Blocks: Integer = 4;
  const Splitter: string = ' '): string;

function CardOfHex(Hex: string): Cardinal;
function IntOfBin(Bin: string): Cardinal;

function BinOfIntFill(n: cardinal; MinCount: Integer = 8): string;
function BinOfInt(n: cardinal): string;

function BaseNOfInt(I: Cardinal; B: TBaseN): string;
function IntOfBaseN(V: string; B: TBaseN): Cardinal;

{ Ranges }
function KeepIn(i, Bottom, Top: Variant): Variant;
function InRange(Value, Bottom, Top: Variant): Boolean;
function InStrictRange(Value, Bottom, Top: Variant): Boolean;
function Min(const A, B: Integer): Integer; overload;
function Min(const A: TIntA): Integer; overload;
function Max(const A, B: Integer): Integer; overload;
function Max(const A: TIntA): Integer; overload;

const
  RangesSeparator = ',';
  RangeInnerSeparator = '-';
  RangeInfinite = '*';
  RangeSpecialChars = [RangesSeparator, RangeInnerSeparator, RangeInfinite];

function RangesOfStr(const S: string): TRanges;
function InRanges(Ranges: TRanges; TestValue: Cardinal): Boolean;

function Success(Res: Integer; ResultOnSuccess: Integer = ERROR_SUCCESS): Boolean;
function Failure(Res: Integer; ResultOnSuccess: Integer = ERROR_SUCCESS): Boolean;

function ExpandString(const S: string): string;

{ Files }
procedure DeleteFiles(const Mask: string; ScanSubDirs: Boolean = True;
  Attributes: Integer = faFindEveryFile);
procedure FileNew(const FileName: string);
function DateTimeOfFileTime(const FileTime: TFileTime): TDateTime;

{ FileNames }
function GetFileNew(FileName: string; NoFloppyDrives: Boolean = True): string;

{ Finding Files }
function FindAll(Strings: TStrings; const Mask: string;
  ScanSubDirs: Boolean = True; Attributes: Integer = faFindEveryFile;
  FileReturn: TFileNameFunc = nil): Boolean;
function FindAllFirst(const Mask: string; ScanSubDirs: Boolean = True;
  Attributes: Integer = faFindEveryFile): string;

function FullOSInfo: string;
function Win32PlatformStr: string;
function Win9x: Boolean;
function WinNT: Boolean;
function Win2000: Boolean;
function WinXP: Boolean;

var
  MyDir: string = '';
  LastSuccessRes: Integer = 0;
  
{ Backward compatibility }
{$IFNDEF VER130}
function SameText(const S1, S2: string): Boolean;
{$ENDIF}

implementation
{$IFNDEF VER140}
uses FileCtrl;
{$ENDIF}

{$IFNDEF VER130}
function SameText(const S1, S2: string): Boolean;
begin
  Result := CompareText(S1, S2) = 0;
end;
{$ENDIF}

procedure Report(const ReportName, Text: string);
var
  F: TextFile;
  FileName: string;
begin
  FileName := MyDir + ReportName + '.rep';
  Assign(F, FileName);
  try
    if not FileExists(FileName) then
      Rewrite(F)
    else
      Append(F);
    WriteLn(F, Text);
  finally
    Close(F);
  end;
end;

procedure ClearReport(const ReportName: string);
var
  FileName: string;
begin
  FileName := MyDir + ReportName + '.rep';
  DeleteFile(FileName);
end;

procedure ReportFmt(const ReportName, Fmt: string; const Args: array of const);
begin
  Report(ReportName, Format(Fmt, Args));
end;

procedure GetParams(Strings: TStrings);
var
  P: PChar;
  Param: string;

  function GetParamStr(var P: PChar; var Param: string): Boolean;
  var
    Quoted: Boolean;
  begin
    Param := '';

    repeat
      while (P[0] <> #0) and (P[0] <= ' ') do
        Inc(P);

      Quoted := False;
      while P[0] <> #0 do begin
        if P[0] = '"' then begin
          Quoted := not Quoted;
          Inc(P);
        Continue; end;
        if (P[0] <= ' ') and not Quoted then
          Break;
        Param := Param + P[0];
        Inc(P);
      end;
    until (Param <> '') or (P[0] = #0);

    Result := Param <> '';
  end;

begin
  Strings.Clear;
  P := GetCommandLine;
  GetParamStr(P, Param);
  while GetParamStr(P, Param) do
    Strings.Add(Param);
end;

function GetParams(const Separator: string = ' '): string;
var
  SL: TStringList;
begin
  SL := TStringList.Create;
  GetParams(SL);
  Result := Join(SL, Separator);
  SL.Free;
end;

function Switch(const Switch: string; const PrefixChars: TCharSet = SwitchChars;
  IgnoreCase: Boolean = True): Boolean;
//= SysUtils.FindCmdLineSwitch
var
  i: Integer;
  s: string;
begin
  Result := True;

  for i := 1 to ParamCount do begin
    s := ParamStr(i);

    if (s <> '') and (s[1] in PrefixChars) then begin
    //i know that always s <> '', but this is saver
      s := Copy(s, 2, MaxInt);
      if (s = Switch) or (IgnoreCase and (0=AnsiCompareText(s, Switch))) then
        Exit;
    end;
  end;

  Result := False;
end;

function ParamNum(const S: string): Integer;
begin
  for Result := 1 to ParamCount do
    if 0=AnsiCompareText(ParamStr(Result), S) then
      Exit;

  Result := 0;
end;

function ParamPrefixNum(const Prefix: string): Integer;
var
  Len: Integer;
begin
  Len := Length(Prefix);
  for Result := 1 to ParamCount do
    if 0=AnsiCompareText(Copy(ParamStr(Result), 1, Len), Prefix) then
      Exit;

  Result := 0;
end;

function Param(const S: string): Boolean;
begin
  Result := ParamNum(S) > 0;
end;

function ParamPrefix(const Prefix: string): Boolean;
begin
  Result := ParamPrefixNum(Prefix) > 0;
end;

function GetParam(const Prefix: string = ''; const Default: string = ''): string;
var
  i: Integer;
begin
  Result := Default;

  if Prefix = '' then begin
    Result := ParamStr(1);
  Exit; end;

  i := ParamPrefixNum(Prefix);
  if i > 0 then
    Result := Copy(ParamStr(i), Length(Prefix) + 1, MaxInt);
end;

function GetMyDir(FullPath: Boolean = False): string;
var
  Buffer: array[0..260] of Char;
begin
  Result := '';
  SetString(Result, Buffer, GetModuleFileName(0, Buffer, SizeOf(Buffer)));
  if FullPath then
    Result := GetFileNew(Result);
  Result := ExtractPath(Result);
end;

function WinDir: string;
var
  Res: PChar;
begin
  Result := '\';
  GetMem(Res, MAX_PATH);
  GetWindowsDirectory(Res, MAX_PATH);
  Result := Res + '\';
  FreeMem(Res, MAX_PATH);
end;

function SysDir: string;
var
  Res: PChar;
begin
  Result := '\';
  GetMem(Res, MAX_PATH);
  GetSystemDirectory(Res, MAX_PATH);
  Result := Res + '\';
  FreeMem(Res, MAX_PATH);
end;

function UserName: string;
var
  Len: Cardinal;
  Res: PChar;
begin
  Result := '';
  GetMem(Res, MAX_PATH);
  Len := MAX_PATH;
  GetUserName(Res, Len);
  Result := Res;
  FreeMem(Res, MAX_PATH);
end;

function FirstChar(const S: string): Char;
begin
  if s = '' then
    Result := #0
  else
    Result := s[1];
end;

function LastChar(const S: string): Char;
begin
  if s = '' then
    Result := #0
  else
    Result := s[Length(s)];
end;

function CharPos(C: Char; const S: string; Offset: Integer = 1): Integer;
var
  MaxPosToSearch: Integer;
begin
  Result := Offset;
  MaxPosToSearch := Length(S);

  while Result <= MaxPosToSearch do begin
    if S[Result] = C then
      Exit;
    Inc(Result);
  end;

  Result := 0;
end;

function CharPos(C: TCharSet; const S: string; Offset: Integer = 1): Integer;
var
  MaxPosToSearch: Integer;
begin
  Result := Offset;
  MaxPosToSearch := Length(S);

  while Result <= MaxPosToSearch do begin
    if S[Result] in C then
      Exit;
    Inc(Result);
  end;

  Result := 0;
end;

function CharPosR(C: Char; const S: string; Offset: Integer = -1): Integer;
begin
  if Offset < 0 then
    Result := Length(S) + 1 - Offset
  else
    Result := Offset;
  if Result > Length(S) then
    Result := Length(S);

  while Result > 0 do begin
    if S[Result] = C then
      Exit;
    Dec(Result);
  end;
end;

function PosEx(const SubStr, S: string; Offset: Integer = 1): Integer;
var
  MaxPosToSearch, LenSubStr, i: Integer;
begin
  if SubStr = '' then begin
    Result := 0;
  Exit; end;

  if Offset < 1 then
    Result := 1
  else
    Result := Offset;

  LenSubStr := Length(SubStr);
  MaxPosToSearch := Length(S) - LenSubStr + 1;

  while Result <= MaxPosToSearch do begin
    if S[Result] = SubStr[1] then begin
      i := 1;

      while (i < LenSubStr)
       and (S[Result + i] = SubStr[i + 1]) do
        Inc(i);

      if i = LenSubStr then
        Exit;
    end;
    Inc(Result);
  end;

  Result := 0;
end;

function PosExText(const SubStr, S: string; Offset: Integer = 1): Integer;
var
  MaxPosToSearch, LenSubStr, i: Integer;

  function SameChar(a, b: Char): Boolean;
  begin
    Result := UpCase(a) = UpCase(b)
  end;

begin
  if SubStr = '' then begin
    Result := 0;
  Exit; end;

  if Offset < 1 then
    Result := 1
  else
    Result := Offset;

  LenSubStr := Length(SubStr);
  MaxPosToSearch := Length(S) - LenSubStr + 1;

  while Result <= MaxPosToSearch do begin
    if SameChar(S[Result], SubStr[1]) then begin
      i := 1;

      while (i < LenSubStr)
       and (SameChar(S[Result + i], SubStr[i + 1])) do
        Inc(i);

      if i = LenSubStr then
        Exit;
    end;
    Inc(Result);
  end;

  Result := 0;
end;

function PosExAnsiText(const SubStr, S: string; Offset: Integer = 1): Integer;
var
  MaxPosToSearch, LenSubStr, i: Integer;

  function SameChar(a, b: Char): Boolean;
  begin
    Result := CharLower(PChar(a)) = CharLower(PChar(b));
  end;

begin
  if SubStr = '' then begin
    Result := 0;
  Exit; end;

  if Offset < 1 then
    Result := 1
  else
    Result := Offset;

  LenSubStr := Length(SubStr);
  MaxPosToSearch := Length(S) - LenSubStr + 1;

  while Result <= MaxPosToSearch do begin
    if SameChar(S[Result], SubStr[1]) then begin
      i := 1;

      while (i < LenSubStr)
       and (SameChar(S[Result + i], SubStr[i + 1])) do
        Inc(i);

      if i = LenSubStr then
        Exit;
    end;
    Inc(Result);
  end;

  Result := 0;
end;

function UntilChar(const S: string; Brake: Char): string;
var
  p: Integer;
begin
  p := CharPos(Brake, S);

  if p > 0 then
    Result := Copy(S, 1, p - 1)
  else
    Result := S;
end;

function UntilChar(const S: string; Brake: TCharSet): string;
var
  p: Integer;
begin
  Result := '';
  p := CharPos(Brake, S);

  if p > 0 then
    Result := Copy(S, 1, p - 1)
  else
    Result := S;
end;

function UntilLastChar(const S: string; Brake: Char;
  IgnoreNoBrake: Boolean = True): string;
var
  p: Integer;
begin
  Result := '';
  p := CharPosR(Brake, S);

  if p > 0 then
    Result := Copy(S, 1, p - 1)
  else if IgnoreNoBrake then
    Result := S;
end;

function FromChar(const S: string; Brake: Char): string;
var
  p: Integer;
begin
  Result := '';
  p := CharPos(Brake, S);

  if p > 0 then
    Result := Copy(S, p + 1, Length(S) - p);
end;

function FromChar(const S: string; Brake: TCharSet): string;
var
  p: Integer;
begin
  Result := '';
  p := CharPos(Brake, S);

  if p > 0 then
    Result := Copy(S, p + 1, Length(S) - p);
end;

function FromLastChar(const S: string; Brake: Char;
  IgnoreNoBrake: Boolean = False): string;
var
  p: Integer;
begin
  Result := '';
  p := CharPosR(Brake, S);

  if p > 0 then
    Result := Copy(S, p + 1, Length(S) - p)
  else if IgnoreNoBrake then
    Result := S;
end;

function BetweenChars(const S: string; Start, Finish: Char;
  Inclusive: Boolean = False): string;
var
  p, fin: Integer;
begin
  Result := '';

  p := CharPos(Start, S);
  if p = 0 then
    Exit;

  fin := CharPos(Finish, S, p + 1);
  if fin = 0 then
    Exit;

  if not Inclusive then begin
    Inc(p);
    Dec(fin);
  end;

  Result := Copy(S, p, fin - p + 1);
end;

function UntilStr(const S: string; Brake: string): string;
var
  p: Integer;
begin
  if Length(Brake) = 1 then begin
    Result := UntilChar(S, Brake[1]);
  Exit; end;

  p := PosEx(Brake, S);

  if p > 0 then
    Result := Copy(S, 1, p - 1)
  else
    Result := S;
end;

function FromStr(const S: string; Brake: string): string;
var
  p: Integer;
begin
  if Length(Brake) = 1 then begin
    Result := FromChar(S, Brake[1]);
  Exit; end;

  Result := '';
  p := PosEx(Brake, s);

  if p > 0 then begin
    Inc(p, Length(Brake));
    Result := Copy(S, p, Length(S) - p + 1);
  end;
end;

function StringWrap(const S: string; Width: Integer; const LineEnd: string = EOL): string;
var
  i: Integer;
begin
  Result := '';
  if (S = '') or (Width < 1) then
    Exit;

  i := 1;
  while True do begin
    Result := Result + Copy(S, i, Width);
    Inc(i, Width);
    if i <= Length(S) then
      Result := Result + LineEnd
    else
      Exit;
  end;
end;

function Split(const S, Separator: string; IgnoreMultiSep: Boolean = True;
  MinCount: Integer = 0): TStrA;
var
  p, fin, SepLen: Integer;

  procedure Add(const S: string);
  begin
    if IgnoreMultiSep and (S = '') then
      Exit;
    SetLength(Result, Length(Result) + 1);
    Result[High(Result)] := S;
  end;

begin
  if S = '' then begin
    if Length(Result) < MinCount then
      SetLength(Result, MinCount);
  Exit; end;

  Result := nil;
  SepLen := Length(Separator);

  p := 1;
  fin := PosEx(Separator, S);
  while fin > 0 do begin
    Add(Copy(S, p, fin - p));
    p := fin + SepLen;
    fin := PosEx(Separator, S, p);
  end;
  Add(Copy(S, p, Length(S) - p + 1));

  if Length(Result) < MinCount then
    SetLength(Result, MinCount);
end;

procedure Split(const S, Separator: string; Strings: TStrings;
  IgnoreMultiSep: Boolean = True); 
var
  p, fin, SepLen: Integer;

  procedure Add(const S: string);
  begin
    if IgnoreMultiSep and (S = '') then
      Exit;
    Strings.Add(S);
  end;

begin
  if S = '' then
    Exit;

  Strings.BeginUpdate;
  SepLen := Length(Separator);
  p := 1;
  fin := PosEx(Separator, S);
  while fin > 0 do begin
    Add(Copy(S, p, fin - p));
    p := fin + SepLen;
    fin := PosEx(Separator, S, p);
  end;
  Add(Copy(S, p, Length(S) - p + 1));
  Strings.EndUpdate;
end;

function Split(const S: string; Separators: TCharSet;
  IgnoreMultiSep: Boolean = True; MinCount: Integer = 0): TStrA;
var
  p, fin: Integer;

  procedure Add(const S: string);
  begin
    if IgnoreMultiSep and (S = '') then
      Exit;
    SetLength(Result, Length(Result) + 1);
    Result[High(Result)] := S;
  end;

begin
  if S = '' then begin
    if Length(Result) < MinCount then
      SetLength(Result, MinCount);
  Exit; end;

  Result := nil;

  p := 1;
  fin := CharPos(Separators, S);
  while fin > 0 do begin
    Add(Copy(S, p, fin - p));
    p := fin + 1;
    fin := CharPos(Separators, S, p);
  end;
  Add(Copy(S, p, Length(S) - p + 1));

  if Length(Result) < MinCount then
    SetLength(Result, MinCount);
end;

procedure TileStr(const S: string; BrakeStart: Integer; BrakeEnd: Integer;
  out Left, Right: string);
begin
  Left := Copy(S, 1, BrakeStart-1);
  Right := Copy(S, BrakeEnd + 1, MaxInt);
end;

function Join(Strings: TStrings; Separator: string = ' '): string;
var
  i, imax: Integer;
begin
  Result := '';
  imax := Strings.Count-1;
  for i := 0 to imax do begin
    Result := Result + Strings[i];
    if i < imax then
      Result := Result + Separator;
  end;
end;

function Join(StrA: TStrA; Separator: string = ' '): string; overload;
var
  i: Integer;
begin
  Result := '';
  for i := 0 to High(StrA) do begin
    Result := Result + StrA[i];
    if i < High(StrA) then
      Result := Result + Separator;
  end;
end;

function MulStr(const S: string; Count: Integer): string;
var
  P: PChar;
  Len, i: Integer;
begin
  Result := '';
  if Count = 0 then
    Exit;

  Len := Length(S);
  SetLength(Result, Len * Count);

  P := Pointer(Result);
  for i := 1 to Count do begin
    Move(Pointer(S)^, P^, Len);
    Inc(P, Len);
  end;
end;

function AlignR(const S: string; Width: Integer; Filler: Char = ' '): string;
begin
  Result := MulStr(Filler, Width - Length(S)) + S;
end;

function MaxStr(const S: string; MaxLen: Integer): string;
var
  Len: Integer;
begin
  Len := Length(S);
  if Len <= MaxLen then begin
    Result := S;
  Exit end;

  Result := Copy(S, 1, MaxLen - 3) + '...';
end;

function TrimAll(const S: string): string;
var
  i: Integer;
begin
  for i := 1 to Length(S) do
    if S[i] > #32 then
      Result := Result + S[i];
end;

function ControlChar(C: Char): Boolean;
begin
  Result := C in StrangeChars;
end;

function FriendlyChar(C: Char): Char;
begin
  case C of
    #0: Result := '.';
    #1..#31: Result := '?';
    #255: Result := '#';
  else
    Result := C;
  end;
end;

function FriendlyStr(const S: string): string;
var
  i: Integer;
begin
  SetLength(Result, Length(S));
  for i := 1 to Length(S) do
    Result[i] := FriendlyChar(S[i]);
end;

function FriendlyStr(a: TByteA): string;
var
  i: Integer;
begin
  SetLength(Result, Length(a));
  for i := 0 to High(a) do
    Result[i + 1] := FriendlyChar(Char(a[i]));
end;

function Quote(const S: string; Quoter: Char = '"'): string;
begin
  Result := S;

  if FirstChar(S) <> Quoter then
    Result := Quoter + Result;

  if LastChar(S) <> Quoter then
    Result := Result + Quoter;
end;

function DeQuote(const S: string): string;
begin
  Result := '';
  if Length(S) > 2 then
    Result := Copy(S, 2, Length(S) - 2);
end;

function UnQuote(const S: string): string;
var
  Start, Len: Integer;
begin
  Start := 1;
  Len := Length(S);

  if (S <> '') and (S[1] in ([#0..#32] + QuoteChars)) then begin
    if (LastChar(S) = S[1]) then
      Dec(Len);
    Inc(Start);
  end;

  Result := Copy(S, Start, Len - Start + 1);
end;

function StrNumerus(const Value: Integer; const Singular, Plural: string;
  const Zero: string = '0'): string;
begin
  if Abs(Value) = 1 then
    Result := IntToStr(Value) + ' ' + Singular
  else if Value = 0 then
    Result := Zero + ' ' + Plural
  else
    Result := IntToStr(Value) + ' ' + Plural;
end;

function MakeStr(const Items: array of const; Separator: string = ''): string;
const
  BoolStrings: array[Boolean] of string = ('False', 'True');

var
  i: Integer;

  function StrOfP(P: Pointer): string;
  begin
    if P = nil then
      Result := '[nil]'
    else
      Result := '[' + IntToStr(Cardinal(P)) + ']';
  end;

  procedure Add(const S: string);
  begin
    Result := Result + s + Separator;
  end;

begin
  Result := '';
  for i := 0 to High(Items) do
    with Items[i] do
      case VType of
        vtString:     Add(VString^);
        vtInteger:    Add(IntToStr(VInteger));
        vtBoolean:    Add(BoolStrings[VBoolean]);
        vtChar:       Add(VChar);
        vtPChar:      Add(VPChar);
        vtExtended:   Add(FloatToStr(VExtended^));
        vtObject:     if VObject is TComponent then
                        Add(TComponent(VObject).Name)
                      else
                        Add(VObject.ClassName);
        vtClass:      Add(VClass.ClassName);
        vtAnsiString: Add(string(VAnsiString));
        vtCurrency:   Add(CurrToStr(VCurrency^));
        vtInt64:      Add(IntToStr(VInt64^));
        vtVariant:    Add(string(VVariant^));

        vtWideChar:   Add(VWideChar);
        vtPWideChar:  Add(VPWideChar);
        vtInterface:  Add(StrOfP(VInterface));
        vtPointer:    Add(StrOfP(VPointer));
        vtWideString: Add(WideString(VWideString));
      end;
  if Result <> '' then
    SetLength(result, Length(Result) - Length(Separator));
end;

procedure ShowText(const Items: array of const; Separator: string = '');
var
  Text: string;
begin
  Text := MakeStr(Items, Separator);

  MessageBox(0, PChar(Text), 'Info', MB_OK and MB_APPLMODAL);
end;

function DeleteChars(const S: string; C: Char): string;
var
  i: Integer;
begin
  Result := '';
  for i := 1 to Length(S) do
    if S[i] <> C then
      Result := Result + S[i];
end;

function DeleteChars(const S: string; C: TCharSet): string;
var
  i: Integer;
begin
  Result := '';
  for i := 1 to Length(S) do
    if not (S[i] in C) then
      Result := Result + S[i];
end;

function ExtractChars(const S: string; C: TCharSet): string;
var
  i: Integer;
begin
  Result := '';
  for i := 1 to Length(S) do
    if S[i] in C then
      Result := Result + S[i];
end;

function CharCount(const S: string; C: Char): Integer;
var
  i: Integer;
begin
  Result := 0;
  for i := 1 to Length(S) do
    if S[i] = C then
      Inc(Result);
end;

function StrAtPos(const S: string; Pos: Integer; const Str: string): Boolean;
begin
  Result := (Str <> '') and (Str = Copy(S, Pos, Length(Str)));
end;

function TextAtPos(const S: string; Pos: Integer; const Text: string): Boolean;
begin
  Result := (Text <> '') and SameText(Text, Copy(S, Pos, Length(Text)));
end;

function StrAtBegin(const S, Str: string): Boolean;
begin
  Result := StrAtPos(S, 1, Str);
end;

function TextAtBegin(const S, Text: string): Boolean;
begin
  Result := TextAtPos(S, 1, Text);
end;

function CharIn(const S: string; C: Char): Boolean;
var
  i: Integer;
begin
  Result := True;
  for i := 1 to Length(S) do
    if S[i] = C then Exit;
  Result := False;
end;

function CharIn(const S: string; C: TCharSet): Boolean;
var
  i: Integer;
begin
  Result := False;
  for i := 1 to Length(S) do begin
    Result := S[i] in C;
    if Result then
      Exit;
  end;
end;

function StrIn(const S, SubStr: string): Boolean;
begin
  Result := PosEx(SubStr, S) > 0;
end;

function StrIn(SL: TStrings; const S: string): Boolean;
var
  i: Integer;
begin
  Result := False;
  for i := 0 to SL.Count-1 do begin
    Result := (S = SL[i]);
    if Result then
      Exit;
  end;
end;

function StrIn(A: TStrA; const S: string): Boolean;
var
  i: Integer;
begin
  Result := False;
  for i := Low(A) to High(A) do begin
    Result := (S = A[i]);
    if Result then
      Exit;
  end;
end;

function TextIn(const S, Text: string): Boolean;
begin
  Result := PosExText(Text, S) > 0;
end;

function TextIn(SL: TStrings; const Text: string): Boolean;
var
  i: Integer;
begin
  Result := False;
  for i := 0 to SL.Count-1 do begin
    Result := SameText(Text, SL[i]);
    if Result then
      Exit;
  end;
end;

function TextIn(A: TStrA; const Text: string): Boolean;
var
  i: Integer;
begin
  Result := False;
  for i := Low(A) to High(A) do begin
    Result := SameText(Text, A[i]);
    if Result then
      Exit;
  end;
end;

function StrIndex(SL: TStrings; const S: string): Integer;
begin
  for Result := 0 to SL.Count-1 do
    if S = SL[Result] then
      Exit;
  Result := -1;
end;

function StrIndex(A: TStrA; const S: string): Integer;
begin
  for Result := Low(A) to High(A) do
    if S = A[Result] then
      Exit;
  Result := -1;
end;

function TextIndex(SL: TStrings; const Text: string): Integer;
begin
  for Result := 0 to SL.Count-1 do
    if SameText(Text, SL[Result]) then
      Exit;
  Result := -1;
end;

function TextIndex(A: TStrA; const Text: string): Integer;
begin
  for Result := Low(A) to High(A) do
    if SameText(Text, A[Result]) then
      Exit;
  Result := -1;
end;

function ReplaceChars(const S: string; Old, New: Char): string;
var
  i: Integer;
begin
  Result := S;
  for i := 1 to Length(Result) do
    if Result[i] = Old then
      Result[i] := New;
end;

function ReplaceChars(const S: string; Old: TCharSet; New: Char): string;
var
  i: Integer;
begin
  Result := S;
  for i := 1 to Length(Result) do
    if Result[i] in Old then
      Result[i] := New;
end;

function Replace(const S, Old, New: string): string;
var
  oldp, ps: Integer;
begin
  ps := 1;
  Result := '';
  while True do begin
    oldp := ps;
    ps := PosEx(Old, S, oldp);
    if ps = 0 then begin
      Result := Result + Copy(S, oldp, Length(S) - oldp + 1);
    Exit; end;
    Result := Result + Copy(S, oldp, ps - oldp) + New;
    Inc(ps, Length(Old));
  end;
end;

function SLOfFile(const FileName: string): TStringList;
begin
  Result := TStringList.Create;
  if FileExists(FileName) then
    Result.LoadFromFile(FileName);
end;

function ContainsEmptyLines(SL: TStrings): Boolean;
begin
  Result := StrIn(SL, '');
end;

procedure DeleteEmptyLines(SL: TStrings);
var
  i: Integer;
begin
  i := 0;
  while i < SL.Count do begin
    if SL[i] = '' then
      SL.Delete(i)
    else
      Inc(i);
  end;
end;

procedure DeleteCommentLines(SL: TStrings; const CommentSign: string = '//');
var
  i: Integer;
begin
  i := 0;
  while i < SL.Count do begin
    if (SL[i] = '') or (StrAtBegin(TrimLeft(SL[i]), CommentSign)) then
      SL.Delete(i)
    else
      Inc(i);
  end;
end;

function FindLine(SL: TStrings; const S: string): Integer;
begin
  for Result := 0 to SL.Count-1 do
    if TextAtBegin(SL[Result], S) then
      Exit;
  Result := -1;
end;

procedure QuickSortSL(SL: TStringList);

  procedure Sort(l, r: Integer);
  var
    i,j: Integer;
    z,x: string;
  begin
    i := l;
    j := r;
    x := SL[(j + i) div 2];
    repeat
      while SL[i] < x do Inc(i);
      while SL[j] > x do Dec(j);
      if i <= j then begin
        z := SL[i];
        SL[i] := SL[j];
        SL[j] := z;
        Inc(i); Dec(j);
      end;
    until i > j;
    if j > l then Sort(l, j);
    if i < r then Sort(i, r);
  end;

begin
  if SL.Count > 0 then
    Sort(0, SL.Count-1);
end;

function IncStrA(StrA: TStrA): Integer;
begin
  SetLength(StrA, Length(StrA) + 1);
  Result := High(StrA);
end;

function StrOfByteA(a: TByteA): string;
begin
  Result := string(Copy(a, 0, Length(a)));
end;

function ByteAOfStr(const S: string): TByteA;
begin
  Result := TByteA(Copy(S, 1, Length(s)));
end;

function ByteAOfInt(i: Integer): TByteA;
begin
  SetLength(Result, SizeOf(Integer));
  Move(i, Pointer(Result)^, SizeOf(Integer));
end;

function IntOfByteA(A: TByteA): Integer;
begin
  Result := 0;
  Move(Pointer(A)^, Result, Min(Length(A), SizeOf(Integer)));
end;

function ByteAOfHex(const Hex: string): TByteA;
var
  i: Integer;
  h: string;
begin
  h := ExtractChars(Hex, HexadecimalChars);
  SetLength(Result, Length(h) div 2);
  for i := 0 to High(Result) do
    Result[i] := ByteOfHex(Copy(h, (i shl 1) + 1, 2));
end;

function SizeOfFile(const FileName: string): Integer;
var
  F: file;
begin
  AssignFile(F, FileName);
  {$I-}Reset(F, 1);{$I+}
  if IOResult = 0 then begin
    Result := FileSize(F);
    CloseFile(F);
  end else
    Result := 0;
end;

function FileEx(const FileName: string; AllowFolders: Boolean = False): Boolean;
var
  FindData: TWin32FindData;
begin
  if FileName = '' then begin
    Result := False;
  Exit; end;

  Result := (AllowFolders and DirectoryExists(FileName)) or
    (FindFirstFile(PChar(FileName), FindData) <> INVALID_HANDLE_VALUE);
  Result := Result and not CharIn(FileName, WildCards);
  Result := Result and (AllowFolders
    or ((FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0));
end;

function LWPSolve(const Dir: string): string;
begin
  if (Dir <> '') and (Dir[Length(Dir)] = '\') then begin
    Result := Copy(Dir, 1, Length(Dir) - 1);
  end else
    Result := Dir;
end;

function LWPSlash(const Dir: string): string;
begin
  if (Dir <> '') and (Dir[Length(Dir)] = '\') then begin
    Result := Copy(Dir, 1, Length(Dir));
  end else
    Result := Dir + '\';
end;

function ExtractDrive(const FileName: string): string;
begin
  Result := '';
  if (Length(FileName) >= 2) and (FileName[2] = ':') then
    Result := UpperCase(FileName[1] + ':\');
end;

function ExtractPath(const FileName: string): string;
var
  p: Integer;
begin
  p := CharPosR('\', FileName);
  if P > 0 then
    Result := Copy(FileName, 1, p)
  else
    Result := FileName;
end;

function ExtractPrefix(const FileName: string): string;
begin
  Result := UntilLastChar(ExtractFileName(FileName), '.');
end;

function ExtractSuffix(const FileName: string): string;
begin
  Result := FromLastChar(ExtractFileName(FileName), '.');
end;

function SameByteA(const A, B: TByteA): Boolean;
begin
  Result := (A = B) or ((Length(A) = Length(B)) and CompareMem(A, B, Length(A)));
end;

function Reverse(A: TByteA): TByteA;
var
  i: Integer;
begin
  SetLength(Result, Length(A));

  for i := 0 to High(A) do
    Result[High(Result) - i] := A[i];
end;

function Endian(i: Integer): Integer;
type
  EndianArray = packed array[0..3] of Byte;
var
  a, b: EndianArray;
begin
  a := EndianArray(i);
  b[0] := a[3];
  b[1] := a[2];
  b[2] := a[1];
  b[3] := a[0];
  Result := Integer(b);
end;

function SaveByteA(Data: TByteA; const FileName: string;
  Overwrite: Boolean = True): Boolean;
var
  F: file;
begin
  if FileExists(FileName) and not Overwrite then begin
    Result := False;
  Exit end;

  AssignFile(F, FileName);
  {$I-}Rewrite(F, 1);{$I+}
  if IOResult = 0 then begin
    if Length(Data) > 0 then
      BlockWrite(F, Data[0], Length(Data));
    CloseFile(F);
    Result := True;
  end else
    Result := False;
end;

function LoadByteA(const FileName: string): TByteA;
var
  F: file;
begin
  AssignFile(F, FileName);
  {$I-}Reset(F, 1);{$I+}
  if IOResult = 0 then begin
    SetLength(Result, FileSize(F));
    if Length(Result) > 0 then
      BlockRead(F, Result[0], FileSize(F));
    CloseFile(F);
  end else
    SetLength(Result, 0);
end;

function IsValidFileName(const FileName: string): Boolean;
begin
  Result := (FileName <> '') and not CharIn(FileName, FileNameEnemies)
    and CharIn(Trim(FileName), AllChars - ['.']);
end;

function MakeValidFileName(FileName: string; const Default: string = 'File'): string;
begin
  if FileName = '' then
    FileName := Default;

  if CharIn(FileName, FileNameEnemies) then
    Result := ReplaceChars(FileName, FileNameEnemies, '_')
  else if not CharIn(Trim(FileName), AllChars - ['.']) then
    Result := Default
  else
    Result := FileName;
end;

function IsValidInteger(const S: string): Boolean;
{const
  LowInt = '2147483648';
  HighInt = '2147483647';
var
  len, RealLen, i, o: Integer;
  c: Char;
begin
  Result := False;
  if S = '' then
    Exit;

  len := Length(S);
  o := 1;
  
  if S[1] = '-' then begin
    if len = 1 then
      Exit;
    Inc(o);
    while (o <= len) and (S[o] = '0') do
      Inc(o);
    if o > len then
      Exit;
    if o < len then begin
      RealLen := len - o + 1;
      if RealLen > Length(LowInt) then
        Exit
      else if RealLen = Length(LowInt) then begin
        for i := 1 to Length(LowInt) do begin
          c := S[i + o - 1];
          if (c < '0') or (c > LowInt[i]) then
            Exit;
          if c in ['0'..Char((Byte(LowInt[i])-1))] then
            Break;
        end;
        Inc(o, i);
      end;
    end;
  end else begin
    while (o <= len) and (S[o] = '0') do
      Inc(o);
    if o <= len then begin
      RealLen := len - o + 1;
      if RealLen > Length(HighInt) then
        Exit
      else if RealLen = Length(HighInt) then begin
        for i := 1 to Length(HighInt) do begin
          c := S[i + o - 1];
          if (c < '0') or (c > HighInt[i]) then
            Exit;
          if c in ['0'..Char((Byte(HighInt[i])-1))] then
            Break;
        end;
        Inc(o, i);
      end;
    end;
  end;

  for i := o to len do
    if not (S[i] in ['0'..'9']) then
      Exit;

  Result := True;  }
var
  i: Int64;
begin
  i := StrToInt64Def(S, High(Int64));
  Result := (i >= Low(Integer)) and (i <= High(Integer));
end;

function IsValidCardinal(const S: string): Boolean;
{const
  HighCard = '4294967295';
var
  len, RealLen, i, o: Integer;
begin
  Result := False;
  if S = '' then
    Exit;

  len := Length(S);
  o := 1;
  
  while (o <= len) and (S[o] = '0') do
    Inc(o);
  if o <= len then begin
    RealLen := len - o + 1;
    if RealLen > Length(HighCard) then
      Exit
    else if RealLen = Length(HighCard) then begin
      for i := 1 to Length(HighCard) do begin
        if S[i + o - 1] > HighCard[i] then
          Exit;
        if S[i + o - 1] in ['0'..Char((Byte(HighCard[i])-1))] then
          Break;
      end;
      Inc(o, i);
    end;
  end;

  for i := o to len do
    if not (S[i] in ['0'..'9']) then
      Exit;

  Result := True;  }
var
  i: Int64;
begin
  i := StrToInt64Def(S, -1);
  Result := (i >= 0) and (i <= High(Cardinal));
end;

function StrOfBool(flag: Boolean; const TrueStr: string = 'True';
  const FalseStr: string = 'False'): string;
begin
  if Flag then
    Result := TrueStr
  else
    Result := FalseStr;
end;

function StrOfInt(i: Integer): string;
begin
{  if i = 0 then begin
    Result := '0';
  Exit end;

  while i > 0 do begin
    Result := Char(Byte('0') + (i mod 10)) + Result;
    i := i div 10;
  end;}
  Result := IntToStr(i);
end;

function CardOfStr(const S: string): Cardinal;
var
  Res: Int64;
begin
  Res := StrToInt64Def(S, -1);
  if Res > High(Cardinal) then
    Res := High(Cardinal)
  else if Res < 0 then
    Res := 0;
  Result := Cardinal(Res);
end;

function HexOrd(Hex: Char): THex;
begin
  case Hex of
    '0'..'9':
      Result := Byte(Hex) - 48;
    'A'..'F':
      Result := Byte(Hex) - 55;
    'a'..'f':
      Result := Byte(Hex) - 87;
    else
      Result := 0;
  end;
end;

function ByteOfHex(Hex: THexByteStr): Byte;
begin
  Result := (HexOrd(Hex[1]) shl 4) + HexOrd(Hex[2]);
end;

function DecOfHex(const Hex: string): string;
begin
  Result := IntToStr(CardOfHex(Hex));
end;

function HexOfByte(b: Byte): THexByteStr;
begin
  Result := HexChar[(b and $F0) shr 4]
          + HexChar[ b and $0F       ];
end;

{function HexOfCard2(c: Cardinal): string;
var
  Data: array[0..(1 shl 4) - 1] of Char;
  i: Integer;
begin
  for i := 0 to (1 shl 4) - 1 do
    if i < 10 then
      Data[i] := Char(Ord('0') + i)
    else
      Data[i] := Char(Ord('A') + i - 10);

  Result := Data[(c and (((1 shl (1 shl 2)) - 1) shl (7 shl 2))) shr (7 shl 2)]
          + Data[(c and (((1 shl (1 shl 2)) - 1) shl (6 shl 2))) shr (6 shl 2)]
          + Data[(c and (((1 shl (1 shl 2)) - 1) shl (5 shl 2))) shr (5 shl 2)]
          + Data[(c and (((1 shl (1 shl 2)) - 1) shl (4 shl 2))) shr (4 shl 2)]
          + Data[(c and (((1 shl (1 shl 2)) - 1) shl (3 shl 2))) shr (3 shl 2)]
          + Data[(c and (((1 shl (1 shl 2)) - 1) shl (2 shl 2))) shr (2 shl 2)]
          + Data[(c and (((1 shl (1 shl 2)) - 1) shl (1 shl 2))) shr (1 shl 2)]
          + Data[(c and (((1 shl (1 shl 2)) - 1) shl (0 shl 2))) shr (0 shl 2)];
end; }

function HexOfCard(i: Cardinal): string;
var
  a: Cardinal;
begin
  Result := '';
  while i > 0 do begin
    a := i and $F;
    Result := HexChar[a] + Result;
    i := i shr 4;
  end;
end;

function HexOfCard(i: Cardinal; Digits: Integer): string;
var
  a: Cardinal;
begin
  Result := '';
  while i > 0 do begin
    a := i and $F;
    Result := HexChar[a] + Result;
    i := i shr 4;
  end;
  Result := MulStr('0', Digits - Length(Result)) + Result;
end;

function PascalHexArray(a: TByteA; Name: string): string;
var
  i, len: Integer;
begin
  Result := 'const' + EOL +
    '  ' + Name + ': array[0..' + IntToStr(High(a)) + '] of Byte = (';

  len := Length(a);
  for i := 0 to len-1 do begin
    if (i mod 19) = 0 then
      Result := Result + EOL + '  ' + '  ';
    Result := Result + '$' + HexOfByte(a[i]);
    if i < len-1 then
      Result := Result + ',';
  end;
  Result := Result + EOL + '  );';
end;

function HexOfByteA(a: TByteA; Blocks: Integer = 1;
  const Splitter: string = ' '): string;
var
  i: Integer;
begin
  Result := '';

  if Blocks > 0 then
    for i := 0 to High(a) do begin
      Result := Result + HexOfByte(a[i]);
      if i < High(a) then
        if ((i+1) mod Blocks) = 0 then
          Result := Result + Splitter;
    end
  else
    for i := 0 to High(a) do
      Result := Result + HexOfByte(a[i]);
end;

function BinOfByteA(a: TByteA; Blocks: Integer = 4;
  const Splitter: string = ' '): string;
var
  i, max: Integer;
  Bit: Boolean;
begin
  Result := '';

  if Blocks > 0 then begin
    max := 8 * (High(a)) + 7;
    for i := 0 to max do begin
      Bit := 7-(i mod 8) in TBitSet(a[i div 8]);
      Result := Result + Char(Byte('0') + Byte(Bit));
      if i < max then
        if ((i+1) mod Blocks) = 0 then
          Result := Result + Splitter;
    end;
  end else
    for i := 0 to High(a) do
      Result := Result + Char(Byte('0') + a[i] shr (i and 8));
end;

function CardOfHex(Hex: string): Cardinal;
var
  i: Integer;
begin
  Result := 0;
  Hex := Copy(ExtractChars(Hex, HexadecimalChars), 1, 8);

  for i := 1 to Length(Hex) do
    if Hex[i] <> '0' then
      Inc(Result, HexOrd(Hex[i]) shl ((Length(Hex) - i) shl 2));
end;

function IntOfBin(Bin: string): Cardinal;
var
  i: Integer;
begin
  Result := 0;
  Bin := Copy(ExtractChars(Bin, BinaryChars), 1, 32);

  for i := Length(Bin) downto 1 do
    if Bin[i] = '1' then
      Inc(Result, 1 shl (Length(Bin) - i));
end;

function BinOfInt(n: Cardinal): string;
var
  a: Integer;
begin
  if n = 0 then begin
    Result := '0';
  exit; end;

  Result := '';
  while n > 0 do begin
    a := n and 1;
    Result := Char(a + Byte('0')) + Result;
    n := n shr 1;
  end;
end;

function BinOfIntFill(n: Cardinal; MinCount: Integer = 8): string;
var
  a: Integer;
begin
  if n = 0 then begin
    Result := MulStr('0', MinCount);
  Exit; end;

  Result := '';
  while n > 0 do begin
    a := n and 1;
    Result := Char(a + Byte('0')) + Result;
    n := n shr 1;
  end;
  Result := MulStr('0', MinCount - Length(Result)) + Result;
end;

function BaseNOfInt(I: Cardinal; B: TBaseN): string;
var
  a: Integer;
begin
  if (B < 2) or (i = 0) then begin
    Result := '0';
  Exit; end;

  Result := '';
  while i > 0 do begin
    a := i mod B;
    Result := BaseNChar[a] + Result;
    i := i div B;
  end;
end;

function IntOfBaseN(V: string; B: TBaseN): Cardinal;
var
  i: Integer;
  F: Cardinal;
  c: Byte;
begin
  Result := 0;
  V := TrimAll(V);
  F := 1;
  for i := Length(V) downto 1 do begin
    c := Byte(UpCase(V[i]));
    case Char(c) of
      '0'..'9': c := c - 48;
      'A'..'Z': c := c - 55;
    end;
    if c < B then
      Result := Result + Byte(c) * F;
    F := F * B;
  end;
end;

function KeepIn(i, Bottom, Top: Variant): Variant;
begin
  Result := i;
  if Result > Top then
    Result := Top
  else if Result < Bottom then
    Result := Bottom;
end;

function InRange(Value, Bottom, Top: Variant): Boolean;
begin
  Result := (Value >= Bottom) and (Value <= Top);
end;

function InStrictRange(Value, Bottom, Top: Variant): Boolean;
begin
  Result := (Value > Bottom) and (Value < Top);
end;

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

function Min(const A: TIntA): Integer;
var
  i: Integer;
begin
  Result := 0;
  if Length(A) = 0 then
    Exit;

  Result := A[0];
  for i := 1 to High(A) do
    if A[i] < Result then
      Result := A[i];
end;

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

function Max(const A: TIntA): Integer;
var
  i: Integer;
begin
  Result := 0;
  if Length(A) = 0 then
    Exit;

  Result := A[0];
  for i := 1 to High(A) do
    if A[i] > Result then
      Result := A[i];
end;

function RangesOfStr(const S: string): TRanges;
var
  SL: TStringList;
  r, b, t: string;
  i, p: Integer;

  function TryStrToCard(const S: string; out Value: Cardinal): Boolean;
  var
    E: Integer;
  begin
    Val(S, Value, E);
    Result := E = 0;
  end;

begin
  Result := nil;
  SL := TStringList.Create;
  try
    Split(S, RangesSeparator, SL);
    SetLength(Result, SL.Count);
    for i := 0 to SL.Count-1 do begin
      r := SL[i];
      with Result[i] do begin
        p := CharPos(RangeInnerSeparator, r);
        Simple := p = 0; // no '-' found
        if Simple then begin
          if r = RangeInfinite then begin // * --> *-*
            Simple := False;
            Bottom := Low(Bottom);
            Top := High(Top);
          end else if not TryStrToCard(r, Value) then
            Break;

        end else begin
          TileStr(r, p, p, b, t);

          if b = RangeInfinite then
            Bottom := Low(Bottom)
          else if not TryStrToCard(b, Bottom) then
            Break;

          if t = RangeInfinite then
            Top := High(Top)
          else if not TryStrToCard(t, Top) then
            Break;
          if Bottom > Top then begin
            p := Bottom; Bottom := Top; Top := p;
          end;
        end;
      end;
    end;

    if i <> SL.Count then
      Result := nil;

  finally
    SL.Free;
  end;
end;

function InRanges(Ranges: TRanges; TestValue: Cardinal): Boolean;
var
  i: Integer;
begin
  Result := True;

  for i := 0 to High(Ranges) do
    with Ranges[i] do
      if Simple then begin
        if TestValue = Value then
          Exit;
      end else begin
        if InRange(TestValue, Bottom, Top) then
          Exit;
      end;

  Result := False;
end;

procedure WriteSL(Strings: TStrings; const Prefix: string = '';
  const Suffix: string = '');
var
  i: Integer;
begin
  for i := 0 to Strings.Count-1 do
    WriteLn(Prefix + Strings[i] + Suffix);
end;

function Success(Res: Integer; ResultOnSuccess: Integer = ERROR_SUCCESS): Boolean;
begin
  Result := (Res = ResultOnSuccess);
  LastSuccessRes := Res;
end;

function Failure(Res: Integer; ResultOnSuccess: Integer = ERROR_SUCCESS): Boolean;
begin
  Result := not Success(Res, ResultOnSuccess);
end;

function ExpandString(const S: string): string;
var
  Len: Integer;
  P, Res: PChar;
begin
  Result := '';
  P := PChar(S);
  Len := ExpandEnvironmentStrings(P, nil, 0);
  if Len = 0 then
    Exit;

  GetMem(Res, Len);
  ExpandEnvironmentStrings(P, Res, Len);

  Result := Res;
  FreeMem(Res, Len);
end;

function FindAll(Strings: TStrings; const Mask: string;
  ScanSubDirs: Boolean = True; Attributes: Integer = faFindEveryFile;
  FileReturn: TFileNameFunc = nil): Boolean;
var
  Path, FileName: string;

  procedure ScanDir(const Path, FileName: string);
  var
    PSR: TSearchRec;
    Res: Integer;

    procedure Add(const S: string);
    begin
      if S <> '' then
        Strings.Add(S);
    end;

  begin
    Res := FindFirst(Path + FileName, Attributes, PSR);
    while Success(Res, 0) do begin
      if Assigned(FileReturn) then
        Add(FileReturn(Path + PSR.Name))
      else
        Add(Path + PSR.Name);
      Res := FindNext(PSR);
    end;
    FindClose(PSR);
    if not ScanSubDirs then
      Exit;

    Res := FindFirst(Path + '*', faDirectory, PSR);
    while Success(Res, 0) do begin
      if (PSR.Attr and faDirectory > 0)
       and (PSR.Name <> '.') and (PSR.Name <> '..') then
        ScanDir(Path + PSR.Name + '\', FileName);
      Res := FindNext(PSR);
    end;
    FindClose(PSR);
  end;

begin
  Strings.Clear;
  Path := ExtractPath(Mask);
  FileName := ExtractFileName(Mask);
  ScanDir(Path, FileName);
  Result := Strings.Count > 0;
end;

function FindAllFirst(const Mask: string; ScanSubDirs: Boolean = True;
  Attributes: Integer = faFindEveryFile): string;
var
  Path, FileName: string;

  function ScanDir(const Path, FileName: string): Boolean;
  var
    PSR: TSearchRec;
    Res: Integer;
  begin
    Result := False;
    if Success(FindFirst(Path + FileName, Attributes, PSR), 0) then begin
      FindAllFirst := Path + PSR.Name;
      Result := True;
      FindClose(PSR);
    Exit; end;
    if not ScanSubDirs then
      Exit;

    Res := FindFirst(Path + '*', faDirectory, PSR);
    while not Result and Success(Res, 0) do begin
      if (PSR.Attr and faDirectory > 0)
       and (PSR.Name <> '.') and (PSR.Name <> '..') then
        Result := ScanDir(Path + PSR.Name + '\', FileName);
      Res := FindNext(PSR);
    end;
    FindClose(PSR);
  end;
begin
  Result := '';
  Path := ExtractPath(Mask);
  FileName := ExtractFileName(Mask);
  ScanDir(Path, FileName);
end;

procedure DeleteFiles(const Mask: string; ScanSubDirs: Boolean = True;
  Attributes: Integer = faFindEveryFile);
var
  Path, FileName: string;

  procedure ScanDir(const Path, FileName: string);
  var
    PSR: TSearchRec;
    Res: Integer;

    procedure TryDeleteFile(const FileName: string);
    begin
      try
        DeleteFile(Path + PSR.Name);
      except
      end;
    end;

  begin
    Res := FindFirst(Path + FileName, Attributes, PSR);
    while Success(Res, 0) do begin
      TryDeleteFile(Path + PSR.Name);
      Res := FindNext(PSR);
    end;
    FindClose(PSR);
    if not ScanSubDirs then
      Exit;

    Res := FindFirst(Path + '*', faDirectory, PSR);
    while Success(Res, 0) do begin
      if (PSR.Attr and faDirectory > 0)
       and (PSR.Name <> '.') and (PSR.Name <> '..') then begin
        ScanDir(Path + PSR.Name + '\', FileName);
        TryDeleteFile(Path + PSR.Name);
      end;
      Res := FindNext(PSR);
    end;
    FindClose(PSR);
  end;
begin
  Path := ExtractPath(Mask);
  FileName := ExtractFileName(Mask);
  ScanDir(Path, FileName);
end;

function GetFileNew(FileName: string; NoFloppyDrives: Boolean = True): string;
var
  Drive: string;
  pf, pd, Len: Integer;
  PSR: TSearchRec;
begin
  Result := '';
  FileName := Trim(FileName);
  if Length(FileName) < 2 then
    Exit;

  Drive := ExtractDrive(FileName);
  if not DirectoryExists(Drive) then
    Exit;

  if NoFloppyDrives and (Drive[1] in ['A', 'B']) then
    Exit;

  Len := Length(FileName);
  Result := Drive;
  pf := Length(Drive) + 1;
  while pf <= Len do begin
    if FileName[pf] = '\' then begin
      Result := Result + '\';
      Inc(pf);
    Continue; end;

    pd := CharPos('\', FileName, pf);
    if pd = 0 then begin
      if 0=FindFirst(Result + Copy(FileName, pf, MaxInt), faFindEveryFile, PSR) then begin
        Result := Result + PSR.Name;
      Break; end else begin
        FindClose(PSR);
        if 0=FindFirst(Result + Copy(FileName, pf, MaxInt), faDirectory, PSR) then
          Result := Result + PSR.Name + '\'
        else
          Result := '';
        FindClose(PSR);
        if Result = '' then
          Break;
      end;
    end;

    if 0=FindFirst(Result + Copy(FileName, pf, pd - pf), faDirectory, PSR) then
      Result := Result + PSR.Name + '\'
    else
      Result := '';
    FindClose(PSR);
    if Result = '' then
      Break;

    pf := pd + 1;
  end;

  if (Result <> '') and not FileEx(Result, True) then
    Result := '';
end;

function DateTimeOfFileTime(const FileTime: TFileTime): TDateTime;
var
  LocalFileTime: TFileTime;
  Res: Integer;
begin
  Result := 0;

  FileTimeToLocalFileTime(FileTime, LocalFileTime);
  if not FileTimeToDosDateTime(LocalFileTime, LongRec(Res).Hi,
   LongRec(Res).Lo) then
    Res := -1;

  if (Res = -1) or (Res = 0) then
    Exit;
  try
    Result := FileDateToDateTime(Res);
  except
  end;
end;

procedure FileNew(const FileName: string);
var
  Handle: Integer;
begin
  Handle := FileCreate(FileName);
  FileClose(Handle);
end;

function Win32PlatformStr: string;
const
  PlatformStrings: array[VER_PLATFORM_WIN32s..VER_PLATFORM_WIN32_NT] of string =
    ('VER_PLATFORM_WIN32s', 'VER_PLATFORM_WIN32_WINDOWS', 'VER_PLATFORM_WIN32_NT');
begin
  Result := PlatformStrings[Win32Platform];
end;

function FullOSInfo: string;
begin
  Result := Format(
    'Platform: %s' + EOL +
    'Version: %d.%d Build %d' + EOL +
    'CSD: %s',
    [
      Win32PlatformStr,
      Win32MajorVersion, Win32MinorVersion, Win32BuildNumber,
      Win32CSDVersion
    ]
  );
end;

function Win9x: Boolean;
begin
  Result := Win32Platform = VER_PLATFORM_WIN32_WINDOWS;
end;

function WinNT: Boolean;
begin
  Result := Win32Platform = VER_PLATFORM_WIN32_NT;
end;

function Win2000: Boolean;
begin
  Result := (Win32Platform = VER_PLATFORM_WIN32_NT)
            and (Win32MajorVersion = 4);
end;

function WinXP: Boolean;
begin
  Result := Win32MajorVersion >= 5;
end;

initialization
  MyDir := GetMyDir;

end.

unit FifoStream;

interface

uses Classes, windows, Dialogs;

const
  DefaultChunksize = 32768; // 32kb per chunk as default.

type
  PMemChunk = ^TMemChunk;
  TMemChunk = record
    Filled: Longword;
    Read: Longword;
    Data: pointer;
  end;

  TFifo = class
  private
    FBuffers: TList;
    FChunksize: Longword;
    FCritSect: TRTLCriticalSection;
    FIsWinNT: boolean;
    FBytesInFifo: LongWord;
  protected
    function GetBytesInFifo: LongWord;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Write(Data: pointer; Size: LongWord);
    procedure Read(Buff: pointer; var ReqSize: LongWord);
    procedure PeekData(Buff: pointer; var ReqSize: LongWord);
  published
    property BytesInFifo: LongWord read FBytesInFifo;
  end;

implementation

constructor TFifo.Create;
begin
  inherited;
  FBuffers := TList.Create;
  // set default chunksize...
  FChunksize := DefaultChunksize;
  InitializeCriticalSection(FCritSect);
end;

destructor TFifo.Destroy;
var
  I: Integer;
begin
  EnterCriticalSection(FCritSect);
  for I := 0 to FBuffers.count - 1 do
  begin
    FreeMem(PMemChunk(Fbuffers[I]).Data);
    Dispose(PMemChunk(Fbuffers[I]));
  end;
  FBuffers.Clear;
  FBuffers.Free;
  LeaveCriticalSection(FCritSect);

  DeleteCriticalSection(FCritSect);
  inherited;
end;

function TFifo.GetBytesInFifo: LongWord;
begin
  Result := 0;
  if FBuffers.Count = 0 then
  begin
    exit;
  end
  else
  begin
    if FBuffers.Count > 1 then
      Inc(Result, (FBuffers.Count - 1) * FChunkSize);
    Inc(Result, PMemChunk(FBuffers[Fbuffers.Count - 1]).Filled);
    Dec(Result, PMemChunk(FBuffers[0]).Read);
  end;
end;

procedure TFifo.Write(Data: pointer; Size: LongWord);
var
  Privpointer: pointer;
  PrivSize: LongWord;
  Chunk: PMemChunk;
  PosInChunk: pointer;
begin
  if LongWord(Data) = 0 then
  begin
    // null pointer? somebody is trying to fool us, get out...
    Exit;
  end;
  EnterCriticalSection(FCritSect);
  PrivPointer := Data;
  PrivSize := 0;
  // are already buffers there?
  if FBuffers.count > 0 then
  begin
    // is the last one of them not completely filled?
    if PMemChunk(FBuffers[FBuffers.count - 1]).filled < FChunksize then
      // not completely filled, so fill up the buffer.
    begin
      Chunk := PMemChunk(FBuffers[FBuffers.count - 1]);
      // fetch chunkdata.
      PosInChunk := Chunk.Data;
      // move to current fill pos...
      Inc(LongWord(PosInChunk), Chunk.Filled);
      // can we fill the chunk completely?
      if Size > FChunksize - Chunk.Filled then
      begin
        // yes we can.
        Move(PrivPointer^, PosInChunk^, FChunksize - Chunk.Filled);
        Inc(PrivSize, FChunksize - Chunk.Filled);
        Inc(LongWord(PrivPointer), FChunksize - Chunk.Filled);
        Chunk.Filled := FChunkSize;
      end
      else
        // we have to less data for filling the chunk completely,
        // just put everything in.
      begin
        Move(PrivPointer^, PosInChunk^, Size);
        Inc(PrivSize, Size);
        Inc(Chunk.Filled, Size);
      end;
    end;
  end;
  // as long as we have remaining stuff put it into new chunks.
  while (PrivSize < Size) do
  begin
    new(Chunk);
    GetMem(Chunk.Data, FChunksize);
    Chunk.Read := 0;
    // can we fill an entire chunk with the remaining data?
    if Privsize + FChunksize < Size then
    begin
      // yes we can, so put the stuff in.
      Move(Privpointer^, Chunk.Data^, FChunksize);
      Inc(LongWord(PrivPointer), FChunksize);
      Inc(PrivSize, FChunksize);
      Chunk.Filled := FChunksize;
    end
    else // we have to less data to fill the entire chunk, just put the remaining stuff in.
    begin
      Move(Privpointer^, Chunk.Data^, Size - Privsize);
      Chunk.Filled := Size - Privsize;
      Inc(PrivSize, Size - Privsize);
    end;
    Fbuffers.Add(Chunk);
  end;
  if Size <> Privsize then
    Showmessage('miscalculation in TFifo.write');
  FBytesInFifo := GetBytesInFifo;
  LeaveCriticalSection(FCritSect);
end;

procedure TFifo.Read(Buff: pointer; var ReqSize: LongWord);
var
  PrivSize: Integer;
  Privpos: pointer;
  Chunk: PMemChunk;
  ChunkPos: pointer;
begin
  if LongWord(Buff) = 0 then
  begin
    // null pointer? somebody is trying to fool us, get out...
    Exit;
  end;
  EnterCriticalSection(FCritSect);
  PrivSize := 0;
  Privpos := Buff;
  while FBuffers.Count > 0 do
  begin
    Chunk := PMemChunk(FBuffers[0]);
    ChunkPos := Chunk.data;
    Inc(LongWord(ChunkPos), Chunk.Read);
    // does the remaining part of the chunk fit into the buffer?
    if PrivSize + (Chunk.Filled - Chunk.read) < ReqSize then
    begin // yep, it fits
      Move(ChunkPos^, Privpos^, Chunk.Filled - Chunk.read);
      Inc(PrivSize, Chunk.Filled - Chunk.read);
      FreeMem(Chunk.Data);
      Dispose(Chunk);
      FBuffers.Delete(0);
    end
    else // remaining part didn't fit, get as much as we can and increment the
      // read attribute.
    begin
      Move(ChunkPos^, Privpos^, ReqSize - PrivSize);
      Inc(Chunk.read, ReqSize - PrivSize);
      Inc(PrivSize, ReqSize - PrivSize);
      // as we filled the buffer, we'll have to break here.
      break;
    end;
  end;
  FBytesInFifo := GetBytesInFifo;
  LeaveCriticalSection(FCritSect);
  ReqSize := PrivSize;
end;

// read Data from Stream without removing it from the Stream...

procedure TFifo.PeekData(Buff: pointer; var ReqSize: LongWord);
var
  PrivSize: Integer;
  Privpos: pointer;
  Chunk: PMemChunk;
  ChunkPos: pointer;
  ChunkNr: Integer;
begin
  if LongWord(Buff) = 0 then
  begin
    // null pointer? somebody is trying to fool us, get out...
    Exit;
  end;
  EnterCriticalSection(FCritSect);
  PrivSize := 0;
  Privpos := Buff;
  ChunkNr := 0;
  while FBuffers.Count > ChunkNr do
  begin
    Chunk := PMemChunk(FBuffers[ChunkNr]);
    ChunkPos := Chunk.data;
    Inc(LongWord(ChunkPos), Chunk.Read);
    // does the remaining part of the chunk fit into the buffer?
    if PrivSize + (Chunk.Filled - Chunk.read) < ReqSize then
    begin // yep, it fits
      Move(ChunkPos^, Privpos^, Chunk.Filled - Chunk.read);
      Inc(PrivSize, Chunk.Filled - Chunk.read);
      Inc(ChunkNr);
    end
    else // remaining part didn't fit, get as much as we can and increment the
      // read attribute.
    begin
      Move(ChunkPos^, Privpos^, ReqSize - PrivSize);
      Inc(PrivSize, ReqSize - PrivSize);
      // as we filled the buffer, we'll have to break here.
      break;
    end;
  end;
  LeaveCriticalSection(FCritSect);
  ReqSize := PrivSize;
end;

end.