unit uAuditGames;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtCtrls, ComCtrls, ImgList, IniFiles, StdCtrls, Menus, CommCtrl,
  VirtualTrees, BarMenus, GraphicEx, Buttons, ToolWin, GR32_Image;

type
  pAuditInfo = ^TAuditInfo;
  TAuditInfo = record
    aImageIndex: Integer;
    aGameName,
    aParentGameName,
    aGameTitle,
    aName,
    aCRC32: String;
    aSize: Integer;
    aCorrectName,
    aCorrectCRC32: String;
    aCorrectSize: Integer;
    aMerged: Boolean;
    aROMStatus: String;
    aNotes: String;
    aSystem: Integer;
    aMissingZip: Boolean;
  end;

type
  TFormAudit = class(TForm)
    PopupAuditGames: TBcBarPopupMenu;
    PopupAuditGameAgain: TMenuItem;
    N2: TMenuItem;
    N8: TMenuItem;
    PopupShowOnlyBadGames: TMenuItem;
    N1: TMenuItem;
    SaveAuditDialog: TSaveDialog;
    PopupSaveResultstoHTMLFile: TMenuItem;
    PopupSaveBadGamestoHTMLFile: TMenuItem;
    AuditTree: TVirtualStringTree;
    PopupToggleExpandedClosed: TMenuItem;
    PopupSaveBadListLinksDownload: TMenuItem;
    PopupDownloadAddress: TMenuItem;
    PopupShowIconsLegend: TMenuItem;
    PopupColumnsEditor: TMenuItem;
    ImageListResults: TImageList;
    ImageListSmallIcons: TImageList;
    StatusBarPanel: TCoolBar;
    ToolBarStatus: TToolBar;
    SB_TotalGames: TToolButton;
    SB_GoodGames: TToolButton;
    SB_GamesWithErrors: TToolButton;
    SB_BadGames: TToolButton;
    SB_InfoMessages: TToolButton;
    TopImage: TImage32;
    PanelButtons: TPanel;
    ButtonClose: TButton;
    ButtonCancel: TButton;
    LabelGameDescription: TLabel;
    procedure ButtonCloseClick(Sender: TObject);
    procedure ButtonCancelClick(Sender: TObject);
    procedure PopupShowOnlyBadGamesClick(Sender: TObject);
    procedure PopupAuditGameAgainClick(Sender: TObject);
    procedure PopupSaveResultstoHTMLFileClick(Sender: TObject);
    procedure FormKeyUp(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure AuditTreeFreeNode(Sender: TBaseVirtualTree;
      Node: PVirtualNode);
    procedure AuditTreeGetImageIndex(Sender: TBaseVirtualTree;
      Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex;
      var Ghosted: Boolean; var ImageIndex: Integer);
    procedure AuditTreeHeaderClick(Sender: TVTHeader; Column: TColumnIndex;
      Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
    procedure FormCreate(Sender: TObject);
    procedure PopupToggleExpandedClosedClick(Sender: TObject);
    procedure AuditTreeCompareNodes(Sender: TBaseVirtualTree; Node1,
      Node2: PVirtualNode; Column: TColumnIndex; var Result: Integer);
    procedure AuditTreeIncrementalSearch(Sender: TBaseVirtualTree;
      Node: PVirtualNode; const SearchText: WideString;
      var Result: Integer);
    procedure AuditTreeChange(Sender: TBaseVirtualTree;
      Node: PVirtualNode);
    procedure PopupDownloadAddressClick(Sender: TObject);
    procedure FormActivate(Sender: TObject);
    procedure PopupShowIconsLegendClick(Sender: TObject);
    procedure PopupAuditGamesPopup(Sender: TObject);
    procedure PopupColumnsEditorClick(Sender: TObject);
    procedure AuditTreeGetHint(Sender: TBaseVirtualTree;
      Node: PVirtualNode; Column: TColumnIndex;
      var LineBreakStyle: TVTTooltipLineBreakStyle;
      var HintText: WideString);
    procedure AuditTreeGetText(Sender: TBaseVirtualTree;
      Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
      var CellText: WideString);
    procedure PopupAuditGamesMeasureMenuItem(Sender: TObject;
      AMenuItem: TMenuItem; ACanvas: TCanvas; var Width, Height: Integer;
      ABarVisible: Boolean; var DefaultMeasure: Boolean);
    procedure FormShow(Sender: TObject);
    procedure AuditTreePaintText(Sender: TBaseVirtualTree;
      const TargetCanvas: TCanvas; Node: PVirtualNode;
      Column: TColumnIndex; TextType: TVSTTextType);
  private
    ListROMName, ListROMSize, ListROMCRC: String;
    ROMsList, ZipListFull: THashedStringList;
    ROMStatus: Integer;
    GameOk, Auditing: Boolean;
    aSelectedNode: PVirtualNode;

    procedure TestROMSizeCRC(ListName, ZIPName, ListSize, ZIPSize, ListCRC, ZIPCRC, FileStatus: String; DataROM: pAuditInfo);
    function  MountHTMLLine(StringData: String): String;
    function  SetGameName(DataHolder: pAuditInfo): String;

    procedure WindowInfo(ReadInfo: Boolean);
    procedure ClearSB_MessagesButton;

    { Private declarations }
  public
    ROMsName, ROMsSize, ROMsCRC, ROMsStatus, gameFilesList: THashedStringList;
    GameName, ParentGameName, GameTitle, LabelStatusFormat: String;
    TotalGames, MissingGames, GoodGames, BadGames, CurrentGame, AuditMode: Integer;
    function  CreateMD5Checksum(DiskFileName, DiskMD5: String; out RealMD5Value: String): Boolean;
    function  AuditGame(GameName, ParentGameName, GameTitle: String): Boolean;
    procedure Audit;
    procedure UpdateColumns;
    { Public declarations }
  end;

var
  FormAudit: TFormAudit;

implementation

uses uMain, uStatus, uCommon, uFilesUtil, uAuditLegend, uAuditColumnsEditor;

{$R *.DFM}

procedure TFormAudit.ClearSB_MessagesButton;
begin
  SB_InfoMessages.Caption:= '';
end;

function TFormAudit.CreateMD5Checksum(DiskFileName, DiskMD5: String; out RealMD5Value: String): Boolean;
var
  TestFile: TFileStream;
  FileName, MD5ChecksumFound: String;
  Buffer: array [0..15] of Byte;
  Loop: ShortInt;
const
  HexTable: String = '0123456789ABCDEF';

  function ByteToHex(Value: Byte): String;
  var
    zwVal: Byte;
  begin
    zwVal := (Value and $0F); //first mask (Lo) Byte of Value
    Result:= HexTable[zwVal + 1]; //from HexTable to Result
    zwVal := (Value and $F0) shr 4; //second mask (Hi) Byte of Value and 4Bits to the right shift
    Result:= HexTable[zwVal + 1] + Result; //add from HexTable to Result
  end;

begin
  FileName:= FormMain.SearchCHDFolder(DiskFilename, False, FormMain.SystemID_Number);
  Result:= (FileName <> '');
  if Result then
     begin
       try
         TestFile:= TFileStream.Create(FileName, (fmOpenRead or fmShareDenyNone));
         TestFile.Seek(44, soFromBeginning); // go to byte 44 (begining of MD5 checksum on .chd files)
         TestFile.ReadBuffer(Buffer, 16); // read 16 bytes (size of MD5 checksum on .chd files)
       except
       end;
       FreeAndNil(TestFile);
       MD5ChecksumFound:= '';
       for loop:=0 to 15 do
         MD5ChecksumFound:= MD5ChecksumFound+LowerCase(ByteToHex(Buffer[Loop])); // put all hexa values together

       RealMD5Value:= MD5ChecksumFound;
       Result:= (MD5ChecksumFound = DiskMD5);
     end;
end;

procedure TFormAudit.TestROMSizeCRC(ListName, ZIPName, ListSize, ZIPSize, ListCRC, ZIPCRC, FileStatus: String; DataROM: pAuditInfo);
var
  SizeOk, CRCOk, DiskImage: Boolean;
  CRCStatus, ZipIndex: Integer;
begin
  DiskImage:= (Length(ListCRC) > 8);
  case DiskImage of
    True:
      begin
        ROMStatus:= 28; // game ok merged
        SizeOk:= True;
        CRCOk:= CreateMD5Checksum(ZIPName, ListCRC, ZIPCRC);
        DataROM.aSize:= StrToInt(ListSize);
      end;
    False:
      begin
        ROMStatus:= 27;
        SizeOk:= ZIPSize = ListSize;
        CRCOk:= ZIPCRC = ListCRC;
        DataROM.aSize:= StrToInt(ZIPSize);
      end;
  end;
  CRCStatus:= -1;
  if FileStatus = 'nodump' then
     begin
       if (not CRCOk) and (ListCRC = '') then
          CRCOk:= True;
       CRCStatus:= 1 // No Good Dump Known
     end
  else
  if FileStatus = 'baddump' then
     CRCStatus:= 2 // ROM Need Redump
  else
  if not CRCOk then
     CRCStatus:= 0; // CRC32 / MD5 is wrong

  DataROM.aName:= ZIPName;
  if ZIPName <> ListName then
     DataROM.aCorrectName:= ListName;

  DataROM.aCRC32:= ZIPCRC;

  case SizeOk of
    False:
      begin
        DataROM.aCorrectSize:= StrToInt(ListSize);
        case DiskImage of
          True : ROMStatus:= 13; // bad chd
          False: ROMStatus:= 12; // bad rom
        end;
        GameOk:= False;
      end;
    True:
      begin
        case DiskImage of
          True : ROMStatus:= 01; // good chd
          False: ROMStatus:= 00; // good rom
        end;
      end;
  end;

  case CRCStatus of
    0: begin
         if not CRCOk then
            begin
              DataROM.aCorrectCRC32:= ListCRC;
              case DiskImage of
                True : ROMStatus:= 13; // bad chd
                False: ROMStatus:= 12; // bad rom
              end;
              GameOk:= False;
            end;
       end;
    1: begin
         case CRCOk of
           True:
             begin
               case DiskImage of
                 True:
                   begin
                     if ROMStatus <> 13 then // different than bad chd
                        ROMStatus:= 03 // good chd no dump
                     else
                        ROMStatus:= 15; // bad chd no dump
                   end;
                 False:
                   begin
                     if ROMStatus <> 12 then // different than bad rom
                        ROMStatus:= 02 // good rom no dump
                     else
                        ROMStatus:= 14; // bad rom no dump
                   end;
               end;
             end;
           False:
             begin
               case DiskImage of
                 True : ROMStatus:= 15; // bad chc no dump
                 False: ROMStatus:= 14; // bad rom no dump
               end;
             end;
         end;
       end;
    2: begin
         case CRCOk of
           True:
             begin
               case DiskImage of
                 True:
                   begin
                     if ROMStatus <> 13 then // different than bad chd
                        ROMStatus:= 05 // good chd redump
                     else
                        ROMStatus:= 17; // bad chd redump
                   end;
                 False:
                   begin
                     if ROMStatus <> 12 then // different than bad rom
                        ROMStatus:= 04 // good rom redump
                     else
                        ROMStatus:= 16; // bad rom redump
                   end;
                 end;
             end;
           False:
             begin
               case DiskImage of
                 True : ROMStatus:= 17; // bad chd redump
                 False: ROMStatus:= 16; // bad rom redump
               end;
             end;
         end;
       end;
  end;
  if Assigned(ZipListFull) and (ZipListFull.Count > 0) then
     begin
       ZipIndex:= ZipListFull.IndexOf(ZIPName);
       if ZipIndex = -1 then
          ZipIndex:= ZipListFull.IndexOf(ListName);
       if ZipIndex <> -1 then
          ZipListFull.Delete(ZipIndex);
     end;
end;

function TFormAudit.AuditGame(GameName, ParentGameName, GameTitle: String): Boolean;
var
  Loop, ROMIndex, ROMsCount, NotFoundROMsCount, CRCNumber: Integer;
  FileNotFound, ParentFileNotFound, OriginalGame, DiskImage, ZipOK, ParentZipOk: Boolean;
  ZIPFileName, ParentZIPFileName, FixedCRCHex: String;

  Node, ChildNode: PVirtualNode;
  Data, ChildData: pAuditInfo;

  function GetFixedCRC: String;
  begin
    case DiskImage of
      False:
        begin
          if ListROMCRC <> '' then
             begin
               CRCNumber:= StrToInt('$'+ListROMCRC);
               FixedCRCHex:= LowerCase(Format('%.8x', [CRCNumber]));
             end
          else
             FixedCRCHex:= '';
        end;
      True: FixedCRCHex:= LowerCase(ListROMCRC);
    end;
  end;

  function AddNote(destData: pAuditInfo; NoteString: String): Boolean;
  begin
    if destData.aNotes <> '' then
       destData.aNotes:= destData.aNotes+', '+NoteString
    else
       destData.aNotes:= NoteString;
  end;

  procedure ScanCHDFileMD5;
  var
    CHDFileName: String;
  begin
    CHDFileName:= FormMain.SearchCHDFolder(ROMsName[Loop], False, FormMain.SystemID_Number);
    if CHDFileName <> '' then
       begin
         // I need to read chd_md5.ini file for the full file MD5 checksum
         if ListROMSize = '0' then
            ListROMSize:= IntToStr(GetFileSize(CHDFileName));

         TestROMSizeCRC(ListROMName, ROMsName[Loop], ListROMSize, ROMsSize[Loop], ListROMCRC, ROMsStatus[Loop], '-create', ChildData);
         Inc(ROMsCount);
       end
    else
       begin
         ChildData.aName:= ROMsName[Loop];
         ChildData.aSize:= StrToInt(ListROMSize);
         ChildData.aCRC32:= ROMsCRC[Loop];
         ROMStatus:= 26; // missing chd
         AddNote(ChildData, ROMsName[Loop]+' not found');
         GameOk:= False;
         Inc(ROMsCount);
         //Inc(NotFoundROMsCount);
       end;
  end;

begin
  Application.ProcessMessages;
  OriginalGame:= not ((ParentGameName <> '') and (ParentGameName <> GameName));
  ZIPFileName:= FormMain.SearchZIPFolder(GameName);
  FileNotFound:= True;
  ParentFileNotFound:= True;
  Result:= False;
  ROMsCount:= 0;
  NotFoundROMsCount:= 0;
  GameOk:= True;
  ClearSB_MessagesButton;
  //ROMStatus:=25; // missing rom

  if Assigned(FormMain.ListROMsName) then
     FormMain.ListROMsName.Clear;
  if Assigned(FormMain.ListROMsSize) then
     FormMain.ListROMsSize.Clear;
  if Assigned(FormMain.ListROMsCRC) then
     FormMain.ListROMsCRC.Clear;

  if Assigned(FormMain.ParentListROMsName) then
     FormMain.ParentListROMsName.Clear;
  if Assigned(FormMain.ParentListROMsSize) then
     FormMain.ParentListROMsSize.Clear;
  if Assigned(FormMain.ParentListROMsCRC) then
     FormMain.ParentListROMsCRC.Clear;

  if Assigned(FormMain.ListROMsNameFullPath) then
     FormMain.ListROMsNameFullPath.Clear;

  if Assigned(FormMain.ParentListROMsNameFullPath) then
     FormMain.ParentListROMsNameFullPath.Clear;

  ZipOk:= True;
  ParentZipOk:= True;

  if ZIPFileName <> '' then
     begin
        ZipOk:= FormMain.GetContents(ZIPFileName, True, False);
        if ZipOk and (FormMain.ListROMsName.Count > 0) and Assigned(ZipListFull) then
           ZipListFull.AddStrings(FormMain.ListROMsName);
        FileNotFound:= False;
        if not OriginalGame then
           begin
             ParentZIPFileName:= FormMain.SearchZIPFolder(ParentGameName);
             if ParentZIPFileName <> '' then
                begin
                   ParentFileNotFound:= False;
                   ParentZipOk:= FormMain.GetContents(ParentZIPFileName, False, True);
                   OriginalGame:= False;
                end;
           end;
     end
  else
     begin
       case OriginalGame of
         False:
           begin
             ParentZIPFileName:= FormMain.SearchZIPFolder(ParentGameName);
             if ParentZIPFileName <> '' then
                begin
                   ParentFileNotFound:= False;
                   ParentZipOk:= FormMain.GetContents(ParentZIPFileName, True, True); // Original Line
                   OriginalGame:= False;
                end
             else
                begin
                  GameOk:= False;
                  Inc(MissingGames);
                end;
           end;
         True:
           begin
             GameOk:= False;
             Inc(MissingGames);
           end;
       end;
     end;

  case OriginalGame of
    True:
      begin
        Node:= AuditTree.AddChild(nil);
        Data:= AuditTree.GetNodeData(Node);
        AuditTree.Expanded[Node]:= True;
        Data.aParentGameName:= ParentGameName;
        Data.aGameName:= GameName;
        Data.aGameTitle:= GameTitle;
        case FileNotFound of
          True : AddNote(Data, GameName+'.zip not found');
          False:
            begin
              if not ZipOK then
                 AddNote(Data, GameName+'.zip damaged');
            end;
        end;
        Data.aSystem:= FormMain.SystemID_Number;
      end;
    False:
      begin
        case ParentFileNotFound of
          True:
            begin
              Node:= AuditTree.AddChild(nil);
              Data:= AuditTree.GetNodeData(Node);
              AuditTree.Expanded[Node]:= True;
              Data.aParentGameName:= ParentGameName;
              Data.aGameName:= GameName;
              Data.aGameTitle:= GameTitle;

              case FileNotFound of
                True : AddNote(Data, GameName+'.zip and '+ParentGameName+'.zip not found');
                False:
                  begin
                    case ZipOK of
                      True : AddNote(Data, ParentGameName+'.zip not found');
                      False: AddNote(Data, GameName+'.zip damaged and '+ParentGameName+'.zip not found');
                    end;
                  end;
              end;
              Data.aSystem:= FormMain.SystemID_Number;
            end;
          False:
            begin
              Node:= AuditTree.AddChild(nil);
              Data:= AuditTree.GetNodeData(Node);
              AuditTree.Expanded[Node]:= True;
              Data.aParentGameName:= ParentGameName;
              Data.aGameName:= GameName;
              Data.aGameTitle:= GameTitle;

              case FileNotFound of
                True:
                  begin
                    case ParentZipOk of
                      True : AddNote(Data, GameName+'.zip not found');
                      False: AddNote(Data, GameName+'.zip not found and '+ParentGameName+'.zip damaged');
                    end;
                  end;
                False:
                  begin
                    if (not ZipOk) and ParentZipOk then
                       AddNote(Data, GameName+'.zip damaged')
                    else
                    if ZipOk and (not ParentZipOk) then
                       AddNote(Data, ParentGameName+'.zip damaged')
                    else
                    if (not ZipOk) and (not ParentZipOk) then
                       AddNote(Data, GameName+'.zip and '+ParentGameName+'.zip damaged');
                  end;
              end;
              Data.aSystem:= FormMain.SystemID_Number;
            end;
        end;
      end;
  end;

  for Loop:=0 to ROMsName.Count -1 do
  begin
    ROMStatus:= 25; // missing rom
    ChildNode:= AuditTree.AddChild(Node);
    ChildData:= AuditTree.GetNodeData(ChildNode);
    ChildData.aGameTitle:= '';
    ChildData.aSystem:= -1;
    if ((not FileNotFound) or (not ParentFileNotFound) or (LowerCase(ExtractFileExt(ROMsName[Loop])) = '.chd')) then
       begin
         ListROMName:= ROMsName[Loop];
         ListROMSize:= ROMsSize[Loop];
         ListROMCRC:= ROMsCRC[Loop];
         DiskImage:= (Length(ListROMCRC) > 8);
         case OriginalGame of
           True:
             begin
               ROMIndex:= FormMain.ListROMsName.IndexOf(ROMsName[Loop]);
               if ROMIndex <> -1 then
                  begin
                    // ROM Name is found will test Size and CRC
                    TestROMSizeCRC(ListROMName, FormMain.ListROMsName[ROMIndex], ListROMSize, FormMain.ListROMsSize[ROMIndex], ListROMCRC, FormMain.ListROMsCRC[ROMIndex], ROMsStatus[Loop], ChildData);
                    Inc(ROMsCount);
                  end
               else
                  begin
                    // will try to search for the correct CRC number
                    // ListROMCRC - mame_roms.el
                    // FormMain.ListROMsCRC - .zip file
                    if ROMsStatus[Loop] <> 'nodump' then
                       begin
                         GetFixedCRC;
                         case DiskImage of
                           False:
                             begin
                               ROMIndex:= FormMain.ListROMsCRC.IndexOf(FixedCRCHex);
                                if ROMIndex > -1 then
                                   begin
                                     TestROMSizeCRC(ROMsName[Loop], FormMain.ListROMsName[ROMIndex],
                                                    ListROMSize, FormMain.ListROMsSize[ROMIndex],
                                                    ListROMCRC, FormMain.ListROMsCRC[ROMIndex], ROMsStatus[Loop], ChildData);
                                     Inc(ROMsCount);
                                     // found crc number
                                   end
                                else
                                   begin
                                     ChildData.aName:= ROMsName[Loop];
                                     ChildData.aSize:= StrToInt(ROMsSize[Loop]);
                                     ChildData.aCRC32:= ROMsCRC[Loop];
                                     case DiskImage of
                                       True : ROMStatus:= 26; // missing chd
                                       False: ROMStatus:= 25; // missing rom
                                     end;
                                     GameOk:= False;
                                     Inc(NotFoundROMsCount);
                                     AddNote(ChildData, ROMsName[Loop]+' not found');
                                   end;
                             end;
                           True: ScanCHDFileMD5;
                         end;
                       end
                    else
                       begin
                         ChildData.aName:= ROMsName[Loop];
                         ChildData.aSize:= StrToInt(ROMsSize[Loop]);
                         ChildData.aCRC32:= ROMsCRC[Loop];
                         AddNote(ChildData, 'No Good Dump Known');
                         Inc(ROMsCount);
                       end;
                  end;
             end;
           False:
             begin
               ROMIndex:= FormMain.ListROMsName.IndexOf(ROMsName[Loop]);
               if ROMIndex <> -1 then
                  begin
                    // ROM Name is found will test Size and CRC
                    TestROMSizeCRC(ListROMName, FormMain.ListROMsName[ROMIndex], ListROMSize, FormMain.ListROMsSize[ROMIndex], ListROMCRC, FormMain.ListROMsCRC[ROMIndex], ROMsStatus[Loop], ChildData);
                    Inc(ROMsCount);
                  end
               else
                  begin
                    // fixes... file needs to be search in the parent game first,
                    // or the CRC searching will not audit correctly
                    ROMIndex:= FormMain.ParentListROMsName.IndexOf(ROMsName[Loop]);
                    if ROMIndex <> -1 then
                       begin
                         // ROM Name is found will test Size and CRC
                         TestROMSizeCRC(ListROMName, FormMain.ParentListROMsName[ROMIndex], ListROMSize, FormMain.ParentListROMsSize[ROMIndex], ListROMCRC, FormMain.ParentListROMsCRC[ROMIndex], ROMsStatus[Loop], ChildData);
                         Inc(ROMsCount);
                         case ROMStatus of
                           00: ROMStatus:= 06; // good rom:= good rom merged
                           02: ROMStatus:= 08; // good rom no dump:= good rom no dump merged
                           04: ROMStatus:= 10; // good rom redump:= good rom redump merged
                           12: ROMStatus:= 18; // bad rom:= bad rom merged
                           14: ROMStatus:= 20; // bad rom no dump:= bad rom no dump merged
                           16: ROMStatus:= 22; // bad rom redump:= bad rom redump merged
                         end;
                       end
                    else
                       begin
                         // will search for the crc number
                         if ROMsStatus[Loop] <> 'nodump' then
                            begin
                              GetFixedCRC;
                              case DiskImage of
                                False:
                                  begin
                                    ROMIndex:= FormMain.ListROMsCRC.IndexOf(FixedCRCHex);
                                    if ROMIndex > -1 then
                                       begin
                                         TestROMSizeCRC(ROMsName[Loop], FormMain.ListROMsName[ROMIndex],
                                                        ListROMSize, FormMain.ListROMsSize[ROMIndex],
                                                        ListROMCRC, FormMain.ListROMsCRC[ROMIndex], ROMsStatus[Loop], ChildData);
                                         Inc(ROMsCount);
                                         // found crc number
                                       end
                                    else
                                       begin
                                         // merged file... won't be renamed if file is clone merged into master .zip
                                         ROMIndex:= FormMain.ParentListROMsCRC.IndexOf(FixedCRCHex);
                                         if ROMIndex > -1 then
                                            begin
                                              TestROMSizeCRC(ROMsName[Loop], FormMain.ParentListROMsName[ROMIndex],
                                                             ListROMSize, FormMain.ParentListROMsSize[ROMIndex],
                                                             ListROMCRC, FormMain.ParentListROMsCRC[ROMIndex], ROMsStatus[Loop], ChildData);
                                              Inc(ROMsCount);
                                              // found crc number
                                              case ROMStatus of
                                                00: ROMStatus:= 06; // good rom:= good rom merged
                                                02: ROMStatus:= 08; // good rom no dump:= good rom no dump merged
                                                04: ROMStatus:= 10; // good rom redump:= good rom redump merged
                                                12: ROMStatus:= 18; // bad rom:= bad rom merged
                                                14: ROMStatus:= 20; // bad rom no dump:= bad rom no dump merged
                                                16: ROMStatus:= 22; // bad rom redump:= bad rom redump merged
                                              end;
                                            end
                                         else
                                            begin
                                              ChildData.aName:= ROMsName[Loop];
                                              ChildData.aSize:= StrToInt(ROMsSize[Loop]);
                                              ChildData.aCRC32:= ROMsCRC[Loop];
                                              case DiskImage of
                                                True : ROMStatus:= 26; // missing chd
                                                False: ROMStatus:= 25; // missing rom
                                              end;
                                              GameOk:= False;
                                              Inc(NotFoundROMsCount);
                                              AddNote(ChildData, ROMsName[Loop]+' not found');
                                            end;
                                       end;
                                  end;
                                True: ScanCHDFileMD5;
                              end;
                            end
                         else
                            begin
                              ChildData.aName:= ROMsName[Loop];
                              ChildData.aSize:= StrToInt(ROMsSize[Loop]);
                              ChildData.aCRC32:= ROMsCRC[Loop];
                              AddNote(ChildData, 'No Good Dump Known');
                              Inc(ROMsCount);
                            end;
                       end;
                  end;
             end;
         end;
         // here is the place that I would have to see if the file is OK, and add it to the
         // valid files temporary list
         // if CheckUnneededFiles then
         //   begin
              //if (Pos('correct size:', ResultLine) = 0) or (Pos('correct crc:', ResultLine) = 0) then
                 // file is OK, can be added
              //else
                 // file is not OK, don't add it to the list
         //   end;
       end
    else
       begin
         ChildData.aName:= ROMsName[Loop];
         ChildData.aSize:= StrToInt(ROMsSize[Loop]);
         ChildData.aCRC32:= ROMsCRC[Loop];
         case DiskImage of
           True : ROMStatus:= 26; // missing chd
           False: ROMStatus:= 25; // missing rom
         end;
         GameOk:= False;
         Inc(NotFoundROMsCount);
         AddNote(ChildData, ROMsName[Loop]+' not found');
       end;
    ChildData.aImageIndex:= ROMStatus;
    ChildData.aROMStatus:= ROMsStatus[Loop];
  end;
  Data.aMerged:= ((ROMsCount = ROMsName.Count) and (not OriginalGame) and (FileNotFound) and (not ParentFileNotFound));

  Result:= GameOk;
  case GameOk of
    True:
      begin
        case Data.aMerged of
          True : Data.aImageIndex:= 28; // game ok merged
          False: Data.aImageIndex:= 27; // game ok
        end;
      end;
    False:
      begin
        case Data.aMerged of
          True : Data.aImageIndex:= 30; // game bad merged
          False: Data.aImageIndex:= 29; // game bad
        end;
      end;
  end;

  if FormMain.ZipForge.Active then
     FormMain.ZipForge.CloseArchive;
  // here would be the place to scan the file to see if .zip file has unneded files
  if Assigned(ZipListFull) and (ZipListFull.Count > 0) then
     begin
       for Loop:= 0 to ZipListFull.Count-1 do
       begin
         ChildNode:= AuditTree.AddChild(Node);
         ChildData:= AuditTree.GetNodeData(ChildNode);
         ChildData.aImageIndex:= 24; // not used rom
         ChildData.aName:= ZipListFull[Loop];
         ChildData.aMerged:= False;
         ChildData.aGameTitle:= '';
         ChildData.aSystem:= -1;
         AddNote(ChildData, ZipListFull[Loop]+' not needed');
       end;
       ZipListFull.Clear;
     end;

  if not Data.aMerged then
     begin
       case OriginalGame of
         True : Data.aMissingZip:= FileNotFound;
         False: Data.aMissingZip:= FileNotFound or ParentFileNotFound;
       end;
     end;

  if ((NotFoundROMsCount = ROMsName.Count) and (not FileNotFound) and (not ParentFileNotFound)) then
     Inc(MissingGames);

  if ((not OriginalGame) and (FileNotFound) and (not ParentFileNotFound)) then
     begin
       if ROMsCount <> ROMsName.Count then
          Inc(MissingGames);
     end;
  ListROMName:= '';
  ListROMSize:= '';
  ListROMCRC:= '';
  if (Data.aImageIndex in [29, 30]) or (AuditMode = 0) then
     AuditTree.Expanded[Node]:= True; // game bad, game bad merged
end;

procedure TFormAudit.ButtonCloseClick(Sender: TObject);
begin
  Close;
end;

procedure TFormAudit.Audit;
var
  Result, romsFile, romsFolder: String;
  mGamesList: THashedStringList;
  GameROMsList: TextFile;
  Loop{, StartClock}: Integer;
  ROMsFull: TMemIniFile;
  gameFilesList: THashedStringList;

  procedure UpdateStatusBar;
  begin
    SB_TotalGames.Caption:= Format(LabelStatusFormat, [CurrentGame, TotalGames]);
    SB_GoodGames.Caption:= Format('%.6d', [GoodGames]);
    SB_GamesWithErrors.Caption:= Format('%.6d', [MissingGames]);
    SB_BadGames.Caption:= Format('%.6d', [BadGames]);
  end;

begin
  //StartClock:= GetTickCount;
  Auditing:= True;
  if LabelStatusFormat = '' then
     LabelStatusFormat:= '%.6d of %.6d';
  SetCurrentDir(ExtractFilePath(FormMain.EmulatorFile[FormMain.SystemID_Number]));

  AuditTree.BeginUpdate;
  PopupToggleExpandedClosed.Tag:= Ord(Boolean(AuditMode = 0));
  if AuditTree.TotalCount = 0 then
     begin
       MissingGames:= 0;
       GoodGames:= 0;
       BadGames:= 0;
       LabelGameDescription.Visible:= (AuditMode = 0);
     end;

  romsFile:= GetSystemROMFileName(FormMain.SystemID_Number);
  romsFolder:= GetMachineTypeGamesFolder;
  case AuditMode of
    0: //Single Audit
      begin
        if not Assigned(ROMsList) then
           begin
             if FileExists(FormMain.FrontendPath+romsFolder+romsFile) then
                begin
                  ROMsFull:= TMemIniFile.Create(FormMain.FrontendPath+romsFolder+romsFile);
                  ROMsList:= THashedStringList.Create;
                  ROMsFull.ReadSectionValues(GameName, ROMsList);
                  FreeAndNil(ROMsFull);
                  FormMain.GetGameROMs(ROMsList, True);
                end;
           end;

        if AuditTree.TotalCount = 0 then
           begin
             TotalGames:= 0;
             CurrentGame:= 0;
           end;

        ButtonCancel.Enabled:= False;
        case AuditGame(GameName, ParentGameName, GameTitle) of
          True : Inc(GoodGames);
          False: Inc(BadGames);
        end;
        Inc(CurrentGame);
        Inc(TotalGames);
        UpdateStatusBar;
        FreeAndNil(ROMsList);
      end;
    1, 2: // Audit All Games
      begin
        ButtonClose.Enabled:= False;
        LabelGameDescription.Caption:= '';

        mGamesList:= THashedStringList.Create;
        GetGamesList(mGamesList, FormMain.SystemID_Number);
           
        case AuditMode of
          1: begin
               TotalGames:= mGamesList.Count;
               FreeAndNil(mGamesList);
             end;
          2: begin
               mGamesList.BeginUpdate;
               for Loop:=mGamesList.Count-1 downto 0 do
               begin
                 FormMain.GetROMFields(mGamesList, Loop);
                 case FormMain.SystemID_Number of
                   1: // MAME
                     begin
                       if FormMain.mROMIdentification in [03..05] then
                          mGamesList.Delete(Loop)
                       else
                          mGamesList[Loop]:= FormMain.mName;
                     end;
                   4: // AGEMAME
                     begin
                       if FormMain.mROMIdentification in [17..19] then
                          mGamesList.Delete(Loop)
                       else
                          mGamesList[Loop]:= FormMain.mName;
                     end;
                   5: // PinMAME
                     begin
                       if FormMain.mROMIdentification in [23..25] then
                          mGamesList.Delete(Loop)
                       else
                          mGamesList[Loop]:= FormMain.mName;
                     end;
                   6: // HazeMD
                     begin
                       if FormMain.mROMIdentification in [28, 29] then
                          mGamesList.Delete(Loop)
                       else
                          mGamesList[Loop]:= FormMain.mName;
                     end;
                 end;
               end;
               mGamesList.EndUpdate;
               TotalGames:= mGamesList.Count;
               if TotalGames = 0 then
                  UpdateStatusBar;
             end;
        end;
        Application.ProcessMessages;
        LabelGameDescription.Caption:= 'Verifying '+
                             FormMain.GetSystemIniSection(FormMain.SystemID_Number)+' games.';
        AssignFile(GameROMsList, FormMain.FrontendPath+romsFolder+romsFile);
        FileMode:= 0;
        Reset(GameROMsList);
        gameFilesList:= THashedStringList.Create;
        while not Eof(GameROMsList) do
        begin
          ReadLn(GameROMsList, Result);
          if Result[1] = '[' then
             begin
               GameName:= '';
               ParentGameName:= '';
               GameTitle:= '';
               GameName:= Copy(Result, 2, Length(Result)-2);
               ROMsName.BeginUpdate;
               ROMsSize.BeginUpdate;
               ROMsCRC.BeginUpdate;
               ROMsStatus.BeginUpdate;
               gameFilesList.BeginUpdate;
               ROMsName.Clear;
               ROMsSize.Clear;
               ROMsCRC.Clear;
               ROMsStatus.Clear;
               gameFilesList.Clear;
             end
          else
          if Pos('cloneof=', Result) > 0 then
             ParentGameName:= Copy(Result, 9, Length(Result))
          else
          if Pos('gmtitle=', Result) > 0 then
             GameTitle:= Copy(Result, 9, Length(Result))
          else
          if ((Result = ' ') or (Eof(GameROMsList))) then
             begin
               // end of game files list
               if Result <> '' then
                  gameFilesList.Add(Result);
               ROMsName.EndUpdate;
               ROMsSize.EndUpdate;
               ROMsCRC.EndUpdate;
               ROMsStatus.EndUpdate;
               gameFilesList.EndUpdate;
               FormMain.GetGameROMs(gameFilesList, True);
               case AuditMode of
                 1:
                   begin
                     case AuditGame(GameName, ParentGameName, GameTitle) of
                       True : Inc(GoodGames);
                       False: Inc(BadGames);
                     end;
                     Inc(CurrentGame);
                     UpdateStatusBar;
                   end;
                 2:
                   begin
                     if mGamesList.IndexOf(GameName) > -1 then
                        begin
                          case AuditGame(GameName, ParentGameName, GameTitle) of
                            True : Inc(GoodGames);
                            False: Inc(BadGames);
                          end;
                          Inc(CurrentGame);
                          UpdateStatusBar;
                        end;
                   end;
               end;
               GameName:= '';
               ParentGameName:= '';
               GameTitle:= '';
             end
          else
             gameFilesList.Add(Result);

          Application.ProcessMessages;
          if ButtonCancel.Tag = 1 then
             Break;
        end;
        LabelGameDescription.Caption:= '';
        ButtonCancel.Tag:= 0;
        CloseFile(GameROMsList);
        FreeAndNil(mGamesList);
        FreeAndNil(gameFilesList);

        ButtonClose.Enabled:= True;
        ButtonCancel.Enabled:= False;
        ClearSB_MessagesButton;
      end;
  end;
  if AuditTree.TotalCount > 0 then
     AuditTree.SortTree(AuditTree.Tag, sdAscending, False);
  AuditTree.EndUpdate;
  FreeAndNil(ROMsName);
  FreeAndNil(ROMsSize);
  FreeAndNil(ROMsCRC);
  FreeAndNil(ROMsStatus);

  FreeAndNil(FormMain.ListROMsName);
  FreeAndNil(FormMain.ListROMsSize);
  FreeAndNil(FormMain.ListROMsCRC);
  FreeAndNil(FormMain.ListROMsNameFullPath);
  FreeAndNil(FormMain.ParentListROMsName);
  FreeAndNil(FormMain.ParentListROMsSize);
  FreeAndNil(FormMain.ParentListROMsCRC);
  FreeAndNil(FormMain.ParentListROMsNameFullPath);
  SetCurrentDir(FormMain.FrontendPath);
  FormMain.ShowZipErrors;
  Auditing:= False;
  //ShowMessage(IntToStr(GetTickCount-StartClock)); //Time in milliseconds
end;

procedure TFormAudit.ButtonCancelClick(Sender: TObject);
begin
  ButtonCancel.Tag:= 1;
end;

procedure TFormAudit.PopupShowOnlyBadGamesClick(Sender: TObject);
var
  Node: PVirtualNode;
  Data: pAuditInfo;
begin
  AuditTree.BeginUpdate;
  Node:= AuditTree.GetFirst;
  repeat
    Data:= AuditTree.GetNodeData(Node);
    if Data.aImageIndex in [27, 28] then // if game ok, game ok merged
       AuditTree.IsVisible[Node]:= not PopupShowOnlyBadGames.Checked;
    Node:= AuditTree.GetNext(Node);
  until (not Assigned(Node));
  AuditTree.EndUpdate;
end;

procedure TFormAudit.PopupAuditGameAgainClick(Sender: TObject);
var
  Data: pAuditInfo;
begin
  if aSelectedNode = nil then
     Exit;
  Data:= AuditTree.GetNodeData(aSelectedNode);
  ParentGameName:= Data.aParentGameName;
  GameName:= Data.aGameName;
  FormAudit.GameTitle:= Data.aGameTitle;
  AuditTree.BeginUpdate;
  AuditTree.DeleteSelectedNodes;
  AuditTree.EndUpdate;
  Application.ProcessMessages;
  ROMsName:= THashedStringList.Create;
  ROMsSize:= THashedStringList.Create;
  ROMsCRC:= THashedStringList.Create;
  ROMsStatus:= THashedStringList.Create;

  FormMain.ListROMsName:= THashedStringList.Create;
  FormMain.ListROMsSize:= THashedStringList.Create;
  FormMain.ListROMsCRC:= THashedStringList.Create;
  FormMain.ListROMsNameFullPath:= THashedStringList.Create;
  FormMain.ParentListROMsName:= THashedStringList.Create;
  FormMain.ParentListROMsSize:= THashedStringList.Create;
  FormMain.ParentListROMsCRC:= THashedStringList.Create;
  FormMain.ParentListROMsNameFullPath:= THashedStringList.Create;
  FormMain.SystemID_Number:= Data.aSystem;
  if Data.aImageIndex in [29, 30] then // if bad game, bad game merged
     Dec(BadGames)
  else
     Dec(GoodGames);
  Dec(TotalGames);
  Dec(CurrentGame);
  if Data.aMissingZip then
     Dec(MissingGames);
  AuditMode:= 0;
  Audit;
end;

function TFormAudit.MountHTMLLine(StringData: String): String;
var
  Loop: Integer;
begin
  Result:= '';
  for Loop:=1 to Length(StringData) do
  begin
    if StringData[Loop] = ' ' then
       Result:= Result+'&nbsp;'
    else
       Result:= Result+StringData[Loop];
  end;
end;

procedure TFormAudit.PopupSaveResultstoHTMLFileClick(Sender: TObject);
var
  HTMLFile: Textfile;
  Node: PVirtualNode;
  Data: pAuditInfo;
  AddROM, BadGame: Boolean;
  colIndexList: THashedStringList;
  Loop: ShortInt;

  procedure WriteGameLine(NodeLevel: Integer; BitmapFileName: String; IconIndex: Integer; CurrentGameData: pAuditInfo);
  var
    IconList: TBitmap;
    Loop2: ShortInt;
  begin
    if not FileExists(ExtractFilePath(SaveAuditDialog.FileName)+BitmapFileName+'.bmp') then
       begin
         IconList:= TBitmap.Create;
         IconList.Transparent:= True;
         IconList.TransparentColor:= clWhite;
         ImageListResults.GetBitmap(IconIndex,IconList);
         IconList.SaveToFile(ExtractFilePath(SaveAuditDialog.FileName)+BitmapFileName+'.bmp');
         FreeAndNil(IconList);
       end;
    case NodeLevel of
      0:
        begin
          WriteLn(HTMLFile, '</table><br>');
          WriteLn(HTMLFile, Format('<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111" width="%s" height="27">',
                                   [colIndexList[0]]));
          WriteLn(HTMLFile, '<tr>');
          if CurrentGameData.aParentGameName = '' then
             WriteLn(HTMLFile, '<p></font><font face="Segoe UI" size="2"><img src="'+BitmapFileName+'.bmp'+'" align="absmiddle">&nbsp;<i>'+CurrentGameData.aGameName+' - System: '+FormMain.GetSystemIniSection(CurrentGameData.aSystem)+'</i><br></font>')
          else
             WriteLn(HTMLFile, '<p></font><font face="Segoe UI" size="2"><img src="'+BitmapFileName+'.bmp'+'" align="absmiddle">&nbsp;<i>'+CurrentGameData.aGameName+' [Parent: '+CurrentGameData.aParentGameName+'] - System: '+FormMain.GetSystemIniSection(CurrentGameData.aSystem)+'</i><br></font>');

          // adds column header
          WriteLn(HTMLFile, Format('<td width="%u" height="27" bgcolor=#E6EBEE><font face="Segoe UI" size="2"><i>Name</i></font></td>',
                                   [AuditTree.Header.Columns.Items[1].Width]));

          for Loop2:= 1 to colIndexList.Count-2 do
          begin
            case StrToInt(colIndexList[Loop2]) of
              2: WriteLn(HTMLFile, Format('<td width="%u" height="27" bgcolor="#E6EBEE"><font face="Segoe UI" size="2"><i>Size</i></font></td>',
                                          [AuditTree.Header.Columns.Items[2].Width]));
              3: WriteLn(HTMLFile, Format('<td width="%u" height="27" bgcolor=#E6EBEE><font face="Segoe UI" size="2"><i>CRC32 / MD5</i></font></td>',
                                          [AuditTree.Header.Columns.Items[3].Width]));
              4: WriteLn(HTMLFile, Format('<td width="%u" height="27" bgcolor=#E6EBEE><font face="Segoe UI" size="2"><i>Correct Name</i></font></td>',
                                          [AuditTree.Header.Columns.Items[4].Width]));
              5: WriteLn(HTMLFile, Format('<td width="%u" height="27" bgcolor=#E6EBEE><font face="Segoe UI" size="2"><i>Correct Size</i></font></td>',
                                          [AuditTree.Header.Columns.Items[5].Width]));
              6: WriteLn(HTMLFile, Format('<td width="%u" height="27" bgcolor=#E6EBEE><font face="Segoe UI" size="2"><i>Correct CRC32 / MD5</i></font></td>',
                                          [AuditTree.Header.Columns.Items[6].Width]));
              7: WriteLn(HTMLFile, Format('<td width="%u" height="27" bgcolor=#E6EBEE><font face="Segoe UI" size="2"><i>Notes</i></font></td>',
                                          [AuditTree.Header.Columns.Items[7].Width]));
            end;
          end;
          WriteLn(HTMLFile, '</tr>');
          WriteLn(HTMLFile, '<tr>');
        end;
      1:
        begin
          WriteLn(HTMLFile, Format('<td width="%u" height="27"><font face="Segoe UI" size="2">&nbsp;<img src="'+BitmapFileName+'.bmp" align="absmiddle">&nbsp;'+MountHTMLLine(CurrentGameData.aName)+'</font></td>',
                                   [AuditTree.Header.Columns.Items[1].Width]));

          for Loop2:= 1 to colIndexList.Count-1 do
          begin
            case StrToInt(colIndexList[Loop2]) of
              2: begin
                   if CurrentGameData.aSize <> 0 then
                      WriteLn(HTMLFile, Format('<td width="%u" height="27"><font face="Segoe UI" size="2">'+IntToStr(CurrentGameData.aSize)+'</font></td>',
                                               [AuditTree.Header.Columns.Items[2].Width]))
                   else
                      WriteLn(HTMLFile, Format('<td width="%u" height="27"><font face="Segoe UI" size="2"></font></td>',
                                               [AuditTree.Header.Columns.Items[2].Width]));
                 end;
              3: WriteLn(HTMLFile, Format('<td width="%u" height="27"><font face="Segoe UI" size="2">'+CurrentGameData.aCRC32+'</font></td>',
                                          [AuditTree.Header.Columns.Items[3].Width]));
              4: WriteLn(HTMLFile, Format('<td width="%u" height="27"><font face="Segoe UI" size="2">'+CurrentGameData.aCorrectName+'</font></td>',
                                          [AuditTree.Header.Columns.Items[4].Width]));
              5: begin
                   if CurrentGameData.aCorrectSize <> 0 then
                      WriteLn(HTMLFile, Format('<td width="%u" height="27"><font face="Segoe UI" size="2">'+IntToStr(CurrentGameData.aCorrectSize)+'</font></td>',
                                               [AuditTree.Header.Columns.Items[5].Width]))
                   else
                      WriteLn(HTMLFile, Format('<td width="%u" height="27"><font face="Segoe UI" size="2"></td>',
                                               [AuditTree.Header.Columns.Items[5].Width]));
                 end;
              6: WriteLn(HTMLFile, Format('<td width="%u" height="27"><font face="Segoe UI" size="2">'+CurrentGameData.aCorrectCRC32+'</font></td>',
                                          [AuditTree.Header.Columns.Items[6].Width]));
              7: WriteLn(HTMLFile, Format('<td width="%u" height="27"><font face="Segoe UI" size="2">'+CurrentGameData.aNotes+'</font></td>',
                                          [AuditTree.Header.Columns.Items[7].Width]));
            end;
          end;
          WriteLn(HTMLFile, '</tr>');
        end;
    end;
  end;

begin
  if AuditTree.TotalCount = 0 then
     Exit;

  SB_InfoMessages.Caption:= 'Creating HTML file...';
  if SaveAuditDialog.Execute then
     begin
       AssignFile(HTMLFile, SaveAuditDialog.FileName);
       Rewrite(HTMLFile);
       TotalGames:= 0;
       MissingGames:= 0;
       GoodGames:= 0;
       BadGames:= 0;

       colIndexList:= THashedStringList.Create;
       colIndexList.BeginUpdate;
       colIndexList.Add('0');
       for Loop:=2 to AuditTree.Header.Columns.Count-1 do
       begin
         if coVisible in AuditTree.Header.Columns.Items[AuditTree.Header.Columns.ColumnFromPosition(Loop)].Options then
            begin
              colIndexList.Add(IntToStr(AuditTree.Header.Columns.Items[AuditTree.Header.Columns.ColumnFromPosition(Loop)].ID));
              colIndexList[0]:= IntToStr(StrToInt(colIndexList[0])+AuditTree.Header.Columns.Items[AuditTree.Header.Columns.ColumnFromPosition(Loop)].Width);
            end;
       end;
       colIndexList.EndUpdate;
       Node:= AuditTree.GetFirst;
       WriteLn(HTMLFile, '<p align="center"><font face="Segoe UI" color="#FF0000" size="3"><i>'+FormMain.EmulatorVersion[FormMain.SystemID_Number]+' audited games</i></font></p>');
       repeat
         Data:= AuditTree.GetNodeData(Node);
         if AuditTree.GetNodeLevel(Node) = 0 then
            begin
              case Data.aImageIndex of
                27, 28: // game ok, game ok merged
                  begin
                    if TMenuItem(Sender).Tag = 0 then
                       case Data.aImageIndex of
                         27: WriteGameLine(0, 'game_ok', Data.aImageIndex, Data); // game ok
                         28: WriteGameLine(0, 'game_ok_m', Data.aImageIndex, Data); // game ok merged
                       end;
                    Inc(GoodGames);
                    BadGame:= False;
                  end;
                29, 30: // game bad, game bad merged
                  begin
                    case TMenuItem(Sender).Tag of
                      0,1: begin
                             case Data.aImageIndex of
                               29: WriteGameLine(0, 'game_error', Data.aImageIndex, Data); // game bad
                               30: WriteGameLine(0, 'game_error_m', Data.aImageIndex, Data); // game bad merged
                             end;
                           end;
                        2: begin
                             WriteLn(HTMLFile, '<p><a href="'+FormMain.PopupAuditDownloadAddress.Hint+Data.aGameName+'.zip">');
                             if Data.aParentGameName <> '' then
                                WriteLn(HTMLFile, Data.aGameName+' [parent: '+Data.aParentGameName+'] - '+Data.aGameName+'.zip</a></p>')
                              else
                                WriteLn(HTMLFile, Data.aGameName+' - '+Data.aGameName+'.zip</a></p>');
                           end;
                    end;
                    Inc(BadGames);
                    BadGame:= True;
                  end;
              end;
              if (Pos('.zip not found]', Data.aNotes) > 0) and (Pos('[merged]', Data.aNotes) = 0) then
                 Inc(MissingGames);
              //else
              //   Inc(AvailableGames);
              case Data.aImageIndex of
                27, 28: // game ok, game ok merged
                  begin
                    if TMenuItem(Sender).Tag = 0 then
                       Inc(TotalGames);
                  end;
                29, 30: Inc(TotalGames); // game bad, game bad merged
              end;
            end
         else
            begin
              case TMenuItem(Sender).Tag of
                0: AddROM:= True;
                1: AddROM:= BadGame;
                2: AddROM:= False;
              end;

              if AddROM then
                 begin
                   case Data.aImageIndex of
                     00: WriteGameLine(1, 'rom_ok', 00, Data); // good rom
                     01: WriteGameLine(1, 'disk_ok', 01, Data); // good chd
                     02: WriteGameLine(1, 'rom_ok_nodump', 02, Data); // good rom no dump
                     03: WriteGameLine(1, 'disk_ok_nodump', 03, Data); // good chd no dump
                     04: WriteGameLine(1, 'rom_ok_redump', 04, Data); // good rom redump
                     05: WriteGameLine(1, 'disk_ok_redump', 05, Data); // good chd redump
                     06: WriteGameLine(1, 'rom_ok_m', 06, Data); // good rom merged
                     07: WriteGameLine(1, 'disk_ok_m', 07, Data); // good chd merged
                     08: WriteGameLine(1, 'rom_ok_nodump_m', 08, Data); // good rom no dump merged
                     09: WriteGameLine(1, 'disk_ok_nodump_m', 09, Data); // good chd no dump merged
                     10: WriteGameLine(1, 'rom_ok_redump_m', 10, Data); // good rom redump merged
                     11: WriteGameLine(1, 'disk_ok_redump_m', 11, Data); // good rom redump merged
                     12: WriteGameLine(1, 'rom_error', 12, Data); // bad rom
                     13: WriteGameLine(1, 'disk_error', 13, Data); // bad chd
                     14: WriteGameLine(1, 'rom_error_nodump', 14, Data); // bad rom no dump
                     15: WriteGameLine(1, 'disk_error_nodump', 15, Data); // bad chd no dump
                     16: WriteGameLine(1, 'rom_error_redump', 16, Data); // bad rom redump
                     17: WriteGameLine(1, 'disk_error_redump', 17, Data); // bad chd redump
                     18: WriteGameLine(1, 'rom_error_m', 18, Data); // bad rom merged
                     19: WriteGameLine(1, 'disk_error_m', 19, Data); // bad chd merged
                     20: WriteGameLine(1, 'rom_error_nodump_m', 20, Data); // bad rom no dump merged
                     21: WriteGameLine(1, 'disk_error_nodump_m', 21, Data); // bad chd no dump merged
                     22: WriteGameLine(1, 'rom_error_redump_m', 22, Data); // bad rom redump merged
                     23: WriteGameLine(1, 'disk_error_redump_m', 23, Data); // bad chd redump merged
                     24: WriteGameLine(1, 'rom_notused', 24, Data); // rom not used
                     25: WriteGameLine(1, 'rom_missing', 25, Data); // missing rom
                     26: WriteGameLine(1, 'disk_missing', 26, Data); // missing chd
                   end;
                 end;
            end;
         Node:= AuditTree.GetNext(Node);
       until (not Assigned(Node));
       if TMenuItem(Sender).Tag <> 2 then
          WriteLn(HTMLFile, '</table>');
       case TMenuItem(Sender).Tag of
         0: WriteLn(HTMLFile, '<p><font face="Segoe UI" size="2"><i>Audited Games: '+Format('%.4d', [TotalGames])+'<br>'+
                              'Good: '+Format('%.4d', [GoodGames])+'<br>'+'Missing Sets: '+Format('%.4d', [MissingGames])+'<br>'+
                              'Errors: '+Format('%.4d', [BadGames])+'</i><br><font color="#008000">Generated by Emu Loader v'+
                              GetFileInfo(Application.ExeName, VersionInfo[3])+
                              '<br>Date: '+DateToStr(Date)+' - Time: '+TimeToStr(Time)+'</font></font></p>');
         1: WriteLn(HTMLFile, '<p><font face="Segoe UI" size="2"><b>Audited Games: '+Format('%.4d', [TotalGames])+'<br>'+
                              'Missing Sets: '+Format('%.4d of %.4d', [MissingGames, TotalGames])+'<br>'+
                              'Errors: '+Format('%.4d', [BadGames])+'</b><br><font color="#008000">Generated by Emu Loader v'+
                              GetFileInfo(Application.ExeName, VersionInfo[3])+
                              '<br>Date: '+DateToStr(Date)+' - Time: '+TimeToStr(Time)+'</font></font></p>');
         2: WriteLn(HTMLFile, '<p><font face="Segoe UI" size="2"><i>Total Games To Download: '+Format('%.4d', [BadGames])+
                              '</i><br><font color="#008000">Generated by Emu Loader v'+
                              GetFileInfo(Application.ExeName, VersionInfo[3])+'<br>Date: '+
                              DateToStr(Date)+' - Time: '+TimeToStr(Time)+'</font></font></p>');
       end;
       CloseFile(HTMLFile);
       FreeAndNil(colIndexList);
       GenerateMessage('Operation Complete', Format('File "%s" saved with audit info.', [SaveAuditDialog.FileName]), 2);
     end;
  ClearSB_MessagesButton;
end;

procedure TFormAudit.FormKeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if ((Key = VK_ESCAPE) or (Key = VK_RETURN)) then
     begin
       case Auditing of
         True : ButtonCancel.OnClick(Self);
         False: ButtonClose.OnClick(Self);
       end;
       Key:= 0;
     end;
end;

procedure TFormAudit.FormCloseQuery(Sender: TObject;
  var CanClose: Boolean);
begin
  CanClose:= not Auditing;
  if CanClose then
     begin
       FormMain.ClearTreeView(AuditTree);
       FreeAndNil(ZipListFull);

       FreeAndNil(ROMsName);
       FreeAndNil(ROMsSize);
       FreeAndNil(ROMsCRC);
       FreeAndNil(ROMsStatus);

       FreeAndNil(FormMain.ListROMsName);
       FreeAndNil(FormMain.ListROMsSize);
       FreeAndNil(FormMain.ListROMsCRC);
       FreeAndNil(FormMain.ListROMsNameFullPath);
       FreeAndNil(FormMain.ParentListROMsName);
       FreeAndNil(FormMain.ParentListROMsSize);
       FreeAndNil(FormMain.ParentListROMsCRC);
       FreeAndNil(FormMain.ParentListROMsNameFullPath);

       WindowInfo(False);
       FormAudit.Release;
       FormAudit:= nil;
     end;
end;

procedure TFormAudit.AuditTreeFreeNode(Sender: TBaseVirtualTree;
  Node: PVirtualNode);
var
  Data: pAuditInfo;
begin
  Data:= AuditTree.GetNodeData(Node);
  Finalize(Data^);
end;

procedure TFormAudit.AuditTreeGetImageIndex(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex;
  var Ghosted: Boolean; var ImageIndex: Integer);
var
  Data: pAuditInfo;
begin
  if Kind in [ikNormal, ikSelected] then
     begin
       Data:= Sender.GetNodeData(Node);
       case Column of
         0:
          begin
            if Data.aImageIndex in [27..30] then // game ok / bad final status
               ImageIndex:= Data.aImageIndex;
          end;
         1:
          begin
            if Data.aImageIndex in [00..26] then // rom / chd final status
               ImageIndex:= Data.aImageIndex;
          end;
       end;
     end;
end;

procedure TFormAudit.AuditTreeHeaderClick(Sender: TVTHeader;
  Column: TColumnIndex; Button: TMouseButton; Shift: TShiftState; X,
  Y: Integer);
begin
  case Button of
    mbLeft:
      begin
        with Sender, Treeview do
        begin
          if SortColumn > NoColumn then
             Columns[SortColumn].Options:= Columns[SortColumn].Options + [coParentColor];

          if (SortColumn = NoColumn) or (SortColumn <> Column) then
          begin
            SortColumn:= Column;
            SortDirection:= sdAscending;
          end
          else
            if SortDirection = sdAscending then
              SortDirection:= sdDescending
            else
              SortDirection:= sdAscending;

          Columns[SortColumn].Color:= $F7F7F7;
          AuditTree.Tag:= SortColumn;
          SortTree(SortColumn, SortDirection, False);
        end;
      end;
    mbRight: PopupColumnsEditor.Click;
  end;
end;

procedure TFormAudit.FormCreate(Sender: TObject);
begin
  FormMain.SetAlphaBlendTreeView(AuditTree);
  AuditTree.NodeDataSize:= SizeOf(TAuditInfo);
  AuditTree.RootNodeCount:= 0;
  if FormMain.PopupAuditNotUsedFiles.Checked then
     ZipListFull:= THashedStringList.Create;
end;

procedure TFormAudit.PopupToggleExpandedClosedClick(Sender: TObject);
begin
  if AuditTree.TotalCount = 0 then
     Exit;
  AuditTree.BeginUpdate;
  case PopupToggleExpandedClosed.Tag of
    0: AuditTree.FullExpand(nil);
    1: AuditTree.FullCollapse(nil);
  end;
  PopupToggleExpandedClosed.Tag:= Ord(not Boolean(PopupToggleExpandedClosed.Tag));
  AuditTree.EndUpdate;
end;

procedure TFormAudit.AuditTreeCompareNodes(Sender: TBaseVirtualTree; Node1,
  Node2: PVirtualNode; Column: TColumnIndex; var Result: Integer);
var
  Data1, Data2: pAuditInfo;
begin
  Data1:= Sender.GetNodeData(Node1);
  Data2:= Sender.GetNodeData(Node2);
  case Column of
     0: Result:= CompareText(Data1.aGameTitle, Data2.aGameTitle);
     1: Result:= CompareText(Data1.aName, Data2.aName);
     2: Result:= Data1.aSize - Data2.aSize;
     3: Result:= CompareText(Data1.aCRC32, Data2.aCRC32);
     4: Result:= CompareText(Data1.aCorrectName, Data2.aCorrectName);
     5: Result:= Data1.aCorrectSize - Data2.aCorrectSize;
     6: Result:= CompareText(Data1.aCorrectCRC32, Data2.aCorrectCRC32);
     7: Result:= CompareText(Data1.aNotes, Data2.aNotes);
     8: Result:= CompareText(FormMain.GetEmuFullDescription(Data1.aSystem), FormMain.GetEmuFullDescription(Data2.aSystem));
  end;
end;

procedure TFormAudit.AuditTreeIncrementalSearch(Sender: TBaseVirtualTree;
  Node: PVirtualNode; const SearchText: WideString; var Result: Integer);
var
  StringsText,
  EntryText: String;
  Data: pAuditInfo;
begin
  StringsText:= SearchText;
  Data:= Sender.GetNodeData(Node);
  EntryText:= Data.aGameTitle;
  Result := AnsiStrLIComp(PChar(StringsText), PChar(EntryText), Length(StringsText));
end;

procedure TFormAudit.AuditTreeChange(Sender: TBaseVirtualTree;
  Node: PVirtualNode);
var
  Data: pAuditInfo;
begin
  if Sender.SelectedCount > 0 then
     begin
       if aSelectedNode <> Node then
          begin
            aSelectedNode:= Node;
            Data:= AuditTree.GetNodeData(Node);
            if Data.aGameTitle <> '' then
               if LabelGameDescription.Caption <> Data.aGameTitle then
                  LabelGameDescription.Caption:= Data.aGameTitle;
          end;
     end;
end;

procedure TFormAudit.PopupDownloadAddressClick(Sender: TObject);
begin
  FormMain.PopupAuditDownloadAddress.Click;
end;

procedure TFormAudit.WindowInfo(ReadInfo: Boolean);
var
  INIFile: TMemIniFile;
  Loop: ShortInt;
  ColumnsPosList: array of ShortInt;
begin
  if not ReadInfo then
     begin
       if FormMain.CheckReadOnly(FormMain.FrontendPath+'ini_files\el_extras.ini') then
          Exit;
     end;
  INIFile:= TMemIniFile.Create(FormMain.FrontendPath+'ini_files\el_extras.ini');
  case ReadInfo of
    True:
      begin
        FormAudit.Top:= INIFile.ReadInteger('Audit', 'ScreenTop', (Screen.Height-550) div 2);
        FormAudit.Left:= INIFile.ReadInteger('Audit', 'ScreenLeft', (Screen.Width-746) div 2);
        FormAudit.Width:= INIFile.ReadInteger('Audit', 'ScreenWidth', 746);
        FormAudit.Height:= INIFile.ReadInteger('Audit', 'ScreenHeight', 550);

        SetLength(ColumnsPosList, AuditTree.Header.Columns.Count+1);
        ColumnsPosList[0]:= 0;
        ColumnsPosList[1]:= 1;
        ColumnsPosList[INIFile.ReadInteger('Audit', 'ColumnPosition_Size', 2)]:= 2;
        ColumnsPosList[INIFile.ReadInteger('Audit', 'ColumnPosition_CRC', 3)]:= 3;
        ColumnsPosList[INIFile.ReadInteger('Audit', 'ColumnPosition_CorrectName', 4)]:= 4;
        ColumnsPosList[INIFile.ReadInteger('Audit', 'ColumnPosition_CorrectSize', 5)]:= 5;
        ColumnsPosList[INIFile.ReadInteger('Audit', 'ColumnPosition_CorrectCRC', 6)]:= 6;
        ColumnsPosList[INIFile.ReadInteger('Audit', 'ColumnPosition_Notes', 7)]:= 7;
        ColumnsPosList[INIFile.ReadInteger('Audit', 'ColumnPosition_System', 8)]:= 8;

        AuditTree.BeginUpdate;
        AuditTree.Header.Columns[0].Width:= INIFile.ReadInteger('Audit', 'ColumnSize_GameName', 225);
        AuditTree.Header.Columns[1].Width:= INIFile.ReadInteger('Audit', 'ColumnSize_Name', 175);
        AuditTree.Header.Columns[2].Width:= INIFile.ReadInteger('Audit', 'ColumnSize_Size', 120);
        AuditTree.Header.Columns[3].Width:= INIFile.ReadInteger('Audit', 'ColumnSize_CRC', 240);
        AuditTree.Header.Columns[4].Width:= INIFile.ReadInteger('Audit', 'ColumnSize_CorrectName', 175);
        AuditTree.Header.Columns[5].Width:= INIFile.ReadInteger('Audit', 'ColumnSize_CorrectSize', 120);
        AuditTree.Header.Columns[6].Width:= INIFile.ReadInteger('Audit', 'ColumnSize_CorrectCRC', 240);
        AuditTree.Header.Columns[7].Width:= INIFile.ReadInteger('Audit', 'ColumnSize_Notes', 250);
        AuditTree.Header.Columns[8].Width:= INIFile.ReadInteger('Audit', 'ColumnSize_System', 120);

        if not Boolean(INIFile.ReadInteger('Audit', 'ColumnVisible_Size', 1)) then
           AuditTree.Header.Columns.Items[2].Options:= AuditTree.Header.Columns.Items[2].Options-[coVisible];
        if not Boolean(INIFile.ReadInteger('Audit', 'ColumnVisible_CRC', 1)) then
           AuditTree.Header.Columns.Items[3].Options:= AuditTree.Header.Columns.Items[3].Options-[coVisible];
        if not Boolean(INIFile.ReadInteger('Audit', 'ColumnVisible_CorrectName', 1)) then
           AuditTree.Header.Columns.Items[4].Options:= AuditTree.Header.Columns.Items[4].Options-[coVisible];
        if not Boolean(INIFile.ReadInteger('Audit', 'ColumnVisible_CorrectSize', 1)) then
           AuditTree.Header.Columns.Items[5].Options:= AuditTree.Header.Columns.Items[5].Options-[coVisible];
        if not Boolean(INIFile.ReadInteger('Audit', 'ColumnVisible_CorrectCRC', 1)) then
           AuditTree.Header.Columns.Items[6].Options:= AuditTree.Header.Columns.Items[6].Options-[coVisible];
        if not Boolean(INIFile.ReadInteger('Audit', 'ColumnVisible_Notes', 1)) then
           AuditTree.Header.Columns.Items[7].Options:= AuditTree.Header.Columns.Items[7].Options-[coVisible];
        if not Boolean(INIFile.ReadInteger('Audit', 'ColumnVisible_System', 1)) then
           AuditTree.Header.Columns.Items[8].Options:= AuditTree.Header.Columns.Items[8].Options-[coVisible];

        for Loop:= 2 to AuditTree.Header.Columns.Count-1 do
            begin
              if AuditTree.Header.Columns.Items[ColumnsPosList[Loop]].Position <> Loop then
                 AuditTree.Header.Columns.Items[ColumnsPosList[Loop]].Position:= Loop;
            end;
        for Loop:=0 to Length(ColumnsPosList) do
            Finalize(ColumnsPosList);
        AuditTree.EndUpdate;
        if Boolean(INIFile.ReadInteger('Audit', 'Maximized', 0)) then
           FormAudit.WindowState:= wsMaximized;
      end;
    False:
      begin
        INIFile.WriteInteger('Audit', 'Maximized', Ord(FormAudit.WindowState = wsMaximized));
        if FormAudit.WindowState <> wsMaximized then
           begin
             INIFile.WriteInteger('Audit', 'ScreenTop', FormAudit.Top);
             INIFile.WriteInteger('Audit', 'ScreenLeft', FormAudit.Left);
             INIFile.WriteInteger('Audit', 'ScreenWidth', FormAudit.Width);
             INIFile.WriteInteger('Audit', 'ScreenHeight', FormAudit.Height);
           end;

        INIFile.WriteInteger('Audit', 'ColumnPosition_Size', AuditTree.Header.Columns.Items[2].Position);
        INIFile.WriteInteger('Audit', 'ColumnPosition_CRC', AuditTree.Header.Columns.Items[3].Position);
        INIFile.WriteInteger('Audit', 'ColumnPosition_CorrectName', AuditTree.Header.Columns.Items[4].Position);
        INIFile.WriteInteger('Audit', 'ColumnPosition_CorrectSize', AuditTree.Header.Columns.Items[5].Position);
        INIFile.WriteInteger('Audit', 'ColumnPosition_CorrectCRC', AuditTree.Header.Columns.Items[6].Position);
        INIFile.WriteInteger('Audit', 'ColumnPosition_Notes', AuditTree.Header.Columns.Items[7].Position);
        INIFile.WriteInteger('Audit', 'ColumnPosition_System', AuditTree.Header.Columns.Items[8].Position);
        INIFile.WriteInteger('Audit', 'ColumnSize_GameName', AuditTree.Header.Columns[0].Width);
        INIFile.WriteInteger('Audit', 'ColumnSize_Name', AuditTree.Header.Columns[1].Width);
        INIFile.WriteInteger('Audit', 'ColumnSize_Size', AuditTree.Header.Columns[2].Width);
        INIFile.WriteInteger('Audit', 'ColumnSize_CRC', AuditTree.Header.Columns[3].Width);
        INIFile.WriteInteger('Audit', 'ColumnSize_CorrectName', AuditTree.Header.Columns[4].Width);
        INIFile.WriteInteger('Audit', 'ColumnSize_CorrectSize', AuditTree.Header.Columns[5].Width);
        INIFile.WriteInteger('Audit', 'ColumnSize_CorrectCRC', AuditTree.Header.Columns[6].Width);
        INIFile.WriteInteger('Audit', 'ColumnSize_Notes', AuditTree.Header.Columns[7].Width);
        INIFile.WriteInteger('Audit', 'ColumnSize_System', AuditTree.Header.Columns[8].Width);
        INIFile.WriteInteger('Audit', 'ColumnVisible_Size', Ord(coVisible in AuditTree.Header.Columns.Items[2].Options));
        INIFile.WriteInteger('Audit', 'ColumnVisible_CRC', Ord(coVisible in AuditTree.Header.Columns.Items[3].Options));
        INIFile.WriteInteger('Audit', 'ColumnVisible_CorrectName', Ord(coVisible in AuditTree.Header.Columns.Items[4].Options));
        INIFile.WriteInteger('Audit', 'ColumnVisible_CorrectSize', Ord(coVisible in AuditTree.Header.Columns.Items[5].Options));
        INIFile.WriteInteger('Audit', 'ColumnVisible_CorrectCRC', Ord(coVisible in AuditTree.Header.Columns.Items[6].Options));
        INIFile.WriteInteger('Audit', 'ColumnVisible_Notes', Ord(coVisible in AuditTree.Header.Columns.Items[7].Options));
        INIFile.WriteInteger('Audit', 'ColumnVisible_System', Ord(coVisible in AuditTree.Header.Columns.Items[8].Options));
      end;
  end;
  INIFile.UpdateFile;
  FreeAndNil(INIFile);
end;

procedure TFormAudit.FormActivate(Sender: TObject);
begin
  if Tag = 0 then
     begin
       WindowInfo(True);
       Tag:= 1;
     end;
end;

procedure TFormAudit.PopupShowIconsLegendClick(Sender: TObject);
begin
  if not Assigned(FormAuditLegend) then
     FormAuditLegend:= TFormAuditLegend.Create(nil);
  FormAuditLegend.ShowModal;   
  FreeAndNil(FormAuditLegend);
end;

procedure TFormAudit.PopupAuditGamesPopup(Sender: TObject);
begin
  if aSelectedNode <> nil then
     PopupAuditGameAgain.Enabled:= aSelectedNode.ChildCount > 0
  else
     PopupAuditGameAgain.Enabled:= False;
end;

procedure TFormAudit.PopupColumnsEditorClick(Sender: TObject);
begin
  if not Assigned(FormAuditColumnsEditor) then
     FormAuditColumnsEditor:= TFormAuditColumnsEditor.Create(nil);
  FormAuditColumnsEditor.ShowModal;
  FreeAndNil(FormAuditColumnsEditor);
end;

procedure TFormAudit.UpdateColumns;
var
  Loop: ShortInt;
begin
  AuditTree.BeginUpdate;
  for Loop:= 0 to FormAuditColumnsEditor.ColumnsList.Items.Count-1 do
  begin
    if AuditTree.Header.Columns.Items[FormAuditColumnsEditor.ColumnsList.Items[Loop].ImageIndex].Position <> Loop then
       AuditTree.Header.Columns.Items[FormAuditColumnsEditor.ColumnsList.Items[Loop].ImageIndex].Position:= Loop;
  end;

  for Loop:= 0 to FormAuditColumnsEditor.ColumnsList.Items.Count-1 do
  begin
    AuditTree.Header.Columns.Items[FormAuditColumnsEditor.ColumnsList.Items[Loop].ImageIndex].Width:= StrToInt(FormAuditColumnsEditor.ColumnsList.Items[Loop].SubItems[0]);
    case FormAuditColumnsEditor.ColumnsList.Items[Loop].Checked of
      True : AuditTree.Header.Columns.Items[FormAuditColumnsEditor.ColumnsList.Items[Loop].ImageIndex].Options:=
               AuditTree.Header.Columns[FormAuditColumnsEditor.ColumnsList.Items[Loop].ImageIndex].Options+[coVisible];
      False: AuditTree.Header.Columns.Items[FormAuditColumnsEditor.ColumnsList.Items[Loop].ImageIndex].Options:=
               AuditTree.Header.Columns[FormAuditColumnsEditor.ColumnsList.Items[Loop].ImageIndex].Options-[coVisible];
    end;
  end;
  AuditTree.EndUpdate;
end;

function TFormAudit.SetGameName(DataHolder: pAuditInfo): String;
begin
  if DataHolder.aParentGameName = '' then
     Result:= DataHolder.aGameName else
     Result:= DataHolder.aGameName+' [Parent: '+DataHolder.aParentGameName+']';
end;

procedure TFormAudit.AuditTreeGetHint(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Column: TColumnIndex;
  var LineBreakStyle: TVTTooltipLineBreakStyle; var HintText: WideString);
var
  Data: pAuditInfo;
begin
  Data:= AuditTree.GetNodeData(Node);
  case Column of
    0: HintText:= Data.aGameTitle;
    1:
      begin
        if Data.aGameTitle = '' then
           HintText:= Data.aName
        else
           HintText:= SetGameName(Data);
      end;
    2: HintText:= IntToStr(Data.aSize);
    3: HintText:= Data.aCRC32;
    4: HintText:= Data.aCorrectName;
    5: HintText:= IntToStr(Data.aCorrectSize);
    6: HintText:= Data.aCorrectCRC32;
    7: HintText:= Data.aNotes;
    8:
      begin
        if Data.aSystem <> 0 then
           HintText:= FormMain.GetEmuFullDescription(Data.aSystem)
        else
           HintText:= '';
      end;
  end;
end;

procedure TFormAudit.AuditTreeGetText(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
  var CellText: WideString);
var
  Data: pAuditInfo;
begin
  Data:= Sender.GetNodeData(Node);
  case Column of
     0: CellText:= Data.aGameTitle;
     1:
       begin
         if Data.aGameTitle = '' then
            CellText:= Data.aName
         else
            CellText:= SetGameName(Data);
       end;
     2: if Data.aSize = 0 then
           CellText:= '' else
           CellText:= IntToStr(Data.aSize);
     3: CellText:= Data.aCRC32;
     4: CellText:= Data.aCorrectName;
     5: if Data.aCorrectSize = 0 then
           CellText:= '' else
           CellText:= IntToStr(Data.aCorrectSize);
     6: CellText:= Data.aCorrectCRC32;
     7: CellText:= Data.aNotes;
     8:
       begin
        if Data.aSystem <> -1 then
           CellText:= FormMain.GetEmuFullDescription(Data.aSystem)
        else
           CellText:= '';
       end;
  end;
end;

procedure TFormAudit.PopupAuditGamesMeasureMenuItem(Sender: TObject;
  AMenuItem: TMenuItem; ACanvas: TCanvas; var Width, Height: Integer;
  ABarVisible: Boolean; var DefaultMeasure: Boolean);
begin
  if (GetMenuBarMenusIntf(TMenu(Sender)).UseMenuStyle = msWindowsXP) then
     Height:= Height+2;
end;

procedure TFormAudit.FormShow(Sender: TObject);
begin
  if TopImage.Tag = 0 then
     begin
       if FileExists(FormMain.FrontendPath+'resources\images\msgbox.png') then
          begin
            TopImage.Tag:= 1;
            TopImage.Bitmap.LoadFromFile(FormMain.FrontendPath+'resources\images\msgbox.png');
          end;
     end;
end;

procedure TFormAudit.AuditTreePaintText(Sender: TBaseVirtualTree;
  const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
  TextType: TVSTTextType);
var
  Data: pAuditInfo;
begin
  Data:= Sender.GetNodeData(Node);
  if Data.aImageIndex in [12..23, 25, 26] then
     TargetCanvas.Font.Color:= clRed;
end;

end.

