unit uCommon;

interface

uses Windows, Messages, Classes, StdCtrls, ExtCtrls, ComCtrls, Dialogs, Graphics,
     SysUtils, ShlObj, Forms, Controls, IniFiles, uMessageBox, uFilesUtil,
     MessageDigests, MessageAuthenticationCodes, uInputBox;

const
  MaxArcadeSystems = 6;
  aMediaType: packed array[-1..5] of String =
    ('',
     'ROM',
     'MAME CHD',
     'LD (LaserDisc)',
     'CD',
     'DVD',
     'Disc Image File');
  aAudioChannels: packed array[0..8] of String =
    ('',
     'Mono',
     'Stereo',
     'Stereo (2.1)',
     'Quadraphonic',
     'Surround (4.1)',
     'Dolby Digital 5.1',
     'DTS 6.1',
     'THX 7.1');
  aAudioSamples: packed array[-1..1] of String = ('', 'Missing', 'Present');
  aScreenType: packed array[-1..1] of String = ('', 'Raster', 'Vector');
  aOrientation: packed array[-1..1] of String = ('', 'Horizontal', 'Vertical');
  aStatus: packed array[-1..2] of String = ('', 'Good', 'Imperfect', 'Preliminary');
  aMerged: String = 'Yes';
  aSaveState: packed array[-1..1] of String = ('', 'Unsupported', 'Supported');
  {aControlType: packed array[0..20] of String =
    ('',
     'Joystick 2-Way', // 01 joy2way
     'Joystick 4-Way', // 02 joy4way
     'Joystick 8-Way', // 03 joy8way
     'Vertical Joystick 2-Way', // 04 vjoy2way
     'Double Joystick 2-Way', // 05 doublejoy2way
     'Double Joystick 4-Way', // 06 doublejoy4way
     'Double Joystick 8-Way', // 07 doublejoy8way
     'Vertical Double Joystick 2-Way', // 08 vdoublejoy2way
     'Paddle', // 09 paddle
     'Dial', // 10 dial
     'Trackball', // 11 trackball
     'Stick', // 12 stick
     'Light Gun', // 13 lightgun
     'Pedal', // 14 pedal
     'Buttons', // 15 buttons
     // console entries
     'Gamepad 4-Way', // 16 gamepad4way
     'Gamepad 8-Way', // 17 gamepad8way
     'Analog Stick', // 18 analogstick
     'Dual Analog Sticks', // 19 dualanalogsticks
     'Analog Buttons'); // 20 analogbuttons}

  aControlType: array[-1..20] of array [0..1] of String =
    (('Unknown Control Type', 'unknown'), // -01
     ('', ''),
     ('Joystick 2-Way', 'joy2way'),// 01
     ('Joystick 4-Way', 'joy4way'), // 02
     ('Joystick 8-Way', 'joy8way'), // 03
     ('Vertical Joystick 2-Way', 'vjoy2way'), // 04
     ('Double Joystick 2-Way', 'doublejoy2way'), // 05
     ('Double Joystick 4-Way', 'doublejoy4way'), // 06
     ('Double Joystick 8-Way', 'doublejoy8way'), // 07
     ('Vertical Double Joystick 2-Way', 'vdoublejoy2way'), // 08
     ('Paddle', 'paddle'), // 09
     ('Dial', 'dial'), // 10
     ('Trackball', 'trackball'), // 11
     ('Stick', 'stick'), // 12
     ('Light Gun', 'lightgun'), // 13
     ('Pedal', 'pedal'), // 14
     ('Buttons', 'buttons'), // 15
      // console entries
     ('Gamepad 4-Way', 'gamepad4way'), // 16
     ('Gamepad 8-Way', 'gamepad8way'), // 17
     ('Analog Stick', 'analogstick'), // 18
     ('Dual Analog Sticks', 'dualanalogsticks'), // 19
     ('Analog Buttons', 'analogbuttons')); // 20

  Daphne_VersionInfo: String = 'Daphne v????';
  SystemStr: String[1] = '"';
  ToolBarIndex: array[0..9] of String =
    ('View Mode',
     'Arcade Filter',
     'Main Filter',
     'Master/Clone Filter',
     'Have/Miss Filter',
     'Working/Non-Working Filter',
     'Show/Hide Bios',
     'Number of Screens',
     'Images View Mode',
     'Random Game');

  ImageTypeArray: array[0..11] of array[0..1] of String = (
     ('Title Snapshots', 'image_01_titlesnap'),
     ('Game Snapshots', 'image_02_gamesnap'),
     ('Marquees', 'image_03_marquee'),
     ('Flyers', 'image_04_flyer'),
     ('Cabinets', 'image_05_cabinet'),
     ('Control Panels', 'image_06_cpanel'),
     ('Control Panel Layouts', 'image_07_cpanellayout'),
     ('Select', 'image_08_select'),
     ('Versus', 'image_09_versus'),
     ('Wallpapers', 'image_10_wallpaper'),
     ('Hardware', 'image_11_hardware'),
     ('Custom', 'image_12_custom'));

  aColumns: packed array[0..39] of String =
     ('Description',
      'Year',
      'Manufacturer',
      'AudioChannels',
      'AudioSamples',
      'NumScreens',

      'ScreenType1',
      'Orientation1',
      'Resolution1',
      'RefreshRate1',

      'ScreenType2',
      'Orientation2',
      'Resolution2',
      'RefreshRate2',

      'ScreenType3',
      'Orientation3',
      'Resolution3',
      'RefreshRate3',

      'ScreenType4',
      'Orientation4',
      'Resolution4',
      'RefreshRate4',

      'DriverStatus',
      'EmulationStatus',
      'ColorStatus',
      'SoundStatus',
      'GraphicStatus',
      'CocktailStatus',
      'ProtectionStatus',

      'Category',
      'VersionAdded',
      'Name',
      'Clone',
      'DriverName',
      'Merged',
      'PlayTime',
      'Played',
      'NumPlayers',
      'ControlType',
      'SaveState');

  ChecksumMode: array [0..4] of TMessageDigestClass = (
    TMD2, TMD4, TMD5, TSHA1, TRIPEMD160);

  // The constants here are for the CRC-32 generator
  // polynomial, as defined in the Microsoft
  // Systems Journal, March 1995, pp. 107-108

  Table: array[0..255] of DWORD =
   ($00000000, $77073096, $EE0E612C, $990951BA,
    $076DC419, $706AF48F, $E963A535, $9E6495A3,
    $0EDB8832, $79DCB8A4, $E0D5E91E, $97D2D988,
    $09B64C2B, $7EB17CBD, $E7B82D07, $90BF1D91,
    $1DB71064, $6AB020F2, $F3B97148, $84BE41DE,
    $1ADAD47D, $6DDDE4EB, $F4D4B551, $83D385C7,
    $136C9856, $646BA8C0, $FD62F97A, $8A65C9EC,
    $14015C4F, $63066CD9, $FA0F3D63, $8D080DF5,
    $3B6E20C8, $4C69105E, $D56041E4, $A2677172,
    $3C03E4D1, $4B04D447, $D20D85FD, $A50AB56B,
    $35B5A8FA, $42B2986C, $DBBBC9D6, $ACBCF940,
    $32D86CE3, $45DF5C75, $DCD60DCF, $ABD13D59,
    $26D930AC, $51DE003A, $C8D75180, $BFD06116,
    $21B4F4B5, $56B3C423, $CFBA9599, $B8BDA50F,
    $2802B89E, $5F058808, $C60CD9B2, $B10BE924,
    $2F6F7C87, $58684C11, $C1611DAB, $B6662D3D,

    $76DC4190, $01DB7106, $98D220BC, $EFD5102A,
    $71B18589, $06B6B51F, $9FBFE4A5, $E8B8D433,
    $7807C9A2, $0F00F934, $9609A88E, $E10E9818,
    $7F6A0DBB, $086D3D2D, $91646C97, $E6635C01,
    $6B6B51F4, $1C6C6162, $856530D8, $F262004E,
    $6C0695ED, $1B01A57B, $8208F4C1, $F50FC457,
    $65B0D9C6, $12B7E950, $8BBEB8EA, $FCB9887C,
    $62DD1DDF, $15DA2D49, $8CD37CF3, $FBD44C65,
    $4DB26158, $3AB551CE, $A3BC0074, $D4BB30E2,
    $4ADFA541, $3DD895D7, $A4D1C46D, $D3D6F4FB,
    $4369E96A, $346ED9FC, $AD678846, $DA60B8D0,
    $44042D73, $33031DE5, $AA0A4C5F, $DD0D7CC9,
    $5005713C, $270241AA, $BE0B1010, $C90C2086,
    $5768B525, $206F85B3, $B966D409, $CE61E49F, 
    $5EDEF90E, $29D9C998, $B0D09822, $C7D7A8B4,
    $59B33D17, $2EB40D81, $B7BD5C3B, $C0BA6CAD, 

    $EDB88320, $9ABFB3B6, $03B6E20C, $74B1D29A,
    $EAD54739, $9DD277AF, $04DB2615, $73DC1683,
    $E3630B12, $94643B84, $0D6D6A3E, $7A6A5AA8,
    $E40ECF0B, $9309FF9D, $0A00AE27, $7D079EB1, 
    $F00F9344, $8708A3D2, $1E01F268, $6906C2FE,
    $F762575D, $806567CB, $196C3671, $6E6B06E7,
    $FED41B76, $89D32BE0, $10DA7A5A, $67DD4ACC,
    $F9B9DF6F, $8EBEEFF9, $17B7BE43, $60B08ED5,
    $D6D6A3E8, $A1D1937E, $38D8C2C4, $4FDFF252,
    $D1BB67F1, $A6BC5767, $3FB506DD, $48B2364B,
    $D80D2BDA, $AF0A1B4C, $36034AF6, $41047A60,
    $DF60EFC3, $A867DF55, $316E8EEF, $4669BE79,
    $CB61B38C, $BC66831A, $256FD2A0, $5268E236,
    $CC0C7795, $BB0B4703, $220216B9, $5505262F,
    $C5BA3BBE, $B2BD0B28, $2BB45A92, $5CB36A04,
    $C2D7FFA7, $B5D0CF31, $2CD99E8B, $5BDEAE1D,

    $9B64C2B0, $EC63F226, $756AA39C, $026D930A,
    $9C0906A9, $EB0E363F, $72076785, $05005713,
    $95BF4A82, $E2B87A14, $7BB12BAE, $0CB61B38,
    $92D28E9B, $E5D5BE0D, $7CDCEFB7, $0BDBDF21,
    $86D3D2D4, $F1D4E242, $68DDB3F8, $1FDA836E,
    $81BE16CD, $F6B9265B, $6FB077E1, $18B74777,
    $88085AE6, $FF0F6A70, $66063BCA, $11010B5C,
    $8F659EFF, $F862AE69, $616BFFD3, $166CCF45,
    $A00AE278, $D70DD2EE, $4E048354, $3903B3C2,
    $A7672661, $D06016F7, $4969474D, $3E6E77DB,
    $AED16A4A, $D9D65ADC, $40DF0B66, $37D83BF0,
    $A9BCAE53, $DEBB9EC5, $47B2CF7F, $30B5FFE9,
    $BDBDF21C, $CABAC28A, $53B39330, $24B4A3A6,
    $BAD03605, $CDD70693, $54DE5729, $23D967BF,
    $B3667A2E, $C4614AB8, $5D681B02, $2A6F2B94,
    $B40BBE37, $C30C8EA1, $5A05DF1B, $2D02EF8D);

function  ExtMatch(FileExtension, Extensions: String; Delimiter: Char): Boolean;
function  GenerateZipErrorsMessage(TitleMessage: String; ZipFilesList: TStrings): Integer;
function  GenerateMessage(TitleMessage, DescriptionMessage: String; MessageType: Integer; DefaultButtonNo: Boolean = False): Integer;
procedure CallMessageBox;
procedure FreeMessageBox;
function  GetSystemFileName(SystemID: Integer): String;
function  GetSystemROMFileName(SystemID: Integer): String;
function  GetMachineTypeGamesFolder(IsArcade: Boolean = True): String;
function  GetGamesList(HolderStrings: THashedStringList; SystemID: Integer): Boolean;
procedure GenerateControllerDefinitionsFilesList(Folder: String; ListHolder: THashedStringList; ClearList: Boolean);
procedure GetFilesList(Folder, FileType, FileMask: String; ListHolder: THashedStringList; SubDirectories, ClearList, ReturnFullPath: Boolean);
procedure GetFoldersList(Folder: String; ListHolder: THashedStringList; ClearList, SubDirectories: Boolean);

function  EncryptData(DataString: String): String;
function  DecryptData(DataString: String): String;

function  GetPlayTime(Milliseconds: Int64; GamesList, FMOD: Boolean): String;
function  ExtractMAMEIniValue(MAMEOption: String): String;

function  GetGameHistory(const GameName: String; const StringLine: String): Boolean;
procedure GetGamesFilesList(Folder, FileType: String; ListHolder: THashedStringList; SubDirectories: Boolean);

// SHA-1 routines
function  ComputeHashValue(Mode: Integer; const FileName: String): String;
function  GetSHA1_Value(strFileName: String): String;
function  GetMD5_Value(strFileName: String): String;

procedure CalcCRC32(p: Pointer; ByteCount: DWORD; var CRCValue: DWORD);
function  CalcStringCRC32(s: string; out CRC32: DWORD): Boolean;
function  CalcFileCRC32(FromName: String; var CRC32Value: DWORD): Boolean;

//function  CalcSNESCRC32(FileStrm: TMemoryStream; var CRC32Value: DWORD): Boolean;

function  InputBoxEL(const CaptionMsg, PromptMsg, DefaultValue: String): String;
function  InputBoxELQuery(const CaptionMsg, PromptMsg, DefaultValue: String; out ValueOutput: String): Boolean;

function  GetButtonDescIndex(const Description: String): ShortInt;
function  CheckAndCreateFolder(const FolderString: String): Boolean;
function  GetCRC32(emuFile: String): String;
function  CompareIntValue(const A, B: Integer): ShortInt;
procedure SetComboBoxEx(Holder: TComboBoxEx; ItemNumber: Integer; ResetSelection: Boolean = False);
procedure SetSelectedComboBox(sIndex: ShortInt; ComboBoxHolder: TComboBox);

function  XML_CheckData(strLine, FieldEntry: String): Boolean;
function  XML_GetEntryName(strLine: String): String;
function  XML_GetEntryValue(strLine, EntryName: String): String;

function  IsWindowsVista: Boolean;
function  GetWindowsVersion: String;

implementation

function ExtMatch(FileExtension, Extensions: String; Delimiter: Char): Boolean;
var
  Position: Longint;
begin
  Result:= False;
  // Check strings.
  if (Length(FileExtension) = 0) or (Length(Extensions) = 0) then
     Exit;

  // Rewrite strings.
  FileExtension:= Delimiter + UpperCase(FileExtension) + Delimiter;
  Extensions:= Delimiter + UpperCase(Extensions) + Delimiter;

  // Search.
  Position:= Pos(FileExtension, Extensions);

  // Found ?
  Result:= Position <> 0;
end;

function GenerateZipErrorsMessage(TitleMessage: String; ZipFilesList: TStrings): Integer;
begin
  CallMessageBox;
  FormMessageBox.Caption:= TitleMessage;
  FormMessageBox.LabelMessage.Clear;
  FormMessageBox.LabelMessage.Lines.AddStrings(ZipFilesList);

  FormMessageBox.ButtonYes.Caption:= '&Ok';
  FormMessageBox.ButtonNo.Visible:= False;

  FormMessageBox.ShowModal;
  Result:= FormMessageBox.MsgResult;
  FreeMessageBox;
end;

function GenerateMessage(TitleMessage, DescriptionMessage: String; MessageType: Integer; DefaultButtonNo: Boolean = False): Integer;
begin
  CallMessageBox;
  FormMessageBox.Caption:= TitleMessage;
  FormMessageBox.LabelMessage.Clear;
  FormMessageBox.LabelMessage.Lines.Add(DescriptionMessage);
  if MessageType = 2 then
     begin
       FormMessageBox.ButtonYes.Caption:= '&Ok';
       FormMessageBox.ButtonNo.Visible:= False;
     end;
  case DefaultButtonNo of
    True:
      begin
        with FormMessageBox do
        begin
          ButtonYes.Default:= False;
          ButtonNo.Default:= True;
          ActiveControl:= ButtonNo;
        end;
      end;
    False: FormMessageBox.ActiveControl:= FormMessageBox.ButtonYes;
  end;

  FormMessageBox.ShowModal;
  Result:= FormMessageBox.MsgResult;
  FreeMessageBox;
end;

procedure CallMessageBox;
begin
  if not Assigned(FormMessageBox) then
     FormMessageBox:= TFormMessageBox.Create(nil);
end;

procedure FreeMessageBox;
begin
  FreeAndNil(FormMessageBox);
end;

function GetSystemFileName(SystemID: Integer): String;
begin
  case SystemID of
    1: Result:= 'mame.el';
    2: Result:= 'zinc.el';
    3: Result:= 'daphne.el';
    4: Result:= 'agemame.el';
    5: Result:= 'pinmame.el';
    6: Result:= 'hazemd.el';
    500: Result:= 'customgames.el';
  end;
end;

function GetSystemROMFileName(SystemID: Integer): String;
begin
  case SystemID of
    1: Result:= 'mame_roms.el';
    4: Result:= 'agemame_roms.el';
    5: Result:= 'pinmame_roms.el';
    6: Result:= 'hazemd_roms.el';
  end;
end;

function GetMachineTypeGamesFolder(IsArcade: Boolean = True): String;
begin
  case IsArcade of
    True : Result:= 'arcade\';
    False: Result:= 'console\';
  end;
end;

function GetGamesList(HolderStrings: THashedStringList; SystemID: Integer): Boolean;
var
  TempFile: THashedStringList;
  FilePath, FileName: String;
  Loop: Integer;
  tmpString: String;
begin
  FilePath:= ExtractFilePath(Application.ExeName);
  FileName:= GetSystemFileName(SystemID);
  HolderStrings.BeginUpdate;
  Result:= FileExists(FilePath+GetMachineTypeGamesFolder+FileName);
  if Result then
     begin
       TempFile:= THashedStringList.Create;
       TempFile.LoadFromFile(FilePath+GetMachineTypeGamesFolder+FileName);
       if TempFile.Count > 0 then
          begin
            TempFile.BeginUpdate;
            if TempFile[0] = '[info_file]' then
               begin
                 TempFile.Delete(3);
                 TempFile.Delete(2);
                 TempFile.Delete(1);
                 TempFile.Delete(0);
               end;
            for Loop:=TempFile.Count-1 downto 0 do
            begin
              tmpString:= TempFile[Loop];
              if (tmpString = '') or (tmpString[1] = '[') then
                 TempFile.Delete(Loop);
            end;
            TempFile.EndUpdate;
            if TempFile.Count > 0 then
               HolderStrings.AddStrings(TempFile);
          end;
       FreeAndNil(TempFile);
     end;
  HolderStrings.EndUpdate;
end;

procedure GenerateControllerDefinitionsFilesList(Folder: String; ListHolder: THashedStringList; ClearList: Boolean);
var
  Search: TSearchRec;
  LengthDir: Longint;
  Counter, Loop, EndIndex, IndexLastFolder: Integer;
  ControllerDescription: String;
  Continue: Boolean;
begin
  LengthDir:= Length(Folder);
  if LengthDir = 0 then
     Exit;

  ListHolder.BeginUpdate;
  if ClearList then
     ListHolder.Clear;

  // Rewrite path
  if Folder[LengthDir] <> ':' then
     Folder:= IncludeTrailingPathDelimiter(Folder);

  // Add Files
  Counter:= FindFirst(Folder+'*.*', $37, Search);
  while Counter = 0 do
  begin
    // It's a directory?
    if (Search.Attr and $10 = $10) and (Search.Name <> '.') and
       (Search.Name <> '..') then
       GenerateControllerDefinitionsFilesList(Folder+Search.Name, ListHolder, False)
    else
       begin
         if LowerCase(ExtractFileExt(Search.Name)) = '.zip' then
            begin
              Continue:= False;
              ControllerDescription:= Search.Name;
              ControllerDescription:= ChangeFileExt(ControllerDescription, '');
              if ListHolder.IndexOf(ControllerDescription) = -1 then
                 ListHolder.Add(ControllerDescription);
            end
         else
         if LowerCase(ExtractFileExt(Search.Name)) = '.ini' then
            Continue:= (LowerCase(Search.Name) = 'default.ini')
         else
         if LowerCase(ExtractFileExt(Search.Name)) = '.cfg' then
            Continue:= True
         else
            Continue:= False;

         if Continue then
            begin
              if LowerCase(ExtractFileExt(Search.Name)) = '.ini' then
                 begin
                   EndIndex:= -1;
                   IndexLastFolder:= -1;
                   Folder:= ExcludeTrailingPathDelimiter(Folder);
                   ControllerDescription:= '';
                   for Loop:=Length(Folder) downto 0 do
                   begin
                     if Folder[Loop] = '\' then
                        begin
                          if EndIndex = -1 then
                             begin
                               EndIndex:= Loop;
                               if IndexLastFolder = -1 then
                                  IndexLastFolder:= Loop;
                             end
                          else
                             begin
                               ControllerDescription:= Copy(Folder, IndexLastFolder+1, Length(Folder));
                               Break;
                             end;
                        end;
                   end;
                 end
              else
              if LowerCase(ExtractFileExt(Search.Name)) = '.cfg' then
                 ControllerDescription:= ChangeFileExt(Search.Name, '');
              if ListHolder.IndexOf(ControllerDescription) = -1 then
                 ListHolder.Add(ControllerDescription);
            end;
       end;
    Counter:= FindNext(Search);
  end;
  FindClose(Search);
  ListHolder.Sort;
  ListHolder.EndUpdate;
end;

procedure GetFilesList(Folder, FileType, FileMask: String; ListHolder: THashedStringList; SubDirectories, ClearList, ReturnFullPath: Boolean);
var
  Search: TSearchRec;
  LengthDir: Longint;
  Counter: Integer;
begin
  LengthDir:= Length(Folder);
  if LengthDir = 0 then
     Exit;

  ListHolder.BeginUpdate;
  if ClearList then
     ListHolder.Clear;

  // Rewrite path
  if Folder[LengthDir] <> ':' then
     Folder:= IncludeTrailingPathDelimiter(Folder);

  // Add Files
  Counter:= FindFirst(Folder + FileMask, $37, Search);
  while Counter = 0 do
  begin
    // It's a directory?
    if (Search.Attr and $10 = $10) and (Search.Name <> '.') and
       (Search.Name <> '..') and (SubDirectories) then
       GetFilesList(Folder+Search.Name, FileType, FileMask, ListHolder, True, ClearList, ReturnFullPath)
    else
       begin
         if FileType = '.???' then
            begin
              if (Search.Name <> '.') and (Search.Name <> '..') then
                 if ListHolder.IndexOf(Search.Name) = -1 then
                    begin
                      case ReturnFullPath of
                        True : ListHolder.Add(Folder+Search.Name);
                        False: ListHolder.Add(Search.Name);
                      end;
                    end;
            end
         else
         if LowerCase(ExtractFileExt(Search.Name)) = FileType then
            begin
              if ListHolder.IndexOf(Search.Name) = -1 then
                 begin
                   case ReturnFullPath of
                     True : ListHolder.Add(Folder+Search.Name);
                     False: ListHolder.Add(Search.Name);
                   end;
                 end;
            end;
       end;
    Counter:= FindNext(Search);
  end;
  FindClose(Search);
  ListHolder.EndUpdate;
end;

procedure GetFoldersList(Folder: String; ListHolder: THashedStringList; ClearList, SubDirectories: Boolean);
var
  Search: TSearchRec;
  LengthDir: Longint;
  Counter: Integer;
begin
  LengthDir:= Length(Folder);
  if LengthDir = 0 then Exit;

  if ClearList then
     begin
       ListHolder.BeginUpdate;
       ListHolder.Clear;
       ListHolder.EndUpdate;
     end;
  ListHolder.BeginUpdate;
  // Rewrite path
  Folder:= IncludeTrailingPathDelimiter(Folder);

  // Add Files
  Counter:= FindFirst(Folder + '*.*', $37, Search);
  while Counter = 0 do
  begin
    // It's a directory?
    if (Search.Attr and $10 = $10) and (Search.Name <> '.') and
       (Search.Name <> '..') and (SubDirectories) then
       begin
         ListHolder.EndUpdate;
         GetFoldersList(Folder+Search.Name, ListHolder, False, True);
       end
    else
       begin
         if ListHolder.IndexOf(ExcludeTrailingPathDelimiter(Folder)) = -1 then
            ListHolder.Add(ExcludeTrailingPathDelimiter(Folder));
       end;
    Counter:= FindNext(Search);
  end;
  FindClose(Search);
  ListHolder.EndUpdate;
end;

function EncryptData(DataString: String): String;
var
  Data: String[255];
  Count: array[0..255] of Byte absolute Data;
  Loop: Integer;
begin
  Data:= DataString;
  for Loop:=1 to Ord(Data[0]) do
      Count[Loop]:= 23 xor Count[Loop];
  Result:= Data;
end;

function DecryptData(DataString: String): String;
var
  Data: String[255];
  Count: array[0..255] of Byte absolute Data;
  Loop: Integer;
begin
  Data:= DataString;
  for Loop:=1 to Length(Data) do
      Count[Loop]:= 23 xor Count[Loop];
  Result:= Data;
end;

function ExtractMAMEIniValue(MAMEOption: String): String;
var
  Index: Integer;
  Disabled: Boolean;
begin
  Result:= '';
  Disabled:= (Pos('#', MAMEOption) > 0);
  Index:= Pos(' ', MAMEOption);
  if Index = -1 then
     Result:= ''
  else
     begin
        Delete(MAMEOption, 1, Index);
        if Disabled then
           begin
             Index:= Pos(' ', MAMEOption);
             if Index = -1 then
                begin
                  Result:= '';
                  Exit;
                end
             else
                Delete(MAMEOption, 1, Index);
           end;
        Result:= Trim(MAMEOption);
     end;
end;

function GetGameHistory(const GameName: String; const StringLine: String): Boolean;
var
  Loop, Counter2: Integer;
  HistoryName: String;
begin
  Result:= False;
  HistoryName:= '';
  Counter2:= Length(StringLine);
  for Loop:=7 to Counter2 do
  begin
    if (StringLine[Loop] <> ',') and (Loop < Counter2) then
       HistoryName:= HistoryName+StringLine[Loop]
    else
       begin
         if (StringLine[Loop] <> ',') and (Loop = Counter2) then
            HistoryName:= HistoryName+StringLine[Loop];
         if Trim(LowerCase(HistoryName)) = LowerCase(GameName) then
            begin
              Result:= True;
              Break;
            end
         else
            HistoryName:= '';
       end;
  end;
end;

procedure GetGamesFilesList(Folder, FileType: String; ListHolder: THashedStringList; SubDirectories: Boolean);
var
  Search: TSearchRec;
  LengthDir: Longint;
  Counter: Integer;
begin
  LengthDir:= Length(Folder);
  if LengthDir = 0 then Exit;

  ListHolder.BeginUpdate;
  // Rewrite path
  if Folder[LengthDir] <> ':' then
     Folder:= IncludeTrailingPathDelimiter(Folder);

  // Add Files
  Counter:= FindFirst(Folder + '*.*', $37, Search);
  while Counter=0 do
  begin
    // It's a directory?
    if (Search.Attr and $10 = $10) and (Search.Name <> '.') and
       (Search.Name <> '..') and (SubDirectories) then
       GetGamesFilesList(Folder+Search.Name, FileType, ListHolder, True)
    else
       begin
         if LowerCase(ExtractFileExt(Search.Name)) = FileType then
            begin
              if ListHolder.IndexOf(Folder+Search.Name) = -1 then
                 ListHolder.Add(Folder+Search.Name);
            end;
       end;
    Counter:= FindNext(Search);
  end;
  FindClose(Search);
  ListHolder.EndUpdate;
end;

function GetPlayTime(Milliseconds: Int64; GamesList, FMOD: Boolean): String;
var
  Hours, Minutes, Seconds, Days, Sec: Int64;
begin
{
from MAME Plus!

hour = temp / 3600;
	temp = temp - 3600*hour;
	minute = temp / 60; //Calc Minutes
	second = temp - 60*minute;

	if (hour == 0)
		swprintf(buf, TEXT("%d:%02d"), minute, second );
	else
		swprintf(buf, TEXT("%d:%02d:%02d"), hour, minute, second );
}

  Sec:= Milliseconds div 1000;
  Days:= Sec div (3600*24);
  Hours:= (Sec-((Days*(3600*24)))) div 3600;
  Minutes:= (Sec-((Days*(3600*24))+(Hours*3600))) div 60;
  Seconds:= (Sec-((Days*(3600*24))+(Hours*3600)+(Minutes*60)));
  case GamesList of
    True:
      begin
        if Days = 0 then
           Result:= Format('%.2d:%.2d:%.2d', [Hours, Minutes, Seconds])
        else
           Result:= Format('%.d d %.2d:%.2d:%.2d', [Days, Hours, Minutes, Seconds]);
      end;
    False:
      begin
        case FMOD of
          True : Result:= Format('%.2d:%.2d', [Minutes, Seconds]);
          False: Result:= Format('%.2d:%.2d min', [Minutes, Seconds]);
        end;
      end;
  end;
end;

function ComputeHashValue(Mode: Integer; const FileName: String): String;
var
  Checksum: TMessageDigest;
  Stream: TFileStream;
begin
  // "No Authentication" mode
  Result:= '';
  if ((Mode < Low(ChecksumMode)) or (Mode > High(ChecksumMode))) or
     (not FileExists(FileName)) then
     Exit;

  try
    Checksum:= ChecksumMode[Mode].Create;
    Stream:= TFileStream.Create(FileName, fmOpenRead);
    Stream.Seek(0, soFromBeginning);
    Checksum.TransformStream(Stream);
    Checksum.Complete;
    FreeAndNil(Stream);
    Result:= LowerCase(Checksum.HashValue);
    FreeAndNil(Checksum);
  except
    FreeAndNil(Stream);
    FreeAndNil(Checksum);
  end;
end;

function GetSHA1_Value(strFileName: String): String;
begin
  Result:= '';
  if not FileExists(strFileName) then
     Exit;
  try
    Result:= ComputeHashValue(3, strFileName);
  except
    Result:= '';
  end;
end;

function GetMD5_Value(strFileName: String): String;
begin
  Result:= '';
  if not FileExists(strFileName) then
     Exit;
  try
    Result:= ComputeHashValue(2, strFileName);
  except
    Result:= '';
  end;
end;

  // Use CalcCRC32 as a procedure so CRCValue can be passed in but
  // also returned. This allows multiple calls to CalcCRC32 for
  // the "same" CRC-32 calculation.
procedure CalcCRC32(p: Pointer; ByteCount: DWORD; var CRCValue: DWORD);
  // The following is a little cryptic (but executes very quickly).
  // The algorithm is as follows:
  // 1. exclusive-or the input byte with the low-order byte of
  // the CRC register to get an INDEX
  // 2. shift the CRC register eight bits to the right
  // 3. exclusive-or the CRC register with the contents of Table[INDEX]
  // 4. repeat steps 1 through 3 for all bytes
var
  i: DWORD;
  q: ^BYTE;
begin
  q:= p;
  for i:= 0 to ByteCount - 1 do
  begin
    CRCvalue:= (CRCvalue shr 8) xor Table[q^ xor (CRCvalue and $000000FF)];
    Inc(q);
  end;
end;

function CalcStringCRC32(s: string; out CRC32: DWORD): Boolean;
var
  CRC32Table: DWORD;
begin
  // Verify the table used to compute the CRCs has not been modified.
  // Thanks to Gary Williams for this suggestion, Jan. 2003.
  CRC32Table:= $FFFFFFFF;
  CalcCRC32(Addr(Table[0]), SizeOf(Table), CRC32Table);
  CRC32Table:= not CRC32Table;

  if CRC32Table <> $6FCF9E13 then
     GenerateMessage('CRC32 Table Corrupted',
                     'CRC32 Table CRC32 is '+IntToHex(Crc32Table, 8)+', expecting 6fcf9e13.', 2)
  else
     begin
       CRC32:= $FFFFFFFF; // To match PKZIP
       if Length(s) > 0 then
          CalcCRC32(Addr(s[1]), Length(s), CRC32); // Avoid access violation in D4
       CRC32:= not CRC32; // To match PKZIP
     end;
end;

function CalcFileCRC32(FromName: String; var CRC32Value: DWORD): Boolean;
var
  Stream: TMemoryStream;
begin
  CRC32Value:= $FFFFFFFF;
  Result:= True;
  if not FileExists(FromName) then
     Exit;
  try
    Stream:= TMemoryStream.Create;
    try
      Stream.LoadFromFile(FromName);
      if Stream.Size > 0 then
         CalcCRC32(Stream.Memory, Stream.Size, CRC32Value);
    except
      on E: EReadError do
         Result:= False;
    end;
    CRC32Value:= not CRC32Value;
  finally
    Stream.Free;
  end;
end;

{function CalcSNESCRC32(FileStrm: TMemoryStream; var CRC32Value: DWORD): Boolean;
begin
  CRC32Value:= $FFFFFFFF;
  Result:= True;
    try
      if FileStrm.Size > 0 then
         CalcCRC32(FileStrm.Memory, FileStrm.Size, CRC32Value);
    except
      on E: EReadError do
         Result:= False;
    end;
    CRC32Value:= not CRC32Value;
end;}

function GetCRC32(emuFile: String): String;
var
  CRC32: DWORD;
begin
  Result:= '';
  if FileExists(emuFile) then
     begin
       if CalcFileCRC32(emuFile, CRC32) then
          Result:= LowerCase(Format('%.8x', [CRC32]));
     end;
end;

function InputBoxEL(const CaptionMsg, PromptMsg, DefaultValue: String): String;
begin
  Result:= DefaultValue;
  if not Assigned(FormInputBox) then
     FormInputBox:= TFormInputBox.Create(nil);
  try
    FormInputBox.Caption:= CaptionMsg;
    FormInputBox.LabelText.Caption:= PromptMsg;
    FormInputBox.NewText.Text:= DefaultValue;
    FormInputBox.ShowModal;
    if FormInputBox.MsgResult = mrOk then
       Result:= FormInputBox.NewText.Text;
  finally
    FreeAndNil(FormInputBox);
  end;
end;

function InputBoxELQuery(const CaptionMsg, PromptMsg, DefaultValue: String; out ValueOutput: String): Boolean;
begin
  Result:= False;
  if not Assigned(FormInputBox) then
     FormInputBox:= TFormInputBox.Create(nil);
  try
    FormInputBox.Caption:= CaptionMsg;
    FormInputBox.LabelText.Caption:= PromptMsg;
    FormInputBox.NewText.Text:= DefaultValue;
    FormInputBox.ShowModal;
    if FormInputBox.MsgResult = mrOk then
       begin
         Result:= True;
         ValueOutput:= FormInputBox.NewText.Text;
       end;
  finally
    FreeAndNil(FormInputBox);
  end;
end;

function GetButtonDescIndex(const Description: String): ShortInt;
var
  intLoop: ShortInt;
begin
  for intLoop:=0 to Length(ToolBarIndex) do
  begin
    if ToolBarIndex[intLoop] = Description then
       begin
         Result:= intLoop;
         Break;
       end;
  end;
end;

function CheckAndCreateFolder(const FolderString: String): Boolean;
begin
  Result:= DirectoryExists(FolderString);
  if not Result then
     Result:= ForceDirectories(FolderString);
end;

function CompareIntValue(const A, B: Integer): ShortInt;
begin
  if A = B then
     Result:= 0 // equal value
  else
  if A < B then
     Result:= -1
  else
     Result:= 1;
end;

procedure SetComboBoxEx(Holder: TComboBoxEx; ItemNumber: Integer; ResetSelection: Boolean = False);
begin
  if (Holder.ItemIndex = -1) or ResetSelection then
   begin
     Holder.ItemIndex:= ItemNumber;
     if Assigned(Holder.OnSelect) then
        Holder.OnSelect(nil);
   end;
end;

procedure SetSelectedComboBox(sIndex: ShortInt; ComboBoxHolder: TComboBox);
begin
  ComboBoxHolder.ItemIndex:= sIndex;
  if Assigned(ComboBoxHolder.OnSelect) then
     ComboBoxHolder.OnSelect(ComboBoxHolder);
end;

function XML_CheckData(strLine, FieldEntry: String): Boolean;
begin
  Result:= Copy(strLine, 1, Length(FieldEntry)) = FieldEntry;
end;

function XML_GetEntryName(strLine: String): String;
var
  Index: Integer;
begin
  Result:= '';
  if strLine <> '' then
     begin
       if strLine[1] <> '#' then
          begin
            Index:= Pos(' ', strLine);
            if Index <> 0 then
               Result:= Copy(strLine, 1, Index-1);
          end;
     end;
end;

function XML_GetEntryValue(strLine, EntryName: String): String;
var
  strPosition: Integer;
begin
  Result:= '';
  if strLine <> '' then
     begin
       strPosition:= Pos(EntryName+'="', strLine);
       if strPosition <> 0 then
          begin
            strPosition:= strPosition+(Length(EntryName)+1);
            Result:= strLine;
            Delete(Result, 1, strPosition);
            Result:= Copy(Result, 1, Pos('"', Result)-1);
            if Result = '' then
               Result:= '';
          end;
     end;
end;

function IsWindowsVista: Boolean;
begin
  Result:= (Win32Platform = VER_PLATFORM_WIN32_NT) and
           (Win32MajorVersion = 6);
end;

function GetWindowsVersion: String;
var
   WindowsInfo: TOSVersionInfo;
begin
    WindowsInfo.dwOSVersionInfoSize:= SizeOf(OSVERSIONINFO);
    GetVersionEx(WindowsInfo);

    case WindowsInfo.dwPlatformId of
      VER_PLATFORM_WIN32_NT:
         begin
           case WindowsInfo.dwMajorVersion of
             4: Result:=  'Microsoft Windows NT';
             5:
               begin
                 case WindowsInfo.dwMinorVersion of
                   0: Result:= 'Microsoft Windows 2000';
                   1: Result:= 'Microsoft Windows XP';
                   2: Result:= 'Microsoft Windows 2003';
                 end;
               end;
             6: Result:= 'Microsoft Windows Vista';
           end;
         end;
      VER_PLATFORM_WIN32_WINDOWS:
         begin
           case WindowsInfo.dwMajorVersion of
             4:
               begin
                 case WindowsInfo.dwMinorVersion of
                    0: Result:= 'Microsoft Windows 95';
                   10: Result:= 'Microsoft Windows 98';
                   90: Result:= 'Microsoft Windows ME';
                 end;
               end;
           end;
         end;
    end;
end;

end.
