unit umainform;

{$DEFINE USE_DCPCRYPT}// Delete this if you don't have the DCrypt library
// It enables the 'Encrypt INI' and 'Decrypt INI' menu entries
// {$DEFINE USE_THREADSAFE} // Enable this for the thread-safe version

{ Test App for cryptini unit

  Copyright (C) 2016 Gordon Bamber minesadorada@gmail.com
  Encrypt/Decrypt INI code: @Ericktux (http://forum.lazarus.freepascal.org)
  This library is free software; you can redistribute it and/or modify it
  under the terms of the GNU Library General Public License as published by
  the Free Software Foundation; either version 2 of the License, or (at your
  option) any later version.

  This program is distributed in the hope that it will be useful, but WITHOUT
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
  for more details.

  You should have received a copy of the GNU Library General Public License
  along with this library; if not, write to the Free Software Foundation,
  Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
}
{$mode objfpc}{$H+}

interface

uses
  SysUtils, LazFileUtils, FileUtil, Forms, Dialogs, StdCtrls, Controls, Classes,
  Buttons, ExtCtrls, Menus, ucryptini, umemoform, ukeydialog,
  uInputSectionValuesForm;

const
    {$IFDEF WINDOWS}
  C_OS = 'win';
    {$ELSE}
  C_OS = 'linux';
    {$ENDIF}
    {$IFDEF CPU32}
  C_BITNESS = '32';
    {$ELSE}
  C_BITNESS = '64';
    {$ENDIF}
  C_PFX = C_OS + C_BITNESS;
  C_KEYPHRASE = 'Rudolph the Red Nosed Reindeer: had a very shiny nose';

  C_VERSION = '1.0.0.5';

type

  { Tmainform }

  Tmainform = class(TForm)
    cmd_convertToCryptini: TButton;
    cmd_DeleteValue: TButton;
    cmd_EraseSection: TButton;
    cmd_Read: TButton;
    cmd_ReadSectionValues: TButton;
    cmd_ShowINI: TButton;
    cmd_Close: TBitBtn;
    cmd_ValueExists: TButton;
    cmd_Verify: TButton;
    cmd_VerifySectionValues: TButton;
    cmd_Write: TButton;
    cmd_WriteSection: TButton;
    cmb_Sections: TComboBox;
    edt_Value: TEdit;
    edt_Section: TEdit;
    edt_Ident: TEdit;
    edt_Integer: TEdit;
    GroupBox1: TGroupBox;
    grp_convert: TGroupBox;
    Grp_DefaultValueTests: TGroupBox;
    lbl_Value: TLabel;
    lbl_Section: TLabel;
    lbl_Ident: TLabel;
    lbl_Integer: TLabel;
    MainMenu1: TMainMenu;
    mnu_optionsDecryptINIFile: TMenuItem;
    mnu_optionsEncryptINIFile: TMenuItem;
    mnu_helpAbout: TMenuItem;
    mnu_helpHelp: TMenuItem;
    mnu_help: TMenuItem;
    mnu_optionsEncryptionKey: TMenuItem;
    mnu_fileClose: TMenuItem;
    mnu_options: TMenuItem;
    mnu_file: TMenuItem;
    OpenDialog1: TOpenDialog;
    rg_Encryption: TRadioGroup;
    rg_SectionHashing: TRadioGroup;
    procedure cmb_SectionsSelect(Sender: TObject);
    procedure cmd_convertToCryptiniClick(Sender: TObject);
    procedure cmd_DeleteValueClick(Sender: TObject);
    procedure cmd_ReadClick(Sender: TObject);
    procedure cmd_ReadSectionValuesClick(Sender: TObject);
    procedure cmd_ShowINIClick(Sender: TObject);
    procedure cmd_ValueExistsClick(Sender: TObject);
    procedure cmd_VerifyClick(Sender: TObject);
    procedure cmd_VerifySectionValuesClick(Sender: TObject);
    procedure cmd_WriteClick(Sender: TObject);
    procedure cmd_EraseSectionClick(Sender: TObject);
    procedure cmd_WriteSectionClick(Sender: TObject);
    procedure edt_IntegerEditingDone(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure mnu_fileCloseClick(Sender: TObject);
    procedure mnu_helpAboutClick(Sender: TObject);
    procedure mnu_helpHelpClick(Sender: TObject);
    {$IFDEF USE_DCPCRYPT}
    procedure mnu_optionsDecryptINIFileClick(Sender: TObject);
    procedure mnu_optionsEncryptINIFileClick(Sender: TObject);
    {$ENDIF}
    procedure mnu_optionsEncryptionKeyClick(Sender: TObject);
    procedure rg_EncryptionSelectionChanged(Sender: TObject);
    procedure rg_SectionHashingSelectionChanged(Sender: TObject);
  private
    IniFilePath: string;
    {$IFDEF USE_THREADSAFE}
    INI: TLockCryptIniFile;
    {$ELSE}
    INI: TCryptIniFile;
    {$ENDIF}
    sStoredMD5Hash: string;
    sVersion: string;
  public

  end;

var
  mainform: Tmainform;

implementation

{$R *.lfm}

{ Tmainform }

procedure Tmainform.FormCreate(Sender: TObject);
begin
  IniFilePath := ProgramDirectory + 'test' + C_PFX +
{$IFDEF WINDOWS}
    '.' +
{$ENDIF}
    'ini';

{$IFNDEF USE_DCPCRYPT}
  mnu_optionsEncryptINIFile.Enabled := False;
  mnu_optionsDecryptINIFile.Enabled := False;
{$ENDIF}

  {DEBUG - delete any old versions of the INI
  if FileExists(IniFilePath) then
    DeleteFile(IniFilePath);
  }
  Caption := Application.Title;
  Icon := Application.Icon;
  {$IFDEF USE_THREADSAFE}
  INI := TLockCryptIniFile.Create(IniFilePath);
  INI.Lock;
  {$ELSE}
  INI := TCryptIniFile.Create(IniFilePath);
  {$ENDIF}

  // Create encryption key for secure Read/WriteInteger
  INI.KeyPhrase := C_KEYPHRASE;

  // Or set INI.Key directly (weaker encryption)
  // DEBUG: ShowMessageFmt('Key set to %d',[INI.Key]);

  // method: WriteIdent(Const sAuthor,sCopyright,sLicense,sContact:String;Force: boolean=False);
  // No need to do this each time
  // Comment this line out once you have the MD5Hash from the ini file
  if INI.IsVirgin then // DeFlowers
  begin
    INI.WriteIdent('Gordon Bamber', '(c)2016', 'LGPL', 'minesadorada@gmail.com', True);
    // MD5 for this is: 92abf0deecbb25c435bff507a396d92a
  end
  else
  // someone tampered with the ident?
  if not INI.VerifyIdent('92abf0deecbb25c435bff507a396d92a') then
  begin
    ShowMessage('Program ident has been tampered with.' + LineEnding +
      'Restoring correct version.');
    // Last parameter (Optional) forces a rewrite even if FirstRun = 0
    INI.WriteIdent('Gordon Bamber', '(c)2016', 'LGPL', 'minesadorada@gmail.com', True);
  end;

  sVersion := C_VERSION;
  sStoredMD5Hash := '32-character MD5Hash string';
  INI.ReadSections(cmb_Sections.Items);
  cmb_Sections.ItemIndex := 0;
  edt_Section.Text := 'TestSection';
end;

procedure Tmainform.FormDestroy(Sender: TObject);
begin
  If Assigned(INI) then FreeAndNil(INI);
end;

procedure Tmainform.FormShow(Sender: TObject);
begin
  // Test the IsVirgin function
  if INI.IsVirgin then
    ShowMessage('First time run of this app');
end;

procedure Tmainform.mnu_fileCloseClick(Sender: TObject);
begin
  Close;
end;

procedure Tmainform.mnu_helpAboutClick(Sender: TObject);
// Shows ReadUnencryptedString method
var
  s: string;
begin
  s := Application.Title + LineEnding;
  s += 'Version: ' + INI.ReadUnencryptedString('ProgramInfo', IDENT_APPVERSION, '') + LineEnding + LineEnding;
  s += INI.ReadUnencryptedString('ProgramInfo', IDENT_COPYRIGHT, '');
  s += ' by ' + INI.ReadUnencryptedString('ProgramInfo', IDENT_AUTHOR, '') + LineEnding;
  s += 'Licence: ' + INI.ReadUnencryptedString('ProgramInfo', IDENT_LICENSE, '') +
    LineEnding;
  s += 'Made with LCL v ' + INI.ReadUnencryptedString('ProgramInfo', IDENT_LCLVERSION, '');
  s += ' FPC v ' + INI.ReadUnencryptedString('ProgramInfo', IDENT_FPCVERSION, '') +
    LineEnding;
  s += 'Compiled ' + INI.ReadUnencryptedString('ProgramInfo', IDENT_LASTCOMPILED, '');
  s += ' for ' + INI.ReadUnencryptedString('ProgramInfo', IDENT_TARGET, '') +
    LineEnding;
  s += 'CryptINI Version: ' + INI.CryptINIVersion + LineEnding;
  s += 'Cipher in use: ' + INI.CipherType + '.  Hash in use: ' + INI.HashType + '.';
  MessageDlg('About ' + Application.Title, s,
    mtInformation, [mbOK], 0);
end;

procedure Tmainform.mnu_helpHelpClick(Sender: TObject);
begin
  with ShowINIForm do
  begin
    MakeReadOnly;
    Caption := 'Help for ' + Application.Title;
    Memo_INI.Lines.Clear;
    Memo_INI.Lines.Add('This test application is to test the Tcryptini class V ' +
      INI.CryptINIVersion);
    Memo_INI.Lines.Add('');
    Memo_INI.Lines.Add(
      'The obvious test is to click Write, Read then Verify, which uses automatic values.');
    Memo_INI.Lines.Add(
      'Verification of an entry involves reading the value with its built-in MD5Hash');
    Memo_INI.Lines.Add(
      'and then computing a new MD5Hash from the value, and comparing it with the built-in one.');
    Memo_INI.Lines.Add('');
    Memo_INI.Lines.Add(
      'Additionally, if SectionHashing=TRUE then every time a new entry is added to the section,');
    Memo_INI.Lines.Add(
      'all the entries are combined into a section hash, and it is written/updated as an automatic');
    Memo_INI.Lines.Add(
      'new/updated entry: MD5Hash=<32-character hash> ,which you can use in the VerifySection(MD5Has Value) method');
    Memo_INI.Lines.Add('');
    Memo_INI.Lines.Add(
      'There is a new method WriteSectionValues(Section,Strings) which is absent in TINIFile that you may find useful.');
    Memo_INI.Lines.Add(
      'WriteSectionValues works with PlainText TRUE/FALSE and SectionHashing TRUE/FALSE.');
    Memo_INI.Lines.Add('');
    Memo_INI.Lines.Add(
      'Other new methods are EncryptINI and DecryptINI.  This acts on the whole INI file,');
    Memo_INI.Lines.Add(
      'with password, new file extension and auto-deleting the "old" file as optional parameters.');
    Memo_INI.Lines.Add('The defaults are the INI.KeyPhrase, ".enc" and no auto-deletion.');
    Memo_INI.Lines.Add('');
    Memo_INI.Lines.Add(
      'Wnen property PlainText=TRUE then TCryptINI behaves just as TINIFile did');
    Memo_INI.Lines.Add(
      'This property can be changed on-the-fly to enable a mixed Crypted/Plaintext INI file.');
    Memo_INI.Lines.Add('');
    Memo_INI.Lines.Add(
      'Note that Integer keys have an automatic "' + INTEGER_MARKER +
      '" added in encrypted mode.');
    Memo_INI.Lines.Add(
      'This is so that the ReadSection method can identify them in Encrypted mode.');
    Memo_INI.Lines.Add(
      '(Integers are double-encrypted for extra security, so need to be read differently from all other types)');
    Memo_INI.Lines.Add('');
    Memo_INI.Lines.Add(
      'When testing, use the Show/Edit INI button to see the results.  This will help you to understand');
    Memo_INI.Lines.Add(
      'how CryptINI works, and what it can do.');
    Memo_INI.Lines.Add('');
    Memo_INI.Lines.Add('CryptINI was designed to solve 2 issues:');
    Memo_INI.Lines.Add(
      '1. Making sure the ProgramInfo section is never altered, assuring you attribution stays secure.');
    Memo_INI.Lines.Add(
      'Once you have the MD5Hash of it, you can easily make an "authenticity checker" app using CryptINI');
    Memo_INI.Lines.Add(
      '2. Storing Passwords and Scores in an editable INI file which is tamper-proof to the casual hacker.');
    Memo_INI.Lines.Add(
      '(If numbers are written using WriteInteger, they are especially difficult to alter)');
    Memo_INI.Lines.Add('');
    Memo_INI.Lines.Add('In this app and ucryptini.pas is a DEFINE: {$DEFINE USE_DCPCRYPT}');
    Memo_INI.Lines.Add(
      'This assumes you have the DCPCrypt runtime/designtime component installed.');
    Memo_INI.Lines.Add(
      'If you haven''t then either install it or comment out the DEFINE and');
    Memo_INI.Lines.Add(
      'remove it from Project Inspector. It is available via OnlinePackageManager');
    Memo_INI.Lines.Add('');
    Memo_INI.Lines.Add('Open ucryptini in a text editor to look at the commented code');
    Memo_INI.Lines.Add('- Enjoy!');
    ShowModal;
  end;
end;

{$IFDEF USE_DCPCRYPT}
procedure Tmainform.mnu_optionsDecryptINIFileClick(Sender: TObject);
begin
  if MessageDlg('Delete encrypted INI file?', mtConfirmation, [mbYes, mbNo], 0, mbYes) =
    mrYes then
    INI.DecryptINI(True)
  else
    INI.DecryptINI(False);
  // Remember to reset the key phrase!
  INI.KeyPhrase := C_KEYPHRASE;

  // Update controls
  cmb_Sections.Clear;
  INI.ReadSections(cmb_Sections.Items);
  cmb_Sections.Refresh;
  cmb_Sections.ItemIndex := 0;
end;

procedure Tmainform.mnu_optionsEncryptINIFileClick(Sender: TObject);
begin
  if MessageDlg('Delete unencrypted INI file?', mtConfirmation,
    [mbYes, mbNo], 0, mbYes) = mrYes then
    INI.EncryptINI(True)
  else
    INI.EncryptINI(False);
  Application.ProcessMessages;
  // Update controls
  cmb_Sections.Clear;
  cmb_Sections.ItemIndex := 0;
end;

{$ENDIF}

procedure Tmainform.mnu_optionsEncryptionKeyClick(Sender: TObject);
var
  s: string;
  l: longint;
begin
  keydialog.sKeyPhrase := INI.KeyPhrase;
  KeyDialog.ShowModal;
  if KeyDialog.ModalResult = mrOk then
  begin
    s := KeyDialog.edt_key.Text;
    if TryStrToInt(s, l) then
      INI.Key := l
    else
      INI.Keyphrase := s;
    ShowMessage('Key changed successfully');
  end;
end;

procedure Tmainform.rg_EncryptionSelectionChanged(Sender: TObject);
begin
  if rg_Encryption.ItemIndex = 0 then
    INI.PlainTextMode := False
  else
    INI.PlainTextMode := True;
  if INI.PlainTextMode = True then
  begin
    rg_SectionHashing.ItemIndex := 1;
    INI.SectionHashing := False;
  end;
  cmd_Read.Enabled := False;
  cmd_Verify.Enabled := False;
end;

procedure Tmainform.rg_SectionHashingSelectionChanged(Sender: TObject);
begin
  if rg_SectionHashing.ItemIndex = 0 then
    INI.SectionHashing := True
  else
    INI.SectionHashing := False;
end;

procedure Tmainform.cmd_WriteClick(Sender: TObject);
var
  s: string;
begin
  // Best results when writing to TestSection
  if (edt_Section.Text <> 'TestSection') then
    if MessageDlg('Please confirm', 'Are you sure you want to write to ' +
      edt_Section.Text + '? (It should be "TestSection")', mtConfirmation,
      [mbYes, mbNo], 0, mbNo) = mrNo then
    begin
      edt_Section.Text := 'TestSection';
      ShowMessage('OK. Writing to default section "TestSection" instead');
    end;
  INI.SectionHashing := False;
  INI.WriteString(edt_Section.Text, edt_Ident.Text, edt_Value.Text);
  INI.WriteInteger(edt_Section.Text, 'Integer', StrToInt(edt_Integer.Text));
  // Write other types as a test
  INI.WriteBool(edt_Section.Text, 'Boolean', True);
  INI.WriteFloat(edt_Section.Text, 'Float', 3.142);
  INI.WriteDateTime(edt_Section.Text, 'Date', StrToDate('15/10/2016',
    'dd mm yyyy', '/'));
  INI.WriteInt64(edt_Section.Text, 'Int64', 1000);
  INI.SectionHashing := True;
  INI.MakeSectionHash(edt_Section.Text, True);
  cmd_Read.Enabled := True;
  cmd_Verify.Enabled := True;
  s := 'Values written:' + LineEnding;
  s += 'WriteString: ' + edt_Value.Text + LineEnding;
  s += 'WriteFloat: 3.142' + LineEnding;
  s += 'WriteDateTime: 15/10/2016' + LineEnding;
  s += 'WriteInt64: 1000' + LineEnding;
  s += 'WriteInteger: ' + edt_Integer.Text + LineEnding;
  INI.ReadSections(cmb_Sections.Items);
  cmb_Sections.ItemIndex := Pred(cmb_Sections.Items.Count);
  ShowMessage(s);
end;

procedure Tmainform.cmd_EraseSectionClick(Sender: TObject);
begin
  if not INI.SectionExists(edt_Section.Text) then
  begin
    ShowMessage(edt_Section.Text + ' is absent, so nothing to erase!');
    Exit;
  end;
  if edt_Section.Text = IDENT_SECTION then
    if MessageDlg('Please confirm', 'Are you sure you want to delete ' +
      IDENT_SECTION + '?', mtConfirmation, [mbCancel, mbYes], 0, mbCancel) =
      mrCancel then
      exit;
  INI.EraseSection(edt_Section.Text);
  ShowMessage('Section ' + edt_Section.Text + ' is no more.');
  INI.ReadSections(cmb_Sections.Items);
  cmb_Sections.ItemIndex := 0;
end;

procedure Tmainform.cmd_WriteSectionClick(Sender: TObject);
var
  iCount: integer;
  sTempSectionName: string;
  MyStringList: TStrings;
begin
  with InputSectionValuesForm do
  begin
    ShowModal;
    if ModalResult = mrCancel then
      Exit;
    sTempSectionName := sSectionName;
    if sTempSectionName = IDENT_SECTION then
      if MessageDlg('Please confirm', 'Are you sure you want to write to ' +
        IDENT_SECTION + '?', mtConfirmation, [mbYes, mbCancel], 0, mbCancel) =
        mrCancel then
        exit;
    edt_Section.Text := sSectionName;
    MyStringList := TStringList.Create;
    try
      MyStringList.Clear;
      MyStringList.BeginUpdate;
      for iCount := 0 to (NumberOfControls - 1) do
        MyStringList.Add(IdentEditArray[iCount].Text + '=' +
          ValueEditArray[iCount].Text);
      MyStringList.EndUpdate;
      INI.WriteSectionValues(sSectionName, MyStringList);
      INI.UpdateFile;
      ShowMessage('Section ' + sTempSectionName + ' written successfully');
    finally
      MyStringList.Free;
    end;
  end;
  INI.ReadSections(cmb_Sections.Items);
  cmb_Sections.ItemIndex := Pred(cmb_Sections.Items.Count);
end;

procedure Tmainform.edt_IntegerEditingDone(Sender: TObject);
var
  iTest: longint;
begin
  if not TryStrToInt(edt_Integer.Text, iTest) then
  begin
    ShowMessageFmt('%s is not an Integer.  Resetting to %s',
      [edt_Integer.Text, '12345']);
    edt_Integer.Text := '12345';
  end;
end;

procedure Tmainform.cmd_ReadClick(Sender: TObject);
var
  s: string;
begin
  if not INI.SectionExists(edt_Section.Text) then
  begin
    ShowMessage(edt_Section.Text + ' is absent, so nothing to read!');
    Exit;
  end;
  // Dont read IDENT_SECTION
  if edt_Section.Text = IDENT_SECTION then
  begin
    cmd_ReadSectionValues.Click;
    Exit;
  end;
  if (edt_Section.Text <> 'TestSection') then
    if MessageDlg('Please confirm', 'Are you sure you want to read test values from ' +
      edt_Section.Text + '?', mtConfirmation, [mbCancel, mbYes], 0, mbCancel) =
      mrCancel then
    begin
      edt_Section.Text := 'TestSection';
      ShowMessage('OK. Reading default section "TestSection" instead');
    end;

  if INI.PlainTextMode then
    INI.MD5Hash := 'n/a';
  s := INI.ReadString(edt_Section.Text, edt_Ident.Text, 'unknown');
  ShowMessageFmt('Value of %s in %s is %s %s(MD5 hash: %s)',
    [edt_Ident.Text, edt_Section.Text, s, LineEnding, INI.MD5Hash]);
  if INI.ReadBool(edt_Section.Text, 'Boolean', False) = True then
    ShowMessageFmt('ReadBool is TRUE%s(MD5 hash: %s)', [LineEnding, INI.MD5Hash])
  else
    ShowMessageFmt('ReadBool is FALSE%s(MD5 hash: %s)', [LineEnding, INI.MD5Hash]);
  ShowMessageFmt('ReadFloat is %.3f%s(MD5 hash: %s)',
    [INI.ReadFloat(edt_Section.Text, 'Float', 0), LineEnding, INI.MD5Hash]);
  ShowMessageFmt('ReadDateTime is %s%s(MD5 hash: %s)',
    [DateToStr(INI.ReadDateTime(edt_Section.Text, 'Date', NOW)),
    LineEnding, INI.MD5Hash]);
  ShowMessageFmt('ReadInt64 is %d%s(MD5 hash: %s)',
    [INI.ReadInt64(edt_Section.Text, 'Int64', 0), LineEnding, INI.MD5Hash]);
  ShowMessageFmt('ReadInteger is %d%s(MD5 hash: %s)',
    [INI.ReadInteger(edt_Section.Text, 'Integer', 0), LineEnding, INI.MD5Hash]);
end;

procedure Tmainform.cmb_SectionsSelect(Sender: TObject);
begin
  edt_Section.Text := cmb_Sections.Items[cmb_Sections.ItemIndex];

end;

procedure Tmainform.cmd_convertToCryptiniClick(Sender: TObject);
{
** This routine is a good demonstation of what TCryptINI can do
** It converts old ini files into encrypted ones.

** Workflow:
0. Inform user what is about to happen and offer bailout (very important!)
1. Backup old ini file
2. Make a working copy
3. Process the working copy
4. Seek approval of changes
5. If yes, Overwrite the old ini file and clean up
}
const
  CR = LineEnding;
var
  sINIFilePathToConvert, sSourceINIFilePath, s, TempSectionName, sKeyPhrase: string;
  sValueEntry, sKey, sValue: string;
  INIFileToConvert: TCryptINIFile;
  SectionNameList, ValueList: TStrings;
  iCount, jCount, lTemp: integer;
  dtTemp: TDateTime;
begin
  try // - EXCEPT
    s := 'This utility will convert a regular INI file to a CryptINI file' + CR;
    s += 'using the Password/Keyphrase of your choice.' + CR + CR;
    s += 'Your chosen INI file will first be backed up in the same folder,' + CR;
    s += 'and a working copy made.  After the conversion you will have a' + CR;
    s += 'chance to view the changes and either approve or revert them.' + CR + CR;
    s += 'If you approve, the original INI file will be overwritten by' + CR;
    s += 'the approved working copy.' + CR;
    s += 'If you revert, your original INI file will remain intact.' + CR + CR;
    s += 'Would you like to continue?' + CR;
    if MessageDlg(s, mtConfirmation, [mbYes, mbNo], 0, mbYes) <> mrYes then
      Exit;
    if OpenDialog1.Execute then
      sINIFilePathToConvert := OpenDialog1.FileName
    else
      Exit;
    // Prevent changing this app's INI file
    if sINIFilePathToConvert = INI.Filename then
    begin
      ShowMessage('You cannot choose the INI file for this application! Try again.');
      Exit;
    end;
    // Make a backup
    if CopyFile(sINIFilePathToConvert, ChangeFileExt(sINIFilePathToConvert, '.bak')) then
      ShowMessageFmt('Your existing INI file has been backed up to %s',
        [ChangeFileExt(sINIFilePathToConvert, '.bak')])
    else
    begin
      ShowMessage('Could not write to ' + ExtractFileDir(sINIFilePathToConvert) +
        ' - Quitting');
      Exit;
    end;
    // Make a working copy
    sSourceINIFilePath := ChangeFileExt(sINIFilePathToConvert, '.src');
    if not CopyFile(sINIFilePathToConvert, sSourceINIFilePath) then
    begin
      ShowMessage('Could not write to ' + ExtractFileDir(sINIFilePathToConvert) +
        ' - Quitting');
      Exit;
    end;
    // Use the working copy
    INIFileToConvert := TCryptINIFile.Create(sSourceINIFilePath);
    // Fetch a pass phrase
    sKeyPhrase := InputBox('Pass Phrase',
      'Please type in your pass phrase for this INI file', C_KEYPHRASE);
    INIFileToConvert.KeyPhrase := sKeyPhrase;
    // Create temprary stringlists
    SectionNameList := TStringList.Create;
    ValueList := TStringList.Create;
    try
      // Use as a regular TiniFile
      INIFileToConvert.PlainTextmode := True;
      INIFileToConvert.SectionHashing := False;
      SectionNameList.Clear;
      // Fetch all the Section names
      INIFileToConvert.ReadSections(SectionNameList);
      if SectionNameList.Count > 0 then
        // For each Sectionnane...
        for iCount := 0 to Pred(SectionNameList.Count) do
        begin
          TempSectionName := SectionNameList[iCount];
          // Don't process ProgramInfo
          if TempSectionName = IDENT_SECTION then
            Continue; // Dont convert this
          ValueList.Clear;
          // Fetch all the Key=Values for this Section
          INIFileToConvert.ReadSectionValues(TempSectionName, ValueList);
          if ValueList.Count > 0 then
            // For each Key-Value pair...
            for jCount := 0 to Pred(ValueList.Count) do
            begin
              sValueEntry := ValueList[jCount];
              sKey := '';
              sValue := '';
              // Split into Key and Value
              If NOT INI.SplitKeyValue(sValueEntry, sKey, sValue) then Continue;
              // We have the valid key and value else skipped
              // Don't process MD5Has key
              if sKey = IDENT_MD5HASH then
                Continue;
              // Is it a number?
              if TryStrToInt(sValue, lTemp) then // Integer
              begin
                INIFileToConvert.PlainTextMode := False;
                // Is it a Boolean?
                if ((lTemp = 0) or (lTemp = 1)) then // Guess a boolean value?
                  INIFileToConvert.WriteString(TempSectionName, sKey, sValue)
                else
                begin
                  // Process Integer Value
                  INIFileToConvert.PlainTextMode := True;
                  // Delete unencrypted key without INTEGER_MARKER
                  INIFileToConvert.DeleteKey(TempSectionName, sKey);
                  INIFileToConvert.PlainTextMode := False;
                  // Rewrite encrypted key with INTEGER_MARKER
                  INIFileToConvert.WriteInteger(TempSectionName, sKey, lTemp);
                end;
                INIFileToConvert.PlainTextMode := True;
              end
              else // String,Date
              begin
                // Process non-numeric values
                INIFileToConvert.PlainTextMode := False;
                // Is it a DateTime?
                if TryStrToDateTime(sValue, dtTemp) then
                  INIFileToConvert.WriteDateTime(TempSectionName, sKey, dtTemp)
                else
                  // Process String value
                  INIFileToConvert.WriteString(TempSectionName, sKey, sValue);
                INIFileToConvert.PlainTextMode := True;
              end;
              // Hash the whole Section (Make MD5Hash entry)
              INIFileToConvert.MakeSectionHash(TempSectionName, True);
            end;
        end;
      // Conversion is done.  Show the user the Working Copy
      with ShowINIForm do
      begin
        MakeReadOnly; // Put memoform into readonly mode
        cmd_Abort.Visible := True; // Show the invisible button
        Caption := 'Contents of converted INI file';
        Memo_INI.Lines.Clear;
        // Check the file is still there :)
        if LazFileUtils.FileExistsUTF8(INIFileToConvert.Filename) then
        begin
          Memo_INI.Lines.LoadFromFile(INIFileToConvert.Filename);
          sINIFilePath := INIFileToConvert.Filename;
          cmd_Close.Caption := 'Approve conversion';
          // Display the INI to the user
          ShowModal;
          // Tidy up
          cmd_Close.Caption := '&Close';
          cmd_Abort.Visible := False; // Make the button invisible again.
          // What did the user decide?
          if ModalResult <> mrAbort then // All good - proceed
          begin
            INIFileToConvert.UpdateFile;
            Sleep(100);
            // Overwrite old INI file:
            if not CopyFile(sSourceINIFilePath, sINIFilePathToConvert) then
            begin
              ShowMessage('Could not Update ' + ExtractFileDir(sINIFilePathToConvert) +
                ' - Quitting');
              Exit;
            end;

{
            // Encrypt file as well?
            if MessageDlg(
              'Conversion Successful. Would you like to Encrypt the whole file as well?',
              mtConfirmation, [mbYes, mbNo], 0, mbNo) = mrYes then
            begin
              // User wants to encrypt.  Get a new pass phrase.
              sKeyPhrase := InputBox('Pass Phrase',
                'Please type in your pass phrase for this INI file', C_KEYPHRASE);
              // Work with the (changed) original
              INIFileToConvert.Free; // Finished with the working copy
              // Load the original
              INIFileToConvert := TCryptINIFile.Create(sINIFilePathToConvert);
              INIFileToConvert.KeyPhrase:=sKeyPhrase;
              // Delete the old INI file or no?
              if MessageDlg('Delete unencrypted INI file?', mtConfirmation,
                [mbYes, mbNo], 0, mbYes) = mrYes then
                INIFileToConvert.EncryptINI(True, sKeyPhrase, '.enc')
              else
                INIFileToConvert.EncryptINI(False, sKeyPhrase, '.enc');
           end;
          }
            // All went well - inform the user.
            ShowMessage('All operations were successful.' + LineEnding +
              'Click OK to clean up temporary files');
          end
          else
            // User chose to Revert.  sINIFilePathToConvert file is still intact.

            ShowMessage('Conversion aborted. INI file is unchanged.' +
              LineEnding + 'Click OK to clean up temporary files');

          // Tidy up:
          // Delete working copy
          if not DeleteFile(sSourceINIFilePath) then
          begin
            ShowMessage('Could not Delete ' + ExtractFileDir(sSourceINIFilePath) +
              ' - Quitting');
            Exit;
          end;
          // Delete backup file as well?
          if MessageDlg('Delete backup file?', mtConfirmation, [mbYes, mbNo], 0, mbYes) =
            mrYes then
            if not DeleteFile(ChangeFileExt(sINIFilePathToConvert, '.bak')) then
            begin
              ShowMessage('Could not Delete ' +
                ExtractFileDir(ChangeFileExt(sINIFilePathToConvert, '.bak')));
              Exit;
            end;
        end;
      end;
    finally
      ValueList.Free;
      SectionNameList.Free;
      FreeAndNil(INIFileToConvert);
    end;
  except
    // It's a long routine.  Let's hope we don't get here.
    On E: Exception do
      ShowMessageFmt('An error has occurred that is not your fault.%sThe error is%s',
        [CR, E.Message]);
  end;
end;

procedure Tmainform.cmd_DeleteValueClick(Sender: TObject);
begin
  if not INI.SectionExists(edt_Section.Text) then
  begin
    ShowMessage(edt_Section.Text + ' is absent, so nothing to read!');
    Exit;
  end;
  if (edt_Section.Text <> 'TestSection') then
    if MessageDlg('Please confirm', 'Are you sure you want to delete test values from ' +
      edt_Section.Text + '?', mtConfirmation, [mbCancel, mbYes], 0, mbCancel) =
      mrCancel then
    begin
      edt_Section.Text := 'TestSection';
      ShowMessage('OK. Deleting values in default section "TestSection" instead');
    end;
  ShowMessage('Deleting values "' + edt_Ident.Text + '" and "Integer"');
  INI.DeleteKey(edt_Section.Text, edt_Ident.Text);
  INI.DeleteKey(edt_Section.Text, 'Integer');
  if INI.ValueExists(edt_Section.Text, edt_Ident.Text) then
    ShowMessage(edt_Ident.Text + ' not deleted!')
  else
    ShowMessage(edt_Ident.Text + ' deleted');
  if INI.ValueExists(edt_Section.Text, 'Integer') then
    ShowMessage('Integer not deleted!')
  else
    ShowMessage('Integer deleted');

end;

procedure Tmainform.cmd_ReadSectionValuesClick(Sender: TObject);
var
  MyStringList: TStrings;
  iCount: integer;
  TempPlainTextMode: boolean;
begin
  if not INI.SectionExists(edt_Section.Text) then
  begin
    ShowMessage(edt_Section.Text + ' is absent, so nothing to read!');
    Exit;
  end;
  TempPlainTextMode := INI.PlainTextMode;
  if edt_Section.Text = IDENT_SECTION then
    INI.PlainTextMode := True;
  MyStringList := TStringList.Create;
  try
    INI.ReadSectionValues(edt_Section.Text, MyStringList);
    if MyStringList.Count > 0 then
      for iCount := 0 to Pred(MyStringList.Count) do
        ShowMessageFmt('Section name: %s%s(Value %d of %d): %s',
          [edt_Section.Text, LineEnding, iCount + 1, MyStringList.Count,
          MyStringList[iCount]])
    else
      ShowMessage('Nothing in this section!');
  finally
    MyStringList.Free;
  end;
  INI.PlainTextMode := TempPlainTextMode;
end;

procedure Tmainform.cmd_ShowINIClick(Sender: TObject);
var
  s: string;
begin
  with ShowINIForm do
  begin
    MakeWriteable;
    Caption := 'Contents of ' + INI.Filename;
    Memo_INI.Lines.Clear;
    {$WARN  UNIT_DEPRECATED OFF}
    if LazFileUtils.FileExistsUTF8(INI.Filename) then
    begin
      Memo_INI.Lines.LoadFromFile(INI.Filename);
      sINIFilePath := INI.Filename;
      ShowModal;
      if bDirty then
      begin
        s := INI.KeyPhrase;
        // Reload (with correct keyphrase)
        INI.Free;
        {$IFDEF USE_THREADSAFE}
        INI := TLockCryptIniFile.Create(IniFilePath);
        INI.Lock;
        {$ELSE}
        INI := TCryptIniFile.Create(IniFilePath);
        {$ENDIF}
        INI.KeyPhrase := s;
      end;
    end
    else
      ShowMessage('No INI file to show!');
  end;
end;

procedure Tmainform.cmd_ValueExistsClick(Sender: TObject);
begin
  if not INI.SectionExists(edt_Section.Text) then
  begin
    ShowMessage(edt_Section.Text + ' is absent, so nothing to read!');
    Exit;
  end;
  // Dont read IDENT_SECTION
  if edt_Section.Text = IDENT_SECTION then
  begin
    ShowMessage('Switching Encryption mode off to read ' + IDENT_SECTION + 'section');
    rg_Encryption.ItemIndex := 1;
  end;
  if (edt_Section.Text <> 'TestSection') then
    if MessageDlg('Please confirm', 'Are you sure you want to test values from ' +
      edt_Section.Text + '?', mtConfirmation, [mbCancel, mbYes], 0, mbCancel) =
      mrCancel then
    begin
      edt_Section.Text := 'TestSection';
      ShowMessage('OK. Reading default section "TestSection" instead');
    end;

  if INI.ValueExists(edt_Section.Text, edt_Ident.Text) then
    ShowMessage('Key "' + edt_Ident.Text + '" exists')
  else
    ShowMessage('Key "' + edt_Ident.Text + '" is absent');
  if INI.ValueExists(edt_Section.Text, 'Integer') then
    ShowMessage('Key "Integer" exists')
  else
    ShowMessage('Key "Integer" is absent');
end;

procedure Tmainform.cmd_VerifyClick(Sender: TObject);
var
  TRUEFALSE: boolean;
begin
  if not INI.SectionExists(edt_Section.Text) then
  begin
    ShowMessage(edt_Section.Text + ' is absent, so nothing to verify!');
    Exit;
  end;
  // Dont verify IDENT_SECTION
  if edt_Section.Text = IDENT_SECTION then
  begin
    cmd_VerifySectionValues.Click;
    Exit;
  end;
  if (edt_Section.Text <> 'TestSection') then
    if MessageDlg('Please confirm', 'Are you sure you want to verify test values from ' +
      edt_Section.Text + '?', mtConfirmation, [mbCancel, mbYes], 0, mbCancel) =
      mrCancel then
    begin
      edt_Section.Text := 'TestSection';
      ShowMessage('OK. Verifying default section "TestSection" instead');
    end;
  if INI.PlainTextMode = False then
  begin
    TRUEFALSE := True; // Assume success, look for failure
    // Test all the value types one-by-one
    TRUEFALSE := TRUEFALSE and INI.VerifyBool(edt_Section.Text, 'Boolean', True);
    if not INI.VerifyBool(edt_Section.Text, 'Boolean', True) then
      ShowMessage('Boolean failed to verify');
    TRUEFALSE := TRUEFALSE and INI.VerifyFloat(edt_Section.Text, 'Float', 3.142);
    if not INI.VerifyFloat(edt_Section.Text, 'Float', 3.142) then
      ShowMessage('Float failed to verify');
    TRUEFALSE := TRUEFALSE and INI.VerifyDateTime(edt_Section.Text,
      'Date', StrToDate('15/10/2016', 'dd mm yyyy', '/'));
    if not INI.VerifyDateTime(edt_Section.Text, 'Date',
      StrToDate('15/10/2016', 'dd mm yyyy', '/')) then
      ShowMessage('Date failed to verify');
    TRUEFALSE := TRUEFALSE and INI.VerifyInt64(edt_Section.Text, 'Int64', 1000);
    if not INI.VerifyInt64(edt_Section.Text, 'Int64', 1000) then
      ShowMessage('Int64 failed to verify');
    TRUEFALSE := TRUEFALSE and INI.VerifyString(edt_Section.Text,
      edt_Ident.Text, edt_Value.Text);
    if not INI.VerifyString(edt_Section.Text, edt_Ident.Text, edt_Value.Text) then
      ShowMessage('String failed to verify');
    if TRUEFALSE = True then
      ShowMessage('Verify: String,Bool,Float,Date and Int64 types all verified OK')
    else
      ShowMessage('One or more types failed verification');
    // Test the Read/Write/Verify Integer stuff
    if INI.VerifyInteger(edt_Section.Text, 'Integer', StrToInt(edt_Integer.Text)) then
      ShowMessage('VerifyInteger: ' + edt_Integer.Text + ' verified OK')
    else
      ShowMessage('VerifyInteger: ' + edt_Integer.Text + ' failed verification');
  end
  else
    ShowMessage('Verification of regular values only works when PlainTextMode=FALSE');
  // Use the MD5 value from the INI file
  if INI.VerifyIdent('92abf0deecbb25c435bff507a396d92a') then
    ShowMessage('Ident ' + IDENT_SECTION + ' verified OK')
  else
    ShowMessage('Ident ' + IDENT_SECTION + ' failed verification');
end;

procedure Tmainform.cmd_VerifySectionValuesClick(Sender: TObject);
var
  s: string;
begin
  if edt_Section.Text = IDENT_SECTION then
  begin
    if INI.VerifyIdent('92abf0deecbb25c435bff507a396d92a') then
      ShowMessage('Ident ' + IDENT_SECTION + ' verified OK')
    else
      ShowMessage('Ident ' + IDENT_SECTION + ' failed verification');
    Exit;
  end;
  if not INI.SectionExists(edt_Section.Text) then
  begin
    ShowMessage(edt_Section.Text + ' is absent, so nothing to verify!');
    Exit;
  end;

  s := InputBox('Verify Section', 'Please enter your 32-character MD5Hash here',
    sStoredMD5Hash);
  if INI.VerifySectionHash(edt_Section.Text, s) then
  begin
    ShowMessage('Section is verified');
    sStoredMD5Hash := s;
  end
  else
    ShowMessage('MD5Hash value incorrect. Section failed verification');
end;

end.