unit filehistounit2forensic; interface // by maXbox4 shell version of GUI , locs=707 //purpose: analysis of a text file tokens with a histogram of char distribution {uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, Menus, StdCtrls, ComCtrls, ExtCtrls, Grids, ImgList, ShellAPI; } type TForm1 = TForm; var MainMenu1: TMainMenu; Exit1: TMenuItem; Open1: TMenuItem; N2: TMenuItem; Exit2: TMenuItem; help1: TMenuItem; OpenDialog1: TOpenDialog; Panel1: TPanel; Image1: TImage; Label1: TLabel; Label2: TLabel; Label3: TLabel; StringGrid1: TStringGrid; Text1: TMenuItem; Graph1: TMenuItem; ImageList1: TImageList; SaveToFile2: TMenuItem; SaveAsBMP2: TMenuItem; N1: TMenuItem; SaveDialog1: TSaveDialog; Contents1: TMenuItem; Index1: TMenuItem; N3: TMenuItem; History1: TMenuItem; Myhomepage1: TMenuItem; N4: TMenuItem; About1: TMenuItem; StatusBar1: TStatusBar; procedure Open1Click(Sender: TObject); procedure Image1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); procedure Exit2Click(Sender: TObject); procedure FormCreate(Sender: TObject); procedure Text1Click(Sender: TObject); procedure Graphic1Click(Sender: TObject); procedure StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState); procedure StringGrid1SelectCell(Sender: TObject; ACol, ARow: Integer; var CanSelect: Boolean); procedure SaveToFile2Click_UpdateGrid(Sender: TObject); procedure Contents1Click(Sender: TObject); procedure TForm1FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); //private var { Private declarations } TB : Array[0..255] of Int64; DB : Byte; //TeTellenF : File Of Byte; EenPct : real; Procedure Init; procedure ClearImage; Procedure TelFile(afilename: string; frombyte, tobyte: byte; blocksize: integer); // Procedure WriteToFile; Procedure WriteGraph; //public { Public declarations } var CancelFlag : Boolean; //end; var Form1: TForm1; implementation //uses filehistU2; //{$R *.DFM} Procedure Init; Var I : integer; Begin For I := 0 To 255 Do TB[I] := 0; End; function ReadLine(Stream: TStream; var Line: string): boolean; var ch: AnsiChar; StartPos, LineLen: integer; begin result := False; StartPos := Stream.Position; ch := #0; while (Stream.Read( ch, 1) = 1) and (ch <> #13) do; LineLen := Stream.Position - StartPos; Stream.Position := StartPos; SetString(Line, 'NIL', LineLen); Stream.ReadBuffer(Line[1], LineLen); if ch = #13 then begin result := True; if (Stream.Read( ch, 1) = 1) and (ch <> #10) then Stream.Seek(-1, 0) // unread it if not LF character. end end; function ReadLine2(var Stream: TFileStream; var Line: string): boolean; var RawLine: UTF8String; ch: AnsiChar; begin result := False; ch := #0; // writeln(itoa(stream.size)) //Stream.Seek(0, sobeginning) //SetLength(ch, 2); //writeln(itoa(Stream.ReadChar( ch, 1))) while (Stream.Readchar( ch, 1) = 1) and (ch <> #13) do begin result := True; RawLine := RawLine + ch //writeln(rawline) end; Line := RawLine; if ch = #13 then begin result := True; //writeln('dd') if (Stream.ReadChar( ch, 1) = 1) and (ch <> #10) then Stream.Seek(-1, socurrent) // unread it if not LF character. end end; var byteStream: TFileStream; //TMemoryStream; procedure WriteStreamInt(Stream : TStream; Num : integer); {writes an integer to the stream} begin Stream.WriteBufferInt(Num, SizeOf(num)); end; procedure WriteStreamStr(Stream : TStream; Str : string); {writes a string to the stream} var StrLen : integer; begin {get length of string} StrLen := Length(Str); {write length of string} WriteStreamInt(Stream, StrLen); if StrLen > 0 then {write characters} Stream.Write(Str[1], StrLen); end; procedure SaveData(FileName: TFileName); var MemStr: TMemoryStream; Title: String; begin MemStr:= TMemoryStream.Create; try MemStr.Seek(0, soFromBeginning); WriteStreamStr( MemStr, TItle ); MemStr.SaveToFile(FileName); finally MemStr.Free; end; end; procedure TForm1btnSaveStream(astream: TFileStream); var fs: TFileStream; begin fs:= TFileStream.Create(Exepath+'examples/jpgtest28.jpg', fmCreate); try fs.WriteString(streamtostring2(astream), astream.size); finally fs.Free; end; end; PROCEDURE TelFile(afilename: string; frombyte, tobyte: byte; blocksize:integer); Var ByteMin, ByteMax, I : Integer; NumRead : Integer; Totaal : Int64; Step : Real; //buf : array[1..2048] of Byte; buf, line : string; FSize : Longword ; // Int64; Begin try (* {$I-} AssignFile(TeTellenF, OpenDialog1.FileName); FileMode := 0; // ( Set file access to read only } Reset(TeTellenF); FSize := FileSize(TeTellenF); {$I+} *) Init; Step := Round(FSize / 100) + 0.0000001; Totaal := 0; // byteStream:= TFileStream.create(ExePath+'examples/whitenoise2.png', fmOpenRead) byteStream:= TFileStream.create(afilename, fmOpenRead) // first line header if ReadLine2(byteStream ,Line) then writeln(line); byteStream.Seek(0, sobeginning); //byteStream.Position:= 4096; writeln('file size: '+inttostr(bytestream.size)); //bytestream.write(Exepath+'examples/jpgtest28.jpg', bytestream.size) //SaveData (Exepath+'examples/jpgtest28.jpg') //TForm1btnSaveStream(bytestream) byteStream.Seek(0, sobeginning); Repeat //BlockRead(TeTellenF,buf,1024,NumRead); if byteStream.Size > 0 then begin SetLength(buf, blocksize); byteStream.Seek(0,socurrent); //byteStream.Position := Stream.Position - Count numread:= byteStream.Read(buf, blocksize); end; Totaal := Totaal + NumRead; writeln(' ' + FloatToStrF(Totaal/Step,ffGeneral,2,0) + ' %'); //Form2.Progressbar1.Position := Round(Totaal / Step); //byteStream.savetofile FOR I := 1 To NumRead Do Begin DB := ord(buf[I]); //write(itoa(DB)+' ') TB[DB] := TB[DB] + 1; Application.ProcessMessages; If CancelFlag = True then exit; End; write(' '+itoa(DB)+' ') Until NumRead = 0; { Array is full of bytes } ByteMax := TB[0]; { first is greatest } //biggest := Chr(0); For I := frombyte To tobyte Do IF TB[I] > ByteMax Then ByteMax := TB[I]; //greatest := Chr(I); ByteMin := TB[0]; For I := frombyte To tobyte Do IF TB[I] < ByteMin Then ByteMin := TB[I]; // Kleinste := Chr(I); finally //CloseFile(TeTellenF); byteStream.Free; EenPct := (ByteMax / 100) + 0.0001; writeln('totals '+floattostr(eenpct)) end; End; procedure Open1Click(Sender: TObject); begin CancelFlag := False; If OpenDialog1.execute then begin //TelFile; If not cancelFlag then begin SaveToFile2Click_UpdateGrid(Self); WriteGraph end; end; end; procedure SaveToFile2Click_UpdateGrid(Sender: TObject); Var I : Integer; StrL : TStringList; S : String; FileOut : Boolean; Begin StrL := TStringList.Create; FileOut := false; If sender = SaveToFile2 then FileOut := True; try For I := 0 To 255 Do Begin S := ' $' + IntToHex(I,2) ; S := S + ' = ' + IntToStr(TB[I]) + ' '; StrL.Add(S); StringGrid1.Cells[(I div 16) + 1, (I mod 16) + 1]:= IntToStr(TB[I]); End; If FileOut then begin SaveDialog1.Filename := ChangeFileExt(OpenDialog1.Filename,'.fhn'); if SaveDialog1.Execute then StrL.SaveToFile(SaveDialog1.Filename); end; finally StrL.Destroy; end; End; procedure ClearImage; begin With Image1.Canvas do begin Brush.Color := $02ccffff; FillRect(form1.ClientRect); Pen.Color := clBlack; // color of the data line MoveTo(0,0); LineTo(Image1.Width,0); LineTo(Image1.Width ,Image1.Height); LineTo(0,Image1.Height); LineTo(0,0); end; end; Procedure WriteGraph; Var MaxX, MaxY : integer; EenDiv, VertDiv : real; I, IMax, Onder, Links, Rechts: Integer; S : String; Begin IMax := 10; { 10 schaaldelen vertikaal } MaxX := Image1.Width - 1; // width of image GetMaxX; MaxY := Image1.Height - 1; // Getmaxy; Onder := Trunc(MaxY * 0.95); { = 100 % van de schaal } Links := Trunc(MaxX * 0.08); Rechts := (Links + 2 * 256) + 2; VertDiv := Onder / 11; { niet afgeronde real } EenDiv := Onder / 110; { niet afgeronde Real } { maak kader om alles } // procedure Line (X0,Y0,X1,Y1:Integer); virtual; abstract; with Image1.Canvas do begin Font.Name := 'Courier'; Font.Size := 8; ClearImage; Pen.Color := clBlack; // color of the data line { maak assen stelsel } MoveTo(Links,(Onder - Trunc(VertDiv*10)) - 10 ); LineTo(Links,Onder); // links vert. MoveTo(Links,Onder); LineTo(Rechts +1,Onder); // basis lijn MoveTo(Rechts,(Onder - Trunc(VertDiv*10)) - 10 ); LineTo(Rechts,Onder); // rechts vert. { zet procenten by de linker Y schaal } for I := 0 To IMax do begin // Str(I*10:3,S); S:= itoa(I*10) S := S + '%_'; TextOut(Links - 40,(Onder - Trunc( I * VertDiv) - 12) ,S) End; { zet getallen by de rechter Y schaal } For I := 0 To IMax Do Begin //Str(Trunc(EenPct * 10 * I),S); S:= itoa(Trunc(EenPct * 10 * I)) S := '_' + S; if I = 0 then S := S + ' Bytes'; TextOut(Rechts + 1,(Onder + 10) - Trunc( I * VertDiv) - 22,S) End; { maak grafiek } For I := 0 To 255 Do begin // Bar(Links+I*2, Onder-Trunc(TB[I]*EenDiv/EenPct), (Links+I*2)+2, Onder); MoveTo(Links+2+I*2, Onder-Trunc(TB[I]*EenDiv/EenPct)); LineTo((Links+2+I*2), Onder); end; end; end; procedure Image1MouseMove(Sender: TObject; Shift: TShiftState; X,Y: Integer); var S, S2 : String; Ch : Char; B : Integer; begin if ((X>53)and(X<565))and((Y>17)and(Y<278)) then begin //Label3.caption := '000'; //writeln('000'); If ColorToString(Image1.Canvas.Pixels[x, y]) = 'clBlack' then begin Ch := Chr((X - 54) div 2); B := ord(Ch); S := '$' + IntToHex(B,2); //If ord(Ch) in [33..126] then If (ord(Ch) >33) and (ord(ch) <126) then S2 := Ch else case Ch of #09 : S2 := 'TAB'; #10 : S2 := 'LF'; #13 : S2 := 'CR'; #32 : S2 := 'Space'; end; // Label2.Enabled := True; writeln (S + ' (' + S2 + ')'); end else begin // Label2.Enabled := False; writeln(''); end; end; end; procedure Exit2Click(Sender: TObject); begin form1.Close; end; procedure GridDrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState); var S: string; RectForText: TRect; //grid: TStringGrid; begin // Check for your cell here (in this case the cell in column 4 and row 2 will be colored) if (ACol = 1) and (ARow = 1) then begin S := StringGrid1.Cells[ACol, ARow]; // Fill rectangle with colour //StringGrid1.Canvas.Brush.Color := clred; // Next, draw the text in the rectangle StringGrid1.Canvas.Font.Color := clRed; StringGrid1.Canvas.FillRect(Rect); //RectForText := Rect; // Make the rectangle where the text will be displayed a bit smaller than the cell // so the text is not "glued" to the grid lines //InflateRect(RectForText, -2, -2); // Edit: using TextRect instead of TextOut to prevent overflowing of text //Grid.Canvas.TextRect1(RectForText, S); end; end; procedure FormCreate(Sender: TObject); var I : Integer; begin ClearImage; StringGrid1:= TStringgrid.create(self) with StringGrid1 do begin parent:= form1; color:= $02ccffff;; font.color:= clblack; height:= 294; width:= 670-1; align:= alright; ColWidths[0] := 21; ColCount:= 17 DefaultColWidth:= 38 DefaultRowHeight:= 16 RowCount:= 17 onSelectCell:= @StringGrid1SelectCell; //ondrawcell:= @GridDrawCell; end; //StringGrid1.ondrawcell:= @StringGrid1DrawCell; //Graphic1Click(Self); For I := 1 to 16 do begin StringGrid1.Cells[0,I]:= '$' + IntToHex(I-1,1) + ' ' ; StringGrid1.Cells[I,0]:= '$' + IntToHex(I-1,1) + '0 '; end; end; procedure Text1Click(Sender: TObject); begin Text1.Checked := True; //Text1.ImageIndex := 1; Graph1.Checked := False; //Graph1.ImageIndex := 0; Image1.Visible := False; StringGrid1.Visible := True; Text1.Visible := False; Text1.Visible := True; Graph1.Visible := False; Graph1.Visible := True; StringGrid1.SetFocus; end; procedure Graphic1Click(Sender: TObject); begin Text1.Checked := False; //Text1.ImageIndex := 0; Graph1.Checked := True; //Graph1.ImageIndex := 1; Image1.Visible := True; StringGrid1.Visible := False; Text1.Visible := False; Text1.Visible := True; Graph1.Visible := False; Graph1.Visible := True; end; const {Choose an alignment} ALIGNMENT = // DT_LEFT; DT_RIGHT; // DT_CENTER; procedure StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState); var Grid : TStringGrid; Texto : String; begin Grid := TStringGrid(Sender); if (ARow < Grid.FixedRows) or (ACol < Grid.FixedCols) then Grid.Canvas.Brush.Color := clBtnFace else Grid.Canvas.Brush.Color := clWhite; Grid.Canvas.FillRect(Rect); Texto := Grid.Cells[ACol,ARow]; DrawText( Grid.Canvas.Handle, PChar(Texto), StrLen(PChar(Texto)), Rect, ALIGNMENT); end; procedure StringGrid1SelectCell(Sender: TObject; ACol, ARow: Integer; var CanSelect: Boolean); var ch: char; s2: string; begin ch:= chr(((ACol-1)*16)+(ARow-1)) s2:= ch case ch of 09: s2:= 'TAB'; 10: s2:= 'LF'; 13: s2:= 'CR'; 32: s2:= 'Space'; end; writeln('$'+ IntToHex(((ACol- 1) * 16) +(ARow- 1),2)+' '+s2+ ' Qty: '+ StringGrid1.Cells[ACol,ARow]); end; procedure TF_avalie_salonStringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState); var S: string; gridclick: boolean; begin S:= StringGrid1.Cells[ACol, ARow]; StringGrid1.Canvas.FillRect(Rect); SetTextAlign(StringGrid1.Canvas.Handle, TA_CENTER); StringGrid1.Canvas.TextRect(Rect,Rect.Left + (Rect.Right - Rect.Left) div 2, Rect.Top + 2, S); if (ARow<>0 )AND(acol<>0)AND(gridclick=true) then begin try gridclick:=false; //x[acol+((strtoint(Edit_hafte.Text)-1)*7),arow]:=strtoint(StringGrid1.Cells[ACol, ARow]); except //x[acol+((strtoint(Edit_hafte.Text)-1)*7),arow]:=0; StringGrid1.Cells[acol,arow]:='0'; with TStringGrid(Sender) do begin Canvas.Brush.Color := clGreen; Canvas.FillRect(Rect); Canvas.TextOut(Rect.Left+2,Rect.Top+2,Cells[ACol, ARow]); end; end; end; end; procedure TForm1StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState); begin if (ACol = 3) and (ARow = 2) then with TStringGrid(Sender) do begin //paint the background Green Canvas.Brush.Color := clGreen; Canvas.FillRect(Rect); Canvas.TextOut(Rect.Left+2,Rect.Top+2,Cells[ACol, ARow]); end; end; procedure TMainFormmmTextStatistic(Sender: TObject; amemo: TMemo; ast: string); var AllText: String; I, LDigits, LLetters, LNumber, LLower, LUpper, LPuct, LSep, LSymbols, LWhites: Integer; TextRect: TRect; //tm: TEXTMETRIC; begin { Calculate all all kinds of charcters in the memo } if amemo = Nil then Alltext:= ast else AllText:= amemo.Text; LDigits:= 0; LNumber:= 0; LLetters:= 0; LLower:= 0; LUpper:= 0; LPuct:= 0; LSep:= 0; LSymbols:= 0; LWhites:= 0; for I := 1 to Length(AllText) do begin { Check for digit } if charIsDigit(AllText[I]) then Inc(LDigits); { Check for number } if charIsNumberchar(AllText[I]) then Inc(LNumber); { Check for letter } if charIsAlpha(AllText[I]) then Inc(LLetters); { Check for lower-cased letter } if charIsLower(AllText[I]) then Inc(LLower); { Check for upper-cased letter } if charIsUpper(AllText[I]) then Inc(LUpper); { Check for punctuation } if charIsPunctuation(AllText[I]) then Inc(LPuct); { Check for separators } if charIscontrol(AllText[I]) then Inc(LSep); { Check for symbols } //if IsSymbol(AllText[I]) then Inc(LSymbols); // if charIsPrintable(AllText[I]) then Inc(LSymbols); if charIsSymbolW(AllText[I]) then Inc(LSymbols); { Check for symbols } if charIsWhiteSpace(AllText[I]) then Inc(LWhites); end; Writeln( Format('%d digits; %d numbers; %d letters; ' + '(%d lower and %d upper); %d punctuation; %d separators; ' + '%d symbols; %d whitespaces', [LDigits, LNumber, LLetters, LLower, LUpper, LPuct, LSep, LSymbols, LWhites] ) ); statusBar1.SimpleText:= Format('Statistic: %d digits; %d numbers; %d letters; '+ '(%d lower and %d upper); %d punctuation; %d separators; ' + '%d symbols; %d whitespaces',[LDigits,LNumber,LLetters,LLower,LUpper,LPuct, LSep,LSymbols,LWhites]); {TextRect := Rect(100,20,400,50); Form1.Canvas.TextRect(TextRect,100,20,'Hello, Statistic!'); Form1.Canvas.Brush.Color := clBlack; Form1.Canvas.FrameRect(TextRect); } //form1.Canvas.TextRect(TextRect, 'some long long long text', //[tfTop, tfLeft, tfEndEllipsis, tfWordBreak]); end; // -- help call section ------ procedure Contents1Click(Sender: TObject); begin //Application.HelpCommand(HELP_FINDER, 0); end; procedure TForm1FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin if key = 112 then Application.HelpContext(100) end; var abt: boolean; codestr: string; begin Init; form1:= TForm1.create(self) statusbar1:= TStatusBar.Create(form1); statusbar1.parent:= form1 statusbar1.simplepanel:= true; statusbar1.showhint:= true; form1.setbounds(0,0,1350,420) form1.color:= clgreen; form1.caption:= ('File binary forensic histogram scanner maXbox4') form1.Icon.LoadFromResourceName(HInstance,'NEWSQLUNIT'); image1:= TImage.create(form1) image1.parent:= form1; image1.Width:= 651 image1.Height:= 294 image1.onMouseMove:= @Image1MouseMove //image1.align:= alClient; form1.show; //TelFile(ExePath+'examples/maxboxnews.htm'); if fileexists(ExePath+'examples/maxbox4_.jpg') then begin // TelFile(ExePath+'examples/moon.wav',1,254, 2048); // TelFile(ExePath+'examples/maxbox.mp3',0,254); // TelFile(ExePath+'examples/uebung.zip',1,250, 2048); TelFile(ExePath+'examples/maxbox4.jpg',1,250, 4096); // TelFile(ExePath+'examples/readmefirst_maxbox.txt',1,250, 4096); // TelFile(ExePath+'examples/income.dll',10,255, 4096); // TelFile(ExePath+'examples/fullmoon.png',10,255, 4096); // TelFile('C:\maxbook\maxboxpython\mentor_xml\casra2018\cassandra\august2019\Scripts\4_twitter\Output\Zurichairport_tweettimelineutfsingle200_2search.txt',1,255, 4096); //TelFile(ExePath+'examples/maXbox_mp3files.txt',1,255); //TelFile(ExePath+'maXbox4.exe'); //TelFile(ExePath+'examples/maxboxnews.htm',40,255); FormCreate(self); form1.caption:= form1.caption +' '+ ExePath+'examples/maxbox4.jpg'; WriteGraph; SaveToFile2Click_UpdateGrid(self); end else begin if savestrtofile(memo1.text, Exepath+'codetester.txt') then TelFile(Exepath+'codetester.txt',1,255, 4096); FormCreate(self); form1.caption:= form1.caption +' '+ Exepath+'codetester.txt'; WriteGraph; SaveToFile2Click_UpdateGrid(self); codestr:= LoadStringofFile(Exepath+'codetester.txt') writeln(itoa(length(codestr))) TMainFormmmTextStatistic(self, Nil, codestr) end; (* //srlist:= TStringlist.create; if LoadDFMFile2Strings('C:\maXbox\EKON_BASTA\EKON23\examples\app0026s\filehistounit.dfm', srlist, abt) = 0 then writeln(srlist.text); srlist.Free; *) End. (* diff number versus digit: https://stackoverflow.com/questions/228532/difference-between-char-isdigit-and-char-isnumber-in-c-sharp Ref:  mX4 executed: 03/09/2019 20:43:20 Runtime: 0:21:57.748 Memload: 43% use PascalScript maXbox4 - RemObjects & SynEdit Ver: 4.7.1.5 (471). Workdir: C:\Program Files (x86)\maxbox3 -----PS-BYTECODE (PSB) mX4-----21:46:42 object Form1: TForm1 Left = 97 Top = 116 BorderIcons = [biSystemMenu, biMinimize] BorderStyle = bsSingle Caption = ' FileHistogram' ClientHeight = 317 ClientWidth = 651 Color = clBtnFace Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -11 Font.Name = 'MS Sans Serif' Font.Style = [] Menu = MainMenu1 OldCreateOrder = False Position = poDesktopCenter OnCreate = FormCreate OnKeyDown = FormKeyDown PixelsPerInch = 96 TextHeight = 13 object Image1: TImage Left = 0 Top = 0 Width = 651 Height = 294 Align = alClient OnMouseMove = Image1MouseMove end object Panel1: TPanel Left = 0 Top = 294 Width = 651 Height = 23 Align = alBottom TabOrder = 0 object Label1: TLabel Left = 8 Top = 4 Width = 19 Height = 13 Caption = 'File:' end object Label2: TLabel Left = 502 Top = 5 Width = 31 Height = 13 Caption = 'Char : ' end object Label3: TLabel Left = 534 Top = 5 Width = 7 Height = 14 Font.Charset = ANSI_CHARSET Font.Color = clWindowText Font.Height = -11 Font.Name = 'Courier New' Font.Style = [] ParentFont = False end end object StringGrid1: TStringGrid Left = 0 Top = 0 Width = 651 Height = 294 Align = alClient ColCount = 17 DefaultColWidth = 38 DefaultRowHeight = 16 RowCount = 17 Font.Charset = ANSI_CHARSET Font.Color = clWindowText Font.Height = -9 Font.Name = 'MS Serif' Font.Style = [] ParentFont = False ScrollBars = ssNone TabOrder = 1 OnDrawCell = StringGrid1DrawCell OnSelectCell = StringGrid1SelectCell ColWidths = ( object SaveDialog1: TSaveDialog DefaultExt = 'fhd' Filter = 'FH Data File (*.fhd)|*.fhd' Options = [ofOverwritePrompt, ofHideReadOnly, ofEnableSizing] Left = 344 *)