unit uMain;

{$IF CompilerVersion >= 17} {$DEFINE USEREGION} {$IFEND}

{$WARN SYMBOL_PLATFORM OFF}

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, VirtualTrees, StdCtrls, ExtCtrls, ComCtrls, CommCtrl, uFOSFile, Grids,
  Buttons, Menus, uHelper, VistaProgressBar, GlassButton, XPMenu, ImgList, HexEdit,
  ShlObj, Clipbrd, MMSystem, XPMan;

const
  WM_ABOUT = WM_USER + 2;

type
  PInt = ^Integer;

  TfMain = class(TForm)
    spSplit: TSplitter;
    pnWorkplace: TPanel;
    pnStatus: TPanel;
    pcEditors: TPageControl;
    imMain: TImage;
    lbMain: TLabel;
    imScreen: TImage;
    pnScreen: TPanel;
    cbScreenCenter, cbScreenProportional, cbScreenStrech: TCheckBox;
    pnData: TPanel;
    odBinOpen: TOpenDialog;
    sdBinSave: TSaveDialog;
    lbIntDec, lbIntHex, lbIntBin: TLabel;
    sb00, sb01, sb02, sb03, sb04, sb05, sb06, sb07: TSpeedButton;
    sb08, sb09, sb10, sb11, sb12, sb13, sb14, sb15: TSpeedButton;
    sb16, sb17, sb18, sb19, sb20, sb21, sb22, sb23: TSpeedButton;
    sb24, sb25, sb26, sb27, sb28, sb29, sb30, sb31: TSpeedButton;
    edIntDec, edIntHex, edIntBin: TEdit;
    pmSave: TPopupMenu;
    pmiNormal, pmiUncompress, pmiCompress: TMenuItem;
    lbRODec, lbROHex, lbROBin: TLabel;
    edRODec, edROHex, edROBin: TEdit;
    lbReadOnly: TLabel;
    lbCalc: TLabel;
    lbRange: TLabel;
    edWString: TEdit;
    edFloat: TEdit;
    tsMain, tsScreen, tsData, tsForms, tsInteger, tsReadCalc, tsString, tsFloat, tsFlags, tsGD01, tsGD04: TTabSheet;
    tvFlags: TTreeView;
    pcGD01: TPageControl;
    tsGD01Type1, tsGD01Type2, tsGD01Type3, tsGD01Type4, tsGD01Type5, tsGD01Type6, tsGD01Type7, tsGD01All: TTabSheet;
    lvGD01Type1, lvGD01Type2, lvGD01Type3, lvGD01Type4, lvGD01Type5, lvGD01Type6, lvGD01Type7, lvGD01All: TListView;
    lvGD04: TListView;
    pnGD01All: TPanel;
    pnGD04: TPanel;
    pnStructure: TPanel;
    pnSearch: TPanel;
    edSearch: TEdit;
    stNotFound: TStaticText;
    lbSearch: TLabel;
    lbLink: TLabel;
    XPManifest: TXPManifest;
    imImages: TImageList;
    imLogo: TImage;
    pnForms: TPanel;
    lvForms: TListView;
    sgHeader: TStringGrid;
    sdCSVSave: TSaveDialog;
    sdSave: TSaveDialog;
    procedure AppMinimize(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure WMUpdateState(var M: TWMUpdateUIState); message WM_UPDATEUISTATE;
    procedure WMSysCommand(var Msg: TWMSysCommand); message WM_SYSCOMMAND;
    procedure spSplitMoved(Sender: TObject);
    procedure btnBreakClick(Sender: TObject);
    procedure btnOpenClick(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure vtStructureGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var Text: WideString);
    procedure vtStructureFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
    procedure vtStructureInitNode(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
    procedure odOpenSelectionChange(Sender: TObject);
    procedure tsScreenShow(Sender: TObject);
    procedure cbScreenClick(Sender: TObject);
    procedure tsMainShow(Sender: TObject);
    procedure vtStructureChange(Sender: TBaseVirtualTree; Node: PVirtualNode);
    procedure btnExportClick(Sender: TObject);
    procedure btnDeleteClick(Sender: TObject);
    procedure btnAddClick(Sender: TObject);
    procedure btnFormsClick(Sender: TObject);
    procedure btnFormsDataClick(Sender: TObject);
    procedure btnGD01AllClick(Sender: TObject);
    procedure btnGD04Click(Sender: TObject);
    procedure btnImportClick(Sender: TObject);
    procedure btnZeroClick(Sender: TObject);
    procedure btnFirstACHRClick(Sender: TObject);
    procedure btnFirstNPCClick(Sender: TObject);
    procedure tsIntegerShow(Sender: TObject);
    procedure edChange(Sender: TObject);
    procedure edKeyPress(Sender: TObject; var Key: Char);
    procedure btnSaveClick(Sender: TObject);
    procedure pmiClick(Sender: TObject);
    procedure tsReadCalcShow(Sender: TObject);
    procedure tsStringShow(Sender: TObject);
    procedure tsFloatShow(Sender: TObject);
    procedure pcGD01DrawTab(Control: TCustomTabControl; TabIndex: Integer; const Rect: TRect; Active: Boolean);
    procedure lvCustomDrawItem(Sender: TCustomListView; Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
    procedure tsGD01Show(Sender: TObject);
    procedure lvEdited(Sender: TObject; Item: TListItem; var S: string);
    procedure lvKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure tsFlagsShow(Sender: TObject);
    procedure tvFlagsCustomDrawItem(Sender: TCustomTreeView; Node: TTreeNode; State: TCustomDrawState; var DefaultDraw: Boolean);
    procedure lvColumnClick(Sender: TObject; Col: TListColumn);
    procedure lvGD02Edited(Sender: TObject; Item: TListItem; var S: string);
    procedure tsGD04Show(Sender: TObject);
    procedure lvGD04Edited(Sender: TObject; Item: TListItem; var S: string);
    procedure sgHeaderDrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState);
    procedure tsFormsShow(Sender: TObject);
    procedure lvFormsData(Sender: TObject; Item: TListItem);
    procedure lvFormsColumnClick(Sender: TObject; Col: TListColumn);
    procedure lvFormsDblClick(Sender: TObject);
    procedure vtStructureKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure edSearchKeyPress(Sender: TObject; var Key: Char);
    procedure edSearchChange(Sender: TObject);
    procedure btnSearchClick(Sender: TObject);
    procedure edSearchKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure lvFormsSelectItem(Sender: TObject; Item: TListItem; Selected: Boolean);
    procedure lvFormsKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure lbLinkClick(Sender: TObject);
  protected
    procedure CreateParams(var Params: TCreateParams); override;
  private
    odOpen: TExOpenDialog;
    vtStructure: TVirtualStringTree;
    sFileName: string;
    FOSFile: TFOSFile;
    bBreak: Boolean;
    BufNode: PVirtualNode;
    BufData: TBytes;
    pbProcess: TVistaProgressBar;
    heEdit: THexEditor;
    btnOpen, btnFirstACHR, btnFirstNPC, btnSave, btnBreak, btnExport, btnImport, btnZero: TGlassButton;
    btnForms, btnFormsData, btnGD01All, btnGD04, btnSearch, btnDelete, btnAdd: TGlassButton;
    FormViews: TFormViews;
    procedure Progress(const Percent: Integer; var Break: Boolean);
    procedure FillTree;
    procedure PrepareDataToShow;
    procedure RefreshData(var MyData: PMyRec);
    procedure vtStructureSearch(Sender: TBaseVirtualTree; Node: PVirtualNode; Text: string);
    procedure WndMsg(var Msg: TMsg; var Handled: Boolean);
    procedure DeleteSelectedObject;
    procedure DeleteSelectedForms;
    procedure AddObject;
  end;

var
  fMain: TfMain;

implementation

uses
  ShellAPI, uAddForm, uFormIDArray;

{$R *.dfm}

type
  THackPanel = class(TCustomPanel);
  TStatCategory = (scGeneral, scQuest, scCombat, scCrafting, scCrime, scTowns, scUnkn, scOther);

{$IF Defined(USEREGION)}{$REGION 'ExOpenDialog'}{$IFEND}
procedure TfMain.odOpenSelectionChange(Sender: TObject);
var
  FOSFileInfo: TFOSFileInfo;
  bmpPicture: TBitmap;
begin
  with odOpen do
    if FileExists(FileName) then
    begin
      if ReadFOSFileInfo(FOSFileInfo, FileName) = 0 then
        with FOSFileInfo do
        begin
          bmpPicture := ScreenShotToBMP(shotWidth, shotHeight, screenshotData);
          try
            imPicture.Picture.Assign(bmpPicture);
            if bmpPicture.Height > 0 then
              imPicture.Width := Round(bmpPicture.Width * (imPicture.Height / bmpPicture.Height));
            lbInfo.Left := imPicture.Width + 10;
            lbInfo.Caption := 'Save No: ' + IntToStr(saveNumber) + #13'Player: ' + playerName + #13'Level: ' + IntToStr(playerLevel) +
                              #13'Race: ' + playerRaceEditorId + #13'Location: ' + playerLocation + #13'Date: ' + gameDate;
          finally
            bmpPicture.Free;
          end;
        end
      else
      begin
        imPicture.Picture := nil;
        lbInfo.Left := 10;
        lbInfo.Caption := 'Can''t read file or file isn''t Fallout 4 FOS file';
      end;
    end
    else
    begin
      imPicture.Picture := nil;
      lbInfo.Caption := '';
    end;
end;
{$IF Defined(USEREGION)}{$ENDREGION}{$IFEND}

function GetVersion: string;
var
  Size: DWORD;
  Buffer: Pointer;
  FI: PVSFixedFileInfo;
begin
  Result := '';
  Size := GetFileVersionInfoSize(PChar(Application.Exename), Size);
  if Size <> 0 then
  begin
    GetMem(Buffer, Size);
    try
      if GetFileVersionInfo(PChar(Application.Exename), 0, Size, Buffer) then
        if VerQueryValue(Buffer, '\', Pointer(FI), Size) then
          Result := Result + IntToStr ((FI.dwFileVersionMS and $FFFF0000) shr $10) + '.' +
                  IntToStr (FI.dwFileVersionMS and $FFFF) + {'.' +
                  IntToStr ((FI.dwFileVersionLS and $FFFF0000) shr $10) + '.' +}
                  '  build ' + IntToStr (FI.dwFileVersionLS and $FFFF);
    finally
      FreeMem (Buffer);
    end;
  end;
end;

procedure TfMain.WndMsg(var Msg: TMsg; var Handled: Boolean);
begin
  if Msg.Message = WM_SYSCOMMAND then
    case Msg.wParam of
      WM_ABOUT: begin
        Handled := True;
        ShellAbout(Handle, PChar('About' + '#' + Self.Caption),
          PChar('Fallout 4 FOS SaveGame Editor version ' + GetVersion + #13#10 + 'Copyright © 2015 fubrus'),
          Application.Icon.Handle);
      end;
    end;
end;

procedure TfMain.pcGD01DrawTab(Control: TCustomTabControl; TabIndex: Integer; const Rect: TRect; Active: Boolean);
var
  R: TRect;
begin
  with Control.Canvas do
  begin
    Brush.Color := $00F0F0F0;
    Font.Color := clBtnText;
    R := Rect;
    FillRect(R);
    DrawText(Handle, PChar(TTabControl(Control).Tabs[Tabindex]), Length(TTabControl(Control).Tabs[Tabindex]), R, DT_VCENTER or DT_CENTER or DT_WORDBREAK or DT_NOPREFIX or DT_SINGLELINE);
  end;
end;

procedure TfMain.pmiClick(Sender: TObject);
var
  Res: Integer;
  MyData: PMyRec;
begin
  if BufNode <> nil then
  begin
    MyData := vtStructure.GetNodeData(BufNode);
    if Assigned(MyData) then
      RefreshData(MyData);
  end;
  Res := 0;
  if Sender is TMenuItem then
    with Sender as TMenuItem do
      if sdSave.Execute then
      try
        bBreak := False;
        CreateProgress(Self, pnWorkplace, btnBreak, pbProcess, pnStatus, btnBreakClick, fMain.Handle);
        case Tag of
          0: Res := SaveFOSFile(FOSFile, sdSave.FileName, Progress);
          1: Res := SaveFOSFileUncompressed(FOSFile, sdSave.FileName, Progress);
          2: Res := SaveFOSFileCompressed(FOSFile, sdSave.FileName, Progress);
        end;
        if Res <> 0 then
          SetTaskbarProgressState(fMain.Handle, tbpsError);
        case Res of
          -2: MessageBox(Handle, 'Wrong data', 'Error', MB_OK or MB_ICONSTOP);
          -1: MessageBox(Handle, 'User break', 'Information', MB_OK or MB_ICONINFORMATION);
           0: begin
                sFileName := sdSave.FileName;
                pnStatus.Caption := MinimizeName(sFileName, THackPanel(pnStatus).Canvas, pnStatus.ClientWidth - 8);
              end
          else
            MessageBox(Handle, PChar('Write error in step: ' + IntToStr(Res) + #13 + SysErrorMessage(LastIOState)), 'Error', MB_OK or MB_ICONERROR);
        end;
      finally
        FreeProgress(btnBreak, pbProcess, pnStatus, fMain.Handle);
        PrepareDataToShow;
      end;
end;

procedure TfMain.btnBreakClick(Sender: TObject);
begin
  bBreak := True;
end;

procedure TfMain.Progress(const Percent: Integer; var Break: Boolean);
begin
  if Assigned(pbProcess) then
  begin
    pbProcess.Position := Percent;
    SetTaskbarProgressValue(fMain.Handle, Percent, pbProcess.Max);
  end;
  Break := bBreak;
  Application.ProcessMessages;
end;

procedure TfMain.btnDeleteClick(Sender: TObject);
begin
  if pcEditors.ActivePageIndex = tsForms.PageIndex then
    DeleteSelectedForms
  else
    DeleteSelectedObject;
end;

function GetSelection(Node: PVirtualNode): PVirtualNode;
begin
  if Node^.PrevSibling <> nil then
    Result := Node^.PrevSibling
  else
    if Node^.NextSibling <> nil then
      Result := Node^.NextSibling
    else
      Result := Node^.Parent;
end;

{$IF Defined(USEREGION)}{$REGION 'Export data'}{$IFEND}
procedure TfMain.btnExportClick(Sender: TObject);
var
  sName: string;
  f: File;
  Res: Integer;
  MyData: PMyRec;
begin
  if BufNode = nil then
    Exit;
  MyData := vtStructure.GetNodeData(BufNode);
  if BufNode^.Parent <> nil then
    sName := IntToStr(BufNode^.Parent^.Index + 1) + '_'
  else
    sName := IntToStr(BufNode^.Index + 1) + '_';
  if Assigned(MyData) then
  begin
    case MyData^._Type of
      btGlobal:
        if (BufNode^.Parent <> nil) and (BufNode^.Parent^.Parent <> nil) then
          case BufNode^.Parent^.Parent^.Index of
            8: sName := sName + GetTypeString(FOSFile.globalDataTable1[BufNode^.Parent^.Index]^._type);
            9: sName := sName + GetTypeString(FOSFile.globalDataTable2[BufNode^.Parent^.Index]^._type);
            11: sName := sName + GetTypeString(FOSFile.globalDataTable3[BufNode^.Parent^.Index]^._type);
          end;
      btForm:
        sName := sName + Copy(GetFormString(FOSFile.changeForms[BufNode^.Parent^.Index]^._type), 1, 4);
    end;
    sName := sName + '.BIN';
    if (sdBinSave.FileName = '') and (BufNode <> nil) then
      sdBinSave.FileName := ExtractFilePath(ParamStr(0)) + sName
    else
      sdBinSave.FileName := ExtractFilePath(sdBinSave.FileName) + sName;
    ReadHexEditor(BufData, heEdit);
    if (Length(BufData) > 0) and sdBinSave.Execute then
    begin
      AssignFile(F, sdBinSave.FileName);
      {$IFOPT I+} {$DEFINE IOCHECKS_ON} {$ENDIF} {$I-}
      ReWrite(F, 1);
      Res := IOResult;
      if Res = 0 then
      try
        BlockWrite(f, BufData[0], Length(BufData));
        Res := IOResult;
        if Res <> 0 then
          MessageBox(Handle, PChar('Can''t write to selected file:'#13 + sdBinSave.FileName + #13 + SysErrorMessage(Res)), 'Error', MB_OK or MB_ICONERROR);
      finally
        CloseFile(f);
      end
      else
        MessageBox(Handle, PChar('Can''t create selected file:'#13 + sdBinSave.FileName + #13 + SysErrorMessage(Res)), 'Error', MB_OK or MB_ICONERROR);
      {$IFDEF IOCHECKS_ON} {$I+} {$UNDEF IOCHECKS_ON} {$ENDIF}
    end;
  end;
end;

procedure TfMain.btnFormsClick(Sender: TObject);
var
  sList: TStringList;
  i: integer;
  sLine: string;
begin
  if sdCSVSave.Execute then
  begin
    sList := TStringList.Create;
    try
      for i := 0 to Length(FormViews) - 1 do
        with FormViews[i], Form do
        begin
          sLine := IntToStr(No);
          sLine := sLine + ListSeparator + RefIDToString(Form.formID);
          sLine := sLine + ListSeparator + IntToHex(changeFlags, 8);
          sLine := sLine + ListSeparator + IntToStr(changeFlags);
          sLine := sLine + ListSeparator + IntToStr(Form._type);
          sLine := sLine + ListSeparator + Copy(GetFormString(Form._type), 1, 4);
          sLine := sLine + ListSeparator + IntToStr(Form.version);
          sLine := sLine + ListSeparator + IntToStr(Form.length1);
          if length2 > 0 then
            sLine := sLine + ListSeparator + IntToStr(Form.length2);
          sList.Add(sLine);
      end;
      sList.SaveToFile(sdCSVSave.FileName);
    finally
      sList.Free
    end;
  end;
end;

procedure TfMain.btnFormsDataClick(Sender: TObject);
var
  sOutDir: string;
  i: integer;
  Buff: TBytes;
  F: File;
begin
  if SelectDirectoryEx(Handle, sOutDir, 'Select output directory:', '', BIF_RETURNONLYFSDIRS or $40 or BIF_VALIDATE, @CallBack) then
  begin
    if (sOutDir <> '') and (sOutDir[Length(sOutDir)] <> '\') then
      sOutDir := sOutDir + '\';
    if (sOutDir <> '') and (FOSFile.magic = C_F4FOS) then
    try
      bBreak := False;
      CreateProgress(Self, pnWorkplace, btnBreak, pbProcess, pnStatus, btnBreakClick);
      for i := 0 to Length(FormViews) - 1 do
        with FormViews[i] do
        begin
          SetLength(Buff, FOSFile.ChangeForms[No]^.length1);
          if FOSFile.ChangeForms[No]^.length1 > 0 then
            Move(FOSFile.ChangeForms[No]^.Data^[0], Buff[0], FOSFile.ChangeForms[No]^.length1);
          if FOSFile.ChangeForms[No]^.length2 > 0 then
            DecompressData(Buff, FOSFile.ChangeForms[No]^.length2);
          AssignFile(F, sOutDir + RefIDToHex(FOSFile.ChangeForms[No]^.formID) + '.BIN');
          {$IFOPT I+} {$DEFINE IOCHECKS_ON} {$ENDIF} {$I-}
          ReWrite(F, 1);
          if IOResult = 0 then
          try
            if Length(Buff) > 0 then
              BlockWrite(f, Buff[0], Length(Buff));
          finally
            CloseFile(F);
          end;
          {$IFDEF IOCHECKS_ON} {$I+} {$UNDEF IOCHECKS_ON} {$ENDIF}
          if Round((i + 1) / Length(FormViews) * 100) > pbProcess.Position then
          begin
            pbProcess.Position := Round((i + 1) / Length(FormViews) * 100);
            Application.ProcessMessages;
          end;
          if bBreak then
            Break;
        end;
    finally
      FreeProgress(btnBreak, pbProcess, pnStatus);
    end;
  end;
end;

procedure TfMain.btnGD01AllClick(Sender: TObject);
var
  sList: TStringList;
  i: integer;
begin
  if sdCSVSave.Execute then
  begin
    sList := TStringList.Create;
    try
      with lvGD01All do
        for i := 0 to Items.Count - 1 do
          with Items[i] do
            sList.Add(Caption + ListSeparator + SubItems[0] + ListSeparator + SubItems[1]);
      sList.SaveToFile(sdCSVSave.FileName);
    finally
      sList.Free
    end;
  end;
end;

procedure TfMain.btnGD04Click(Sender: TObject);
var
  sList: TStringList;
  i: integer;
begin
  if sdCSVSave.Execute then
  begin
    sList := TStringList.Create;
    try
      with lvGD04 do
        for i := 0 to Items.Count - 1 do
          with Items[i] do
            sList.Add(Caption + ListSeparator + SubItems[0]);
      sList.SaveToFile(sdCSVSave.FileName);
    finally
      sList.Free
    end;
  end;
end;
{$IF Defined(USEREGION)}{$ENDREGION}{$IFEND}

{$IF Defined(USEREGION)}{$REGION 'RefreshData'}{$IFEND}
procedure TfMain.RefreshData(var MyData: PMyRec);
begin
  with MyData^ do
    case _Type of
      btForm:
        begin
          SetData(TBytes(_Pointer^), BufData, _Size, _Decompressed, heEdit, True);
          if BufNode^.Parent <> nil then
          begin
            FOSFile.changeForms[BufNode^.Parent^.Index]^.length1 := _Size;
            FOSFile.changeForms[BufNode^.Parent^.Index]^.length2 := _Decompressed;
            SetNodeText(vtStructure, GetChild(BufNode^.Parent, 4));
            SetNodeText(vtStructure, GetChild(BufNode^.Parent, 5));
          end;
        end;
      btGlobal:
        begin
          SetData(TBytes(_Pointer^), BufData, _Size, _Decompressed, heEdit);
          if (BufNode^.Parent <> nil) and (BufNode^.Parent^.Parent <> nil) then
          begin
            case BufNode^.Parent^.Parent^.Index of
              8: FOSFile.globalDataTable1[BufNode^.Parent^.Index]^._length := _Size;
              9: FOSFile.globalDataTable2[BufNode^.Parent^.Index]^._length := _Size;
              11: FOSFile.globalDataTable3[BufNode^.Parent^.Index]^._length := _Size;
            end;
            SetNodeText(vtStructure, GetChild(BufNode^.Parent, 1));
          end;
        end;
    end;
end;
{$IF Defined(USEREGION)}{$ENDREGION}{$IFEND}

{$IF Defined(USEREGION)}{$REGION 'Import BIN data'}{$IFEND}
procedure TfMain.btnImportClick(Sender: TObject);
var
  f: File;
  Res: Integer;
  MyData: PMyRec;
begin
  if (BufNode <> nil) and odBinOpen.Execute then
  begin
    MyData := vtStructure.GetNodeData(BufNode);
    if Assigned(MyData) then
    begin
      AssignFile(F, odBinOpen.FileName);
      {$IFOPT I+} {$DEFINE IOCHECKS_ON} {$ENDIF} {$I-}
      Reset(F, 1);
      Res := IOResult;
      if Res = 0 then
      try
        try
          SetLength(BufData, FileSize(F));
          if Length(BufData) > 0 then
          begin
            BlockRead(f, BufData[0], Length(BufData));
            Res := IOResult;
            if Res <> 0 then
              MessageBox(Handle, PChar('Can''t read from selected file:'#13 + odBinOpen.FileName + #13 + SysErrorMessage(Res)), 'Error', MB_OK or MB_ICONERROR);
          end;
        except
          SetLength(BufData, 0);
          MessageBox(Handle, PChar('File is to large!'), 'Error', MB_OK or MB_ICONERROR);
        end
      finally
        CloseFile(F);
      end
      else
        MessageBox(Handle, PChar('Can''t open selected file:'#13 + odBinOpen.FileName + #13 + SysErrorMessage(Res)), 'Error', MB_OK or MB_ICONERROR);
      {$IFDEF IOCHECKS_ON} {$I+} {$UNDEF IOCHECKS_ON} {$ENDIF}
      WriteHexEditor(BufData, heEdit);
      RefreshData(MyData);
    end;
  end;
end;
{$IF Defined(USEREGION)}{$ENDREGION}{$IFEND}

{$IF Defined(USEREGION)}{$REGION 'Fill zero BIN data'}{$IFEND}
procedure TfMain.btnZeroClick(Sender: TObject);
var
  MyData: PMyRec;
begin
  if BufNode <> nil then
  begin
    MyData := vtStructure.GetNodeData(BufNode);
    if Assigned(MyData) and (MyData^._Size > 0)  then
    begin
      if MyData^._Decompressed <> 0 then
        SetLength(BufData, MyData^._Decompressed)
      else
        SetLength(BufData, MyData^._Size);
      ZeroMemory(@BufData[0], MyData^._Size);
      WriteHexEditor(BufData, heEdit);
      RefreshData(MyData);
    end;
  end;
end;
{$IF Defined(USEREGION)}{$ENDREGION}{$IFEND}

procedure TfMain.btnFirstACHRClick(Sender: TObject);
var
  i: Integer;
begin
  with FOSFile, vtStructure do
    if magic = C_F4FOS then
      for i := 0 to Length(changeForms) - 1 do
        with changeForms[i]^ do
          if (_type and $3F) = 1 then
          begin
            Expanded[GetFirst(False)] := True;
            Expanded[GetChild(GetFirst(False), 11)] := True;
            FocusedNode := GetChild(GetChild(GetFirst(False), 11), i);
            Expanded[FocusedNode] := True;
            Break;
          end;

end;

procedure TfMain.btnFirstNPCClick(Sender: TObject);
var
  i: Integer;
begin
  with FOSFile, vtStructure do
    if magic = C_F4FOS then
      for i := 0 to Length(changeForms) - 1 do
        with changeForms[i]^ do
          if (_type and $3F) = 9 then
          begin
            Expanded[GetFirst(False)] := True;
            Expanded[GetChild(GetFirst(False), 11)] := True;
            FocusedNode := GetChild(GetChild(GetFirst(False), 11), i);
            Expanded[FocusedNode] := True;
            Break;
          end;

end;

procedure TfMain.btnOpenClick(Sender: TObject);
var
  Res: Integer;
begin
  if odOpen.Execute then
  try
    sFileName := '';
    bBreak := False;
    CreateProgress(Self, pnWorkplace, btnBreak, pbProcess, pnStatus, btnBreakClick, fMain.Handle);
    Res := ReadFOSFile(FOSFile, odOpen.FileName, Progress);
    if Res <> 0 then
      SetTaskbarProgressState(fMain.Handle, tbpsError);
    case Res of
      -1: MessageBox(Handle, 'User break', 'Information', MB_OK or MB_ICONINFORMATION);
      0: begin
           sFileName := odOpen.FileName;
           Exit;
         end;
      else
        MessageBox(Handle, PChar('Read error in step: ' + IntToStr(Res) + #13 + SysErrorMessage(LastIOState)), 'Error', MB_OK or MB_ICONERROR);
    end;
  finally
    FreeProgress(btnBreak, pbProcess, pnStatus, fMain.Handle);
    PrepareDataToShow;
  end;
end;

procedure TfMain.btnSaveClick(Sender: TObject);
var
  pPoint: TPoint;
begin
  with Sender as TGlassButton do
  begin
    pPoint := Parent.ClientToScreen(Point(Left, Top + Height));
    pmSave.Popup(pPoint.X, pPoint.Y);
  end;
end;

procedure TfMain.cbScreenClick(Sender: TObject);
begin
  if Sender is TCheckBox then
    with Sender as TCheckBox do
    begin
      if Sender = cbScreenCenter then
        imScreen.Center := Checked;
      if Sender = cbScreenStrech then
        imScreen.Stretch := Checked;
      if Sender = cbScreenProportional then
        imScreen.Proportional := Checked;
    end;
end;

procedure TfMain.edChange(Sender: TObject);
var
  i64: Int64;
  MyData: PMyRec;
  {$IF Defined(USEREGION)}{$REGION 'SetIntValue'}{$IFEND}
  procedure SetDec;
  begin
    edIntDec.Text := IntToStr(i64);
  end;
  procedure SetHex;
  var
    sVal: string;
  begin
    sVal := IntToHex(i64, 8);
    while (sVal <> '') and (sVal[1] = '0') do
      Delete(sVal, 1, 1);
    edIntHex.Text := sVal;
  end;
  procedure SetBin;
  var
    sVal: string;
  begin
    sVal := IntToBin(i64, 32);
    while (sVal <> '') and (sVal[1] = '0') do
      Delete(sVal, 1, 1);
    edIntBin.Text := sVal;
  end;
  procedure SetButtons;
  var
    i: Integer;
  begin
    with Self do
      for i := 0 to ComponentCount - 1 do
        if (Components[i] is TSpeedButton) and ((Components[i] as TSpeedButton).Parent = tsInteger) then
          if (i64 and (1 shl Components[i].Tag)) <> 0 then
            (Components[i] as TSpeedButton).Caption := '1'
          else
            (Components[i] as TSpeedButton).Caption := '0';
  end;
  function GetButtons: Int64;
  var
    i: Integer;
  begin
    Result := 0;
    with Self do
      for i := 0 to ComponentCount - 1 do
        if (Components[i] is TSpeedButton) and ((Components[i] as TSpeedButton).Parent = tsInteger) then
          if (Components[i] as TSpeedButton).Caption = '1' then
            Result := Result or (1 shl Components[i].Tag);
  end;
  {$IF Defined(USEREGION)}{$ENDREGION}{$IFEND}
begin
  if BufNode <> nil then
  begin
    MyData := vtStructure.GetNodeData(BufNode);
    if Assigned(MyData) and (MyData^._Type in [btInt, btHex]) then
    begin
      edIntDec.OnChange := nil;
      edIntHex.OnChange := nil;
      edIntBin.OnChange := nil;
      if Sender is TEdit then
        if (Sender as TEdit).Text <> '' then
        begin
          if Sender = edIntDec then
            i64 := UInt32(StrToInt64((Sender as TEdit).Text));
          if Sender = edIntHex then
            i64 := StrToInt64('$' + (Sender as TEdit).Text);
          if Sender = edIntBin then
            i64 := BinToInt((Sender as TEdit).Text);
        end
        else
          i64 := 0;
      if Sender is TSpeedButton then
      begin
        with Sender as TSpeedButton do
          if Caption = '1' then
            Caption := '0'
          else
            Caption := '1';
        i64 := GetButtons;
      end;
      edIntDec.Font.Color := clWindowText;
      edIntHex.Font.Color := clWindowText;
      edIntBin.Font.Color := clWindowText;
      case MyData^._Size of
        4: begin
             if Sender is TEdit then
               if (i64 shr 32) > 0 then
                 (Sender as TEdit).Font.Color := clRed;
             i64 := uint32(i64);
           end;
        2: begin
             if Sender is TEdit then
               if (i64 shr 16) > 0 then
                 (Sender as TEdit).Font.Color := clRed;
             i64 := uint16(i64);
           end
        else
        begin
          if Sender is TEdit then
            if (i64 shr 8) > 0 then
              (Sender as TEdit).Font.Color := clRed;
          i64 := uint8(i64);
        end;
      end;
      if Sender <> edIntDec then
        SetDec;
      if Sender <> edIntHex then
        SetHex;
      if Sender <> edIntBin then
        SetBin;
      if not (Sender is TSpeedButton) then
        SetButtons;
      edIntDec.OnChange := edChange;
      edIntHex.OnChange := edChange;
      edIntBin.OnChange := edChange;
      case MyData^._Size of
        4: uInt32(MyData^._Pointer^) := uint32(i64);
        2: uInt16(MyData^._Pointer^) := uint16(i64);
        else
          uInt8(MyData^._Pointer^) := uint8(i64);
      end;
      SetNodeText(vtStructure, BufNode);
    end;
  end
end;

procedure TfMain.edKeyPress(Sender: TObject; var Key: Char);
begin
  if not (((Sender = edIntDec) and (Key in ['0'..'9', Char(VK_BACK)])) or
          ((Sender = edIntHex) and (Key in ['0'..'9', 'A'..'F', 'a'..'f', Char(VK_BACK)])) or
          ((Sender = edFloat) and (Key in ['0'..'9', 'E', 'e', '$', Char(VK_BACK), '-', DecimalSeparator])) or
          ((Sender = edIntBin) and (Key in ['0'..'1', Char(VK_BACK)]))) then
     Key := #0;
  if ((Sender = edIntHex) and (Key in ['a'..'f'])) or ((Sender = edFloat) and (Key = 'e')) then
     Key := UpCase(Key);
end;

procedure TfMain.edSearchKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  if (Key = VK_RETURN) and (Trim(edSearch.Text) <> '') then
    btnSearch.Click;
end;

procedure TfMain.edSearchChange(Sender: TObject);
begin
  btnSearch.Enabled := Trim(edSearch.Text) <> '';
  stNotFound.Visible := False;
end;

procedure TfMain.edSearchKeyPress(Sender: TObject; var Key: Char);
begin
  if Key = #13 then
    Key := #0;
end;

procedure TfMain.WMSysCommand(var Msg: TWMSysCommand);
begin
  if IsVista then
    case Msg.CmdType and $FFF0 of
      SC_MINIMIZE, SC_RESTORE, SC_MAXIMIZE:
      begin
        Msg.Result := DefWindowProc(Self.Handle, Msg.Msg, Msg.CmdType, TMessage(Msg).lParam);
        ShowWindow(Application.Handle, SW_HIDE);
      end
      else
        inherited;
    end
  else
    inherited;
end;

procedure TfMain.AppMinimize(Sender: TObject);
begin
  ShowWindow(Handle, SW_SHOW);
  Application.ProcessMessages;
  ShowWindow(Handle, SW_MINIMIZE);
end;

procedure TfMain.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  if IsVista then
  begin
    Params.ExStyle := Params.ExStyle and not WS_EX_TOOLWINDOW or WS_EX_APPWINDOW;
    Params.WndParent := GetDesktopWindow;
  end;
end;

procedure TfMain.WMUpdateState(var M: TWMUpdateUIState);
begin
  inherited;
  if IsVista then
    DoRepaint(Self);
end;

procedure TfMain.FormCreate(Sender: TObject);
var
  i: integer;
begin
  if IsVista then
    SetVistaFonts(Self);
  InitializeTaskbarAPI;
  AppendMenu(GetSystemMenu(Application.Handle, FALSE), MF_SEPARATOR, 0, ' ');
  AppendMenu(GetSystemMenu(Application.Handle, FALSE), MF_STRING, WM_ABOUT, 'About...');
  AppendMenu(GetSystemMenu(Handle, FALSE), MF_SEPARATOR, 0, ' ');
  AppendMenu(GetSystemMenu(Handle, FALSE), MF_STRING, WM_ABOUT, 'About...');
  Application.OnMessage := WndMsg;
  Caption := Caption + ' ' + DateToStr(UnixTimeToDateTime(GetImageLinkTimeStamp(ParamStr(0))));
  {$IF Defined(USEREGION)}{$REGION 'Create controls'}{$IFEND}
  imLogo.Picture.Bitmap.LoadFromResourceName(hInstance, 'SM');
  TXPMenu.Create(Self);
  btnOpen := CreateButton('Open', True, clBlack, 2, 2, 25, 60, 0, 0, [akLeft, akTop], Self, pnWorkplace, imImages, btnOpenClick, nil);
  btnFirstACHR := CreateButton('Player ACHR', True, clBlack, btnOpen.Left + btnOpen.Width + 4, 2, 25, 75, 0, 1, [akLeft, akTop], Self, pnWorkplace, nil, btnFirstACHRClick, nil);
  btnFirstACHR.Visible := False;
  btnFirstNPC := CreateButton('Player NPC', True, clBlack, btnFirstACHR.Left + btnFirstACHR.Width + 4, 2, 25, 70, 0, 2, [akLeft, akTop], Self, pnWorkplace, nil, btnFirstNPCClick, nil);
  btnFirstNPC.Visible := False;
  btnDelete := CreateButton('Delete', True, clBlack, btnFirstNPC.Left + btnFirstNPC.Width + 4, 2, 25, 75, 11, 4, [akLeft, akTop], Self, pnWorkplace, imImages, btnDeleteClick, nil);
  btnAdd := CreateButton('Add', True, clBlack, btnDelete.Left + btnDelete.Width + 4, 2, 25, 60, 13, 5, [akLeft, akTop], Self, pnWorkplace, imImages, btnAddClick, nil);
  btnDelete.Visible := False;
  btnAdd.Visible := False;
  btnSave := CreateButton('Save', True, clBlack, pnWorkplace.ClientWidth - 60, 2, 25, 60, 1, 6, [akRight, akTop], Self, pnWorkplace, imImages, btnSaveClick, nil);
  btnSave.Visible := False;
  btnExport := CreateButton('Export', True, clBlack, 2, 2, 25, 75, 4, 0, [akLeft, akTop], Self, pnData, imImages, btnExportClick, nil);
  btnImport := CreateButton('Import', True, clBlack, 83, 2, 25, 75, 5, 1, [akLeft, akTop], Self, pnData, imImages, btnImportClick, nil);
  btnZero := CreateButton('Fill zeros', True, clBlack, 164, 2, 25, 75, -1, 2, [akLeft, akTop], Self, pnData, nil, btnZeroClick, nil);
  btnForms := CreateButton('Export list', True, clBlack, 2, 2, 25, 100, 4, 0, [akLeft, akTop], Self, pnForms, imImages, btnFormsClick, nil);
  btnFormsData := CreateButton('Export data', True, clBlack, 110, 2, 25, 100, 6, 1, [akLeft, akTop], Self, pnForms, imImages, btnFormsDataClick, nil);
  btnGD01All := CreateButton('Export list', True, clBlack, 2, 2, 25, 100, 4, 0, [akLeft, akTop], Self, pnGD01All, imImages, btnGD01AllClick, nil);
  btnGD04 := CreateButton('Export list', True, clBlack, 2, 2, 25, 100, 4, 0, [akLeft, akTop], Self, pnGD04, imImages, btnGD04Click , nil);
  btnSearch := CreateButton('Search', False, clBlack, edSearch.Left + edSearch.Width + 4, edSearch.Top - 4, 25, 75, 7, 2, [akLeft, akTop], Self, pnSearch, fMain.imImages, btnSearchClick, nil);
  vtStructure := TVirtualStringTree.Create(Self);
  with vtStructure do
  begin
    Parent := pnStructure;
    Align := alClient;
    TabOrder := 1;
    Visible := False;
    Color := $F7F7F7;
    Colors.UnfocusedSelectionColor := clHighlight;
  end;
  odOpen := TExOpenDialog.Create(Self);
  with odOpen do
  begin
    Filter := 'Fallout 4 save game files (*.fos)|*.fos|All files (*.*)|*.*';
    Options := [ofHideReadOnly, ofPathMustExist, ofFileMustExist, ofEnableSizing];
    OnSelectionChange := odOpenSelectionChange;
  end;
  with pcEditors do
  begin
    for i := 0 to PageCount - 1 do
      Pages[i].TabVisible := False;
    ActivePageIndex := 0;
    Style := tsFlatButtons;
    Top := btnOpen.Top + btnOpen.Height + 2;
    Left := 0;
    Height := pnStatus.Top - Top;
    Width := Parent.ClientWidth;
  end;
  imLogo.Left := pcEditors.Left;
  imLogo.Top := pcEditors.Top;
  imLogo.Height := pcEditors.Height;
  imLogo.Width := pcEditors.Width;
  with vtStructure do
  begin
    IncrementalSearch := isVisibleOnly;
    NodeDataSize := SizeOf(TMyRec);
    OnGetText := vtStructureGetText;
    OnFreeNode := vtStructureFreeNode;
    OnInitNode := vtStructureInitNode;
    OnChange := vtStructureChange;
    OnKeyUp := vtStructureKeyUp;
  end;
  heEdit := THexEditor.Create(Self);
  with heEdit do
  begin
    Parent := tsData;
    Align := alClient;
  end;
  {$IF Defined(USEREGION)}{$ENDREGION}{$IFEND}
  PrepareDataToShow;
  with sgHeader do
  begin
    Selection := TGridRect(Rect(-1, -1, -1, -1));
    ColWidths[0] := 100;
    Cells[0, 0] := 'Address';
    ColWidths[1] := 5;
    ColWidths[18] := 15;
    for i := 0 to 15 do
    begin
      ColWidths[i + 2] := 30;
      ColWidths[i + 19] := 10;
      if i < 10 then
      begin
        Cells[i + 2, 0] := Char(Ord('0') + i);
        Cells[i + 19, 0] := Char(Ord('0') + i);
      end
      else
      begin
        Cells[i + 2, 0] := Char(Ord('A') + (i - 10));
        Cells[i + 19, 0] := Char(Ord('A') + (i - 10));
      end;
    end;
  end;
  if IsVista then
    Application.OnMinimize := AppMinimize;
end;

procedure TfMain.FormDestroy(Sender: TObject);
begin
  FreeFOSFile(FOSFile);
end;

procedure TfMain.FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  if Key = VK_F3 then
    if Trim(edSearch.Text) = '' then
      pnSearch.Visible := pnStructure.Visible and (not pnSearch.Visible)
    else
      btnSearch.Click;
end;

procedure TfMain.FormResize(Sender: TObject);
begin
  pnStatus.Caption := MinimizeName(sFileName, THackPanel(pnStatus).Canvas, pnStatus.Parent.ClientWidth - 8);
  btnSave.Left := btnSave.Parent.ClientWidth - btnSave.Width - 2;
  spSplit.Left := pnStructure.Width;
  if pcEditors.ActivePage = tsData then
    heEdit.UpdateSize;
  lbLink.Top := pnWorkplace.ClientHeight - pnStatus.Height - lbLink.Height - 4;
  lbLink.Left := (pnWorkplace.ClientWidth - lbLink.Width) div 2;
end;

procedure TfMain.lbLinkClick(Sender: TObject);
begin
  if Sender is TLabel then
    ShellExecute(Application.Handle, PChar('open'), PChar((Sender as TLabel).Caption), nil, nil, SW_SHOW);
end;

procedure TfMain.lvCustomDrawItem(Sender: TCustomListView; Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
var
  i: Integer;
  Rect: TRect;
begin
  with Sender as TListView do
  begin
    DefaultDraw := False;
    if Item.Selected then
      Canvas.Brush.Color := clHighlight
    else
    begin
      Canvas.Brush.Color := $FCFCFC;
      if Odd(Item.Index) then
        Canvas.Brush.Color := Canvas.Brush.Color - $0F0F0F;
    end;
    if Item.Selected then
      Canvas.Font.Color := clHighlightText
    else
      Canvas.Font.Color := clBlack;
    Rect := Item.DisplayRect(drBounds);
    for i := 0 to Columns.Count - 1 do
    begin
      Rect.Right := Rect.Left + Integer(SendMessage(Handle, LVM_GETCOLUMNWIDTH, i, 0));
      if i = 0 then
        Canvas.TextRect(Rect, Rect.Left + 4, Rect.Top, Item.Caption)
      else
        Canvas.TextRect(Rect, Rect.Left + 4, Rect.Top, Item.SubItems[i - 1]);
      Rect.Left := Rect.Right;
    end;
    if ItemFocused = Item then
      Canvas.DrawFocusRect(Item.DisplayRect(drBounds));
  end;
end;

procedure TfMain.lvEdited(Sender: TObject; Item: TListItem; var S: string);
var
  Value: uint32;
begin
  S := Trim(S);
  with Item do
    if TryStrToInt(S, Integer(Value)) then
    begin
      Move(Value, Data^, SizeOf(uint32));
      Caption := IntToStr(Value);
    end
    else
      S := Caption;
end;

procedure TfMain.lvFormsColumnClick(Sender: TObject; Col: TListColumn);
var
  i: Integer;
begin
  if Sender is TListView then
    with Sender as TListView do
    begin
      if Col.Index = (Tag shr 1) then
        if Boolean(Tag and 1) then
          Tag := Tag and $FFFFFFFE
        else
          Tag := Tag or 1
      else
        Tag := (Col.Index shl 1) or (Tag and 1);
      if Boolean(Tag and 1) then
        QuickSortForms(FormViews, 0, Length(FormViews) - 1, True, Col.Index)
      else
        QuickSortForms(FormViews, 0, Length(FormViews) - 1, False, Col.Index);
      for i := 0 to (Sender as TListView).Columns.Count - 1 do
        SetColumnSortOrder(Sender as TListview, i, Tag shr 1, Boolean(Tag and 1));
      Repaint;
    end;
end;

procedure TfMain.lvFormsData(Sender: TObject; Item: TListItem);
begin
  if Item.Index < Length(FormViews) then
      with FormViews[Item.Index], Item do
      begin
        Data := Pointer(No);
        Caption := IntToStr(No);
        SubItems.Add(RefIDToString(Form.formID));
        SubItems.Add(IntToHex(Form.changeFlags, 8));
        SubItems.Add(IntToStr(Form.changeFlags));
        SubItems.Add(IntToStr(Form._type));
        SubItems.Add(Copy(GetFormString(Form._type), 1, 4));
        SubItems.Add(IntToStr(Form.version));
        SubItems.Add(IntToStr(Form.length1));
        if Form.length2 > 0 then
          SubItems.Add(IntToStr(Form.length2))
        else
          SubItems.Add('');
      end;
end;

procedure TfMain.lvFormsDblClick(Sender: TObject);
begin
  if (lvForms.Selected <> nil) and (lvForms.Selected.Data <> nil) then
    with vtStructure do
    begin
      Expanded[GetFirst(False)] := True;
      Expanded[GetChild(GetFirst(False), 10)] := True;
      FocusedNode := GetChild(GetChild(GetFirst(False), 10), Integer(lvForms.Selected.Data) - 1);
      Expanded[FocusedNode] := True;
    end;
end;

procedure TfMain.lvFormsKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  case Key of
    VK_DELETE:
      if btnDelete.Visible then
        DeleteSelectedForms;
  end;
end;

procedure TfMain.lvFormsSelectItem(Sender: TObject; Item: TListItem; Selected: Boolean);
begin
  if Sender is TListView then
    with Sender as TListView do
      btnDelete.Visible := btnDelete.Visible or (Selected <> nil);
  if btnDelete.Visible and btnAdd.Visible and (btnAdd.Left = btnDelete.Left) then
    btnAdd.Left := btnDelete.Left + btnDelete.Width + 4;
end;

procedure TfMain.lvGD02Edited(Sender: TObject; Item: TListItem; var S: string);
var
  Value: uint32;
begin
  S := Trim(S);
  with Item do
    case Index  of
      1, 6:
        if TryStrToInt('$' + S, Integer(Value)) and (Value < $1000000) then
        begin
          Move(Value, Data^, SizeOf(TRefID));
          Caption := IntToHex(Value, 6);
        end
        else
          S := Caption;
      else
        if TryStrToInt(S, Integer(Value)) then
        begin
          Move(Value, Data^, SizeOf(uint32));
          Caption := IntToStr(Value);
        end
        else
          S := Caption;
    end;
end;

procedure TfMain.lvGD04Edited(Sender: TObject; Item: TListItem; var S: string);
var
  Value: float32;
begin
  S := Trim(S);
  with Item do
    if TryStrToFloat(S, Value) then
    begin
      Move(Value, Data^, SizeOf(float32));
      Caption := FloatToStr(Value);
    end
    else
      S := Caption;
end;

procedure TfMain.lvColumnClick(Sender: TObject; Col: TListColumn);
var
  i: Integer;
begin
  if Sender is TListView then
    with Sender as TListView do
    begin
      if Col.Index = (Tag shr 1) then
        if Boolean(Tag and 1) then
          Tag := Tag and $FFFFFFFE
        else
          Tag := Tag or 1
      else
        Tag := (Col.Index shl 1) or (Tag and 1);
      if Boolean(Tag and 1) then
        TListView(Sender).CustomSort(@SortByColumnAscend, Col.Index)
      else
        TListView(Sender).CustomSort(@SortByColumnDescend, Col.Index);
      for i := 0 to (Sender as TListView).Columns.Count - 1 do
        SetColumnSortOrder(Sender as TListview, i, Tag shr 1, Boolean(Tag and 1));
    end;
end;

procedure TfMain.lvKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  if Sender is TListView then
    with Sender as TListView do
      if Key = VK_F2 then
        if Assigned(Selected) then
          Selected.EditCaption;
end;

const
  VK_C = $43;

procedure TfMain.vtStructureKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
var
  MyData: PMyRec;
begin
  if Sender is TVirtualStringTree then
    with Sender as TVirtualStringTree do
      case Key of
        VK_C:
          if (GetFirstSelected <> nil) and (ssCtrl in Shift) then
          begin
            MyData := GetNodeData(GetFirstSelected);
            if Assigned(MyData) then
              Clipboard.AsText := MyData^._Caption;
          end;
        VK_DELETE:
          if btnDelete.Visible then
            DeleteSelectedObject;
        VK_INSERT:
          if btnAdd.Visible then
            AddObject;
      end;
end;

procedure TfMain.sgHeaderDrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState);
begin
  with (Sender as TStringGrid), Canvas do
  begin
    Font := (Sender as TStringGrid).Font;
    Brush.Color := $00F0F0F0;
    FillRect(Rect);
    Pen.Color := clBtnText;
    MoveTo(Rect.Left, Rect.Bottom - 1);
    LineTo(Rect.Right, Rect.Bottom - 1);
    DrawText(Canvas.Handle, PChar(Cells[ACol, ARow]), Length(Cells[ACol, ARow]), Rect, DT_CENTER or DT_SINGLELINE);
  end;
end;

procedure TfMain.spSplitMoved(Sender: TObject);
begin
  FormResize(Self);
end;

procedure TfMain.tsScreenShow(Sender: TObject);
{$IF Defined(USEREGION)}{$REGION 'tsScreenShow'}{$IFEND}
var
  bmTmp: TBitmap;
begin
  with FOSFile, Header do
    if magic = C_F4FOS then
      if not ((shotWidth > 4096) or (shotHeight > 4096)) then
      begin
        bmTmp := ScreenShotToBMP(shotWidth, shotHeight, screenshotData);
        try
          imScreen.Picture.Assign(bmTmp);
        finally
          bmTmp.Free;
        end;
      end
      else
        imMain.Picture := nil;
  cbScreenCenter.OnClick := nil;
  cbScreenStrech.OnClick := nil;
  cbScreenProportional.OnClick := nil;
  cbScreenCenter.Checked := imScreen.Center;
  cbScreenStrech.Checked := imScreen.Stretch;
  cbScreenProportional.Checked := imScreen.Proportional;
  cbScreenCenter.OnClick := cbScreenClick;
  cbScreenStrech.OnClick := cbScreenClick;
  cbScreenProportional.OnClick := cbScreenClick;
end; {$IF Defined(USEREGION)}{$ENDREGION}{$IFEND}

procedure TfMain.tsStringShow(Sender: TObject);
{$IF Defined(USEREGION)}{$REGION 'tsStringShow'}{$IFEND}
var
  MyData: PMyRec;
begin
  if BufNode <> nil then
  begin
    MyData := vtStructure.GetNodeData(BufNode);
    if Assigned(MyData) and (MyData^._Type = btString) and (MyData^._Pointer <> nil) then
      edWString.Text := wstringToString(wstring(MyData^._Pointer^));
  end;
end; {$IF Defined(USEREGION)}{$ENDREGION}{$IFEND}

procedure TfMain.tvFlagsCustomDrawItem(Sender: TCustomTreeView; Node: TTreeNode; State: TCustomDrawState; var DefaultDraw: Boolean);
begin
  if Node <> nil then
    with Sender.Canvas.Font do
      if IsChecked(TTreeView(Sender), Node.Index) then
        Style := [fsBold]
      else
        Style := [];
end;

procedure TfMain.tsFlagsShow(Sender: TObject);
{$IF Defined(USEREGION)}{$REGION 'tsFlagsShow'}{$IFEND}
var
  MyData: PMyRec;
  i: Integer;
begin
  if BufNode <> nil then
  begin
    MyData := vtStructure.GetNodeData(BufNode);
    if Assigned(MyData) and (MyData^._Type = btFlags) and (MyData^._Pointer <> nil) then
    with tvFlags do
    begin
      Items.Clear;
      Items.Add(nil, ' 0 - CHANGE_FORM_FLAGS');  //0
      Items.Add(nil, ' 1 - CHANGE_REFR_MOVE / CHANGE_QUEST_FLAGS'); //1
      Items.Add(nil, ' 2 - CHANGE_REFR_HAVOK_MOVE'); //2
      Items.Add(nil, ' 3 - CHANGE_REFR_CELL_CHANGED'); //3
      Items.Add(nil, ' 4 - CHANGE_REFR_SCALE'); //4
      Items.Add(nil, ' 5 - CHANGE_REFR_INVENTORY'); //5
      Items.Add(nil, ' 6 - CHANGE_REFR_EXTRA_OWNERSHIP'); //6
      Items.Add(nil, ' 7 - CHANGE_REFR_BASEOBJECT'); //7
      Items.Add(nil, ' 8'); //8
      Items.Add(nil, ' 9'); //9
      Items.Add(nil, ' 10 - CHANGE_OBJECT_EXTRA_ITEM_DATA'); //10
      Items.Add(nil, ' 11'); //11
      Items.Add(nil, ' 12 - CHANGE_OBJECT_EXTRA_LOCK'); //12
      Items.Add(nil, ' 13'); //13
      Items.Add(nil, ' 14'); //14
      Items.Add(nil, ' 15'); //15
      Items.Add(nil, ' 16'); //16
      Items.Add(nil, ' 17'); //17
      Items.Add(nil, ' 18'); //18
      Items.Add(nil, ' 19'); //19
      Items.Add(nil, ' 20'); //20
      Items.Add(nil, ' 21 - CHANGE_OBJECT_EMPTY'); //21
      Items.Add(nil, ' 22'); //22
      Items.Add(nil, ' 23 - CHANGE_OBJECT_OPEN_STATE'); //23
      Items.Add(nil, ' 24'); //24
      Items.Add(nil, ' 25 - CHANGE_REFR_PROMOTED'); //25
      Items.Add(nil, ' 26 - CHANGE_REFR_EXTRA_ACTIVATING_CHILDREN / CHANGE_QUEST_ALREADY_RUN'); //26
      Items.Add(nil, ' 27 - CHANGE_REFR_LEVELED_INVENTORY / CHANGE_QUEST_INSTANCES'); //27
      Items.Add(nil, ' 28 - CHANGE_REFR_ANIMATION / CHANGE_QUEST_RUNDATA'); //28
      Items.Add(nil, ' 29 - CHANGE_REFR_EXTRA_ENCOUNTER_ZONE / CHANGE_QUEST_OBJECTIVES'); //29
      Items.Add(nil, ' 30'); //30
      Items.Add(nil, ' 31 - CHANGE_REFR_EXTRA_GAME_ONLY / CHANGE_QUEST_STAGES'); //31
      SetWindowLong(Handle, GWL_STYLE, GetWindowLong(Handle, GWL_STYLE) or TVS_CHECKBOXES);
      for i := 0 to 31 do
        if (uint32(MyData^._Pointer^) and (1 shl i)) <> 0 then
          SetChecked(tvFlags, i, True);
    end;
  end;
end;  {$IF Defined(USEREGION)}{$ENDREGION}{$IFEND}

procedure TfMain.tsFloatShow(Sender: TObject);
{$IF Defined(USEREGION)}{$REGION 'tsFloatShow'}{$IFEND}
var
  MyData: PMyRec;
begin
  if BufNode <> nil then
  begin
    MyData := vtStructure.GetNodeData(BufNode);
    if Assigned(MyData) and (MyData^._Type = btFloat) and (MyData^._Pointer <> nil) then
      if MyData^._Size = 8 then
        edFloat.Text := FloatToStr(Double(MyData^._Pointer^))
      else
        edFloat.Text := FloatToStr(Single(MyData^._Pointer^));
  end;
end; {$IF Defined(USEREGION)}{$ENDREGION}{$IFEND}

procedure TfMain.tsFormsShow(Sender: TObject);
{$IF Defined(USEREGION)}{$REGION 'tsFormsShow'}{$IFEND}
var
  i: integer;
begin
  lvForms.Clear;
  with FOSFile, lvForms do
    if magic = C_F4FOS then
    begin
      SetLength(FormViews, Length(changeForms));
      for i := 0 to Length(changeForms) - 1 do
      begin
        CopyMemory(@FormViews[i].Form, @changeForms[i]^, SizeOf(TFormView) - SizeOf(Integer));
        FormViews[i].No := i + 1;
      end;
      OwnerData := True;
      Items.Count := Length(changeForms);
      if Boolean(Tag and 1) then
        QuickSortForms(FormViews, 0, Length(FormViews) - 1, True, Tag shr 1)
      else
        QuickSortForms(FormViews, 0, Length(FormViews) - 1, False, Tag shr 1);
      for i := 0 to Columns.Count - 1 do
        SetColumnSortOrder(lvForms, i, Tag shr 1, Boolean(Tag and 1));
  end;
end;  {$IF Defined(USEREGION)}{$ENDREGION}{$IFEND}

procedure TfMain.tsGD01Show(Sender: TObject);
{$IF Defined(USEREGION)}{$REGION 'tsGD01Show'}{$IFEND}
var
  iCount, iPos, i, iAddress: Integer;
  MyData: PMyRec;
  iCategory: uInt8;
  iValue: uInt32;
  sDescription: string;
begin
  if not IsVista and (tsGD01.Tag = 0) then
  begin
    SetListHeadColor(lvGD01Type1.Handle, lvGD01Type1Proc, Longint(@lvGD01Type1NewProc));
    SetListHeadColor(lvGD01Type2.Handle, lvGD01Type2Proc, Longint(@lvGD01Type2NewProc));
    SetListHeadColor(lvGD01Type3.Handle, lvGD01Type3Proc, Longint(@lvGD01Type3NewProc));
    SetListHeadColor(lvGD01Type4.Handle, lvGD01Type4Proc, Longint(@lvGD01Type4NewProc));
    SetListHeadColor(lvGD01Type5.Handle, lvGD01Type5Proc, Longint(@lvGD01Type5NewProc));
    SetListHeadColor(lvGD01Type6.Handle, lvGD01Type6Proc, Longint(@lvGD01Type6NewProc));
    SetListHeadColor(lvGD01Type7.Handle, lvGD01Type7Proc, Longint(@lvGD01Type7NewProc));
    SetListHeadColor(lvGD01All.Handle, lvGD01AllProc, Longint(@lvGD01AllNewProc));
    tsGD01.Tag := 1;
  end;
  pcGD01.ActivePageIndex := pcGD01.PageCount - 1;
  lvGD01Type1.Clear;
  lvGD01Type1.Tag := 0;
  lvGD01Type2.Clear;
  lvGD01Type2.Tag := 0;
  lvGD01Type3.Clear;
  lvGD01Type3.Tag := 0;
  lvGD01Type4.Clear;
  lvGD01Type4.Tag := 0;
  lvGD01Type5.Clear;
  lvGD01Type5.Tag := 0;
  lvGD01Type6.Clear;
  lvGD01Type6.Tag := 0;
  lvGD01Type7.Clear;
  lvGD01Type7.Tag := 0;
  lvGD01All.Clear;
  lvGD01All.Tag := 0;
  if BufNode <> nil then
  begin
    MyData := vtStructure.GetNodeData(BufNode);
    if Assigned(MyData) and (MyData^._Type = btGlobal) and (MyData^._Size > 0) then
    begin
      iPos := 0;
      iCount := Read_uint32(TBytes(MyData^._Pointer^), iPos);
      if iPos > 0 then
        for i := 0 to iCount - 1 do
        begin
          iCategory := 0;
          iValue := 0;
          sDescription := wstringToString(Read_wstring(TBytes(MyData^._Pointer^), iPos));
          if iPos > 0 then
            iCategory := Read_uint8(TBytes(MyData^._Pointer^), iPos);
          iAddress := iPos;
          if iPos > 0 then
            iValue := Read_int32(TBytes(MyData^._Pointer^), iPos);
          if iPos > 0 then
          begin
            case TStatCategory(iCategory) of
              scGeneral:
                with lvGD01Type1.Items.Add do
                begin
                  Caption := IntToStr(iValue);
                  Data := @(TBytes(MyData^._Pointer^)[iAddress]);
                  SubItems.Add(sDescription);
                end;
              scQuest:
                with lvGD01Type2.Items.Add do
                begin
                  Caption := IntToStr(iValue);
                  Data := @(TBytes(MyData^._Pointer^)[iAddress]);
                  SubItems.Add(sDescription);
                end;
              scCombat:
                with lvGD01Type3.Items.Add do
                begin
                  Caption := IntToStr(iValue);
                  Data := @(TBytes(MyData^._Pointer^)[iAddress]);
                  SubItems.Add(sDescription);
                end;
              scCrafting:
                with lvGD01Type4.Items.Add do
                begin
                  Caption := IntToStr(iValue);
                  Data := @(TBytes(MyData^._Pointer^)[iAddress]);
                  SubItems.Add(sDescription);
                end;
              scCrime:
                with lvGD01Type5.Items.Add do
                begin
                  Caption := IntToStr(iValue);
                  Data := @(TBytes(MyData^._Pointer^)[iAddress]);
                  SubItems.Add(sDescription);
                end;
              scTowns:
                with lvGD01Type6.Items.Add do
                begin
                  Caption := IntToStr(iValue);
                  Data := @(TBytes(MyData^._Pointer^)[iAddress]);
                  SubItems.Add(sDescription);
                end;
              scOther:
                with lvGD01Type7.Items.Add do
                begin
                  Caption := IntToStr(iValue);
                  Data := @(TBytes(MyData^._Pointer^)[iAddress]);
                  SubItems.Add(sDescription);
                end;
            end;
            with lvGD01All.Items.Add do
            begin
              Caption := IntToStr(iValue);
              Data := @(TBytes(MyData^._Pointer^)[iAddress]);
              SubItems.Add(GetCategoryString(iCategory));
              SubItems.Add(sDescription);
            end;
          end
          else
          begin
            MessageBox(Handle, 'Input buffer size in not enough!', 'Error', MB_OK or MB_ICONSTOP);
            Break
          end;
        end;
      if (MyData^._Size <> iPos) and (iPos > 0) then
        MessageBox(Handle, 'Not all data are parsed', 'Warning', MB_OK or MB_ICONWARNING);
    end;
    pcGD01.ActivePage := tsGD01Type1;
  end;
  BufNode := nil;
end; {$IF Defined(USEREGION)}{$ENDREGION}{$IFEND}

procedure TfMain.tsGD04Show(Sender: TObject);
{$IF Defined(USEREGION)}{$REGION 'tsGD04Show'}{$IFEND}
var
  iPos, iCount, i, iAddress: Integer;
  MyData: PMyRec;
  fValue: float;
  rValue: TRefID;
begin
  if not IsVista and (tsGD04.Tag = 0) then
  begin
    SetListHeadColor(lvGD04.Handle, lvGD04Proc, Longint(@lvGD04NewProc));
    tsGD04.Tag := 1;
  end;
  lvGD04.Clear;
  lvGD04.Tag := 0;
  lvGD04.Columns[0].Width := 200;
  if BufNode <> nil then
  begin
    MyData := vtStructure.GetNodeData(BufNode);
    if Assigned(MyData) and (MyData^._Type = btGlobal) and (MyData^._Size > 0) then
    begin
      iPos := 0;
      fValue := 0.0;
      iCount := Read_vsval(TBytes(MyData^._Pointer^), iPos);
      if iPos > 0 then
        for i := 0 to iCount - 1 do
        begin
          if iPos > 0 then
            rValue := Read_RefID(TBytes(MyData^._Pointer^), iPos);
          iAddress := iPos;
          if iPos > 0 then
            fValue := Read_float32(TBytes(MyData^._Pointer^), iPos);
          if iPos > 0 then
            with lvGD04.Items.Add do
            begin
              Caption := FloatToStr(fValue);
              Data := @(TBytes(MyData^._Pointer^)[iAddress]);
              SubItems.Add(RefIDToString(rValue));
            end;
          if iPos = 0 then
          begin
            MessageBox(Handle, 'Input buffer size in not enough!', 'Error', MB_OK or MB_ICONSTOP);
            Break
          end;
        end;
      if (MyData^._Size <> iPos) and (iPos > 0) then
        MessageBox(Handle, 'Not all data are parsed', 'Warning', MB_OK or MB_ICONWARNING);
    end;
  end;
  lvGD04.Columns[0].Width := lvGD04.Columns[0].Width + 1;
  BufNode := nil;
end; {$IF Defined(USEREGION)}{$ENDREGION}{$IFEND}

procedure TfMain.tsIntegerShow(Sender: TObject);
{$IF Defined(USEREGION)}{$REGION 'tsIntegerShow'}{$IFEND}
var
  i: Integer;
  MyData: PMyRec;
begin
  if BufNode <> nil then
  begin
    MyData := vtStructure.GetNodeData(BufNode);
    if Assigned(MyData) and (MyData^._Type in [btInt, btHex]) then
    begin
      with Self do
        for i := 0 to ComponentCount - 1 do
          if (Components[i] is TSpeedButton) and ((Components[i] as TSpeedButton).Parent = tsInteger) then
            if StrToInt(Copy(Components[i].Name, 3, Length(Components[i].Name))) < (MyData^._Size * 8) then
            begin
              Components[i].Tag := StrToInt(Copy(Components[i].Name, 3, Length(Components[i].Name)));
              (Components[i] as TSpeedButton).OnClick := edChange;
              (Components[i] as TSpeedButton).Visible := True;
            end
            else
            begin
              Components[i].Tag := -1;
              (Components[i] as TSpeedButton).OnClick := nil;
              (Components[i] as TSpeedButton).Visible := False;
            end;
      uHelper.TEdit(edIntDec).Alignment := taRightJustify;
      uHelper.TEdit(edIntHex).Alignment := taRightJustify;
      uHelper.TEdit(edIntBin).Alignment := taRightJustify;
      lbRange.Caption := 'Value range:';
      case MyData^._Size of
        4: begin
             edIntDec.Text := IntToStr(uint32(MyData^._Pointer^));
             lbRange.Caption := lbRange.Caption + #13'0..4294967295'#13#13'Orginal value:'#13 + IntToStr(uint32(MyData^._Pointer^));
           end;
        2: begin
             edIntDec.Text := IntToStr(uint16(MyData^._Pointer^));
             lbRange.Caption := lbRange.Caption + #13'0..65525'#13#13'Orginal value:'#13 + IntToStr(uint16(MyData^._Pointer^));;
           end
        else
        begin
          edIntDec.Text := IntToStr(uint8(MyData^._Pointer^));
          lbRange.Caption := lbRange.Caption + #13'0..255'#13#13'Orginal value:'#13 + IntToStr(uint8(MyData^._Pointer^));;
        end;
      end;
    end;
  end;
end; {$IF Defined(USEREGION)}{$ENDREGION}{$IFEND}

procedure TfMain.tsMainShow(Sender: TObject);
{$IF Defined(USEREGION)}{$REGION 'tsMainShow'}{$IFEND}
var
  bmTmp: TBitmap;
begin
  with FOSFile, Header do
    if magic = C_F4FOS then
    begin
      if not ((shotWidth > 4096) or (shotHeight > 4096)) then
      begin
        bmTmp := ScreenShotToBMP(shotWidth, shotHeight, screenshotData);
        try
          imMain.Picture.Assign(bmTmp);
        finally
          bmTmp.Free;
        end;
        imMain.Height := bmTmp.Height + 16;
      end
      else
      begin
        imMain.Picture := nil;
        imMain.Height := 0;
      end;
      lbMain.Top := imMain.Height;
      lbMain.Caption := 'Save No: ' + IntToStr(saveNumber) +
                     #13'Player: ' + wstringToString(playerName) +
                     #13'Level: ' + IntToStr(playerLevel) +
                     #13'Race: ' + wstringToString(playerRaceEditorId) +
                     #13'Location: ' + wstringToString(playerLocation) +
                     #13'Date: ' + wstringToString(gameDate);
    end;
end; {$IF Defined(USEREGION)}{$ENDREGION}{$IFEND}

procedure TfMain.tsReadCalcShow(Sender: TObject);
{$IF Defined(USEREGION)}{$REGION 'tsReadCalcShow'}{$IFEND}
var
  i64: Int64;
  MyData: PMyRec;
  sVal: string;
begin
  if BufNode <> nil then
  begin
    MyData := vtStructure.GetNodeData(BufNode);
    if Assigned(MyData) and (MyData^._Type in [btReadOnly, btCalc]) then
    begin
      uHelper.TEdit(edRODec).Alignment := taRightJustify;
      uHelper.TEdit(edROHex).Alignment := taRightJustify;
      uHelper.TEdit(edROBin).Alignment := taRightJustify;
      edRODec.Visible := MyData^._Pointer <> nil;
      edROHex.Visible := edRODec.Visible;
      edROBin.Visible := edRODec.Visible;
      lbRODec.Visible := edRODec.Visible;
      lbROHex.Visible := edRODec.Visible;
      lbROBin.Visible := edRODec.Visible;
      if edRODec.Visible then
      begin
        case MyData^._Size of
          4: i64 := uint32(MyData^._Pointer^);
          2: i64 := uint16(MyData^._Pointer^);
          else
            i64 := uint8(MyData^._Pointer^);
        end;
        edRODec.Text := IntToStr(i64);
        sVal := IntToHex(i64, MyData^._Size * 2);
        while (sVal <> '') and (sVal[1] = '0') do
          Delete(sVal, 1, 1);
        edROHex.Text := sVal;
        sVal := IntToBin(i64, MyData^._Size * 8);
        while (sVal <> '') and (sVal[1] = '0') do
          Delete(sVal, 1, 1);
        edROBin.Text := sVal;
        while (sVal <> '') and (sVal[1] = '0') do
          Delete(sVal, 1, 1);
      end;
      lbReadOnly.Visible := MyData^._Type = btReadOnly;
      lbCalc.Visible := MyData^._Type = btCalc;
    end;
  end;
end; {$IF Defined(USEREGION)}{$ENDREGION}{$IFEND}

procedure TfMain.PrepareDataToShow;
begin
  BufNode := nil;
  vtStructure.Clear;
  if sFileName <> '' then
  begin
    pnStatus.Visible := True;
    btnSave.Visible := True;
    FillTree;
    vtStructure.Visible := True;
    spSplit.Visible := True;
    pnStructure.Visible := True;
    FormResize(Self);
    vtStructureChange(vtStructure, vtStructure.GetFirst(False));
    btnFirstACHR.Visible := True;
    btnFirstNPC.Visible := True;
  end
  else
  begin
    FreeFOSFile(FOSFile);
    vtStructure.Visible := False;
    spSplit.Visible := False;
    pnStructure.Visible := False;
    pnStatus.Visible := False;
    pcEditors.Visible := False;
    btnSave.Visible := False;
    btnFirstACHR.Visible := False;
    btnFirstNPC.Visible := False;
  end;
end;

procedure TfMain.vtStructureChange(Sender: TBaseVirtualTree; Node: PVirtualNode);
var
  i: Integer;
  bShow: Boolean;
  MyData: PMyRec;
  fTmp: Double;
  iuInt32: uInt32;
begin
  bShow := True;
  with Sender do
  begin
    if Node <> nil then
    {$IF Defined(USEREGION)}{$REGION 'Node selected'}{$IFEND}
    begin
      BufNode := nil;
      MyData := GetNodeData(Node);
      if Assigned(MyData) then
        with MyData^ do
          case _Type of
            btNone:
              if Node = Sender.GetFirst(False) then
                pcEditors.ActivePage := tsMain
              else
                if (Node^.Parent <> nil) and (Node^.Parent = Sender.GetFirst(False)) and (Node^.Index = 11) then
                  pcEditors.ActivePage := tsForms
                else
                  bShow := False;
            btShot:
              pcEditors.ActivePage := tsScreen;
            btForm, btGlobal:
              begin
                pcEditors.ActivePage := tsData;
                bBreak := False;
                if _Type = btForm then
                  bShow := GetData(TBytes(_Pointer^), BufData, _Size, _Decompressed, heEdit)
                else
                  bShow := GetData(TBytes(_Pointer^), BufData, _Size, 0, heEdit);
                if bShow then
                  BufNode := Node
                else
                  SetLength(BufData, 0);
              end;
            btInt, btHex:
              begin
                pcEditors.ActivePage := tsInteger;
                BufNode := Node;
              end;
            btReadOnly, btCalc:
              begin
                pcEditors.ActivePage := tsReadCalc;
                BufNode := Node;
              end;
            btString:
              begin
                pcEditors.ActivePage := tsString;
                BufNode := Node;
              end;
            btFloat:
              begin
                pcEditors.ActivePage := tsFloat;
                BufNode := Node;
              end;
            btFlags:
              begin
                pcEditors.ActivePage := tsFlags;
                BufNode := Node;
              end;
            btRec:
              if Node^.Parent <> nil then
              begin
                case Node^.Parent^.Index of
                  9:
                  begin
                    pcEditors.Tag := 0;
                    case Node^.Index of
                      0: pcEditors.ActivePage := tsGD01;
                      3: pcEditors.ActivePage := tsGD04;
                      else
                    bShow := False;
                    end;
                    if bShow then
                    begin
                      Expanded[Node] := True;
                      BufNode := GetChild(Node, 2);
                    end;
                  end;
                  10:
                  begin
                    pcEditors.Tag := 0;
                    bShow := False;
                    if bShow then
                    begin
                      Expanded[Node] := True;
                      BufNode := GetChild(Node, 2);
                    end;
                  end;
                  12:
                  begin
                    bShow := False;
                    if bShow then
                    begin
                      Expanded[Node] := True;
                      BufNode := GetChild(Node, 2);
                    end;
                  end
                  else
                    bShow := False;
                end;
              end
            else
              bShow := False;
          end;
    end
    {$IF Defined(USEREGION)}{$ENDREGION}{$IFEND}
    else
      begin
        bShow := False;
        if Assigned(BufNode) then
        {$IF Defined(USEREGION)}{$REGION 'Node unselected'}{$IFEND}
        begin
          MyData := GetNodeData(BufNode);
          if Assigned(MyData) then
          with MyData^ do
            case _Type of
              btForm:
                begin
                  SetData(TBytes(_Pointer^), BufData, _Size, _Decompressed, heEdit, True);
                  if BufNode^.Parent <> nil then
                  begin
                    FOSFile.changeForms[BufNode^.Parent^.Index]^.length1 := _Size;
                    FOSFile.changeForms[BufNode^.Parent^.Index]^.length2 := _Decompressed;
                    SetNodeText(vtStructure, GetChild(BufNode^.Parent, 4));
                    SetNodeText(vtStructure, GetChild(BufNode^.Parent, 5));
                  end;
                end;
              btGlobal:
                begin
                  SetData(TBytes(_Pointer^), BufData, _Size, _Decompressed, heEdit);
                  if (BufNode^.Parent <> nil) and (BufNode^.Parent^.Parent <> nil) then
                  begin
                    case BufNode^.Parent^.Parent^.Index of
                      8: FOSFile.globalDataTable1[BufNode^.Parent^.Index]^._length := _Size;
                      9: FOSFile.globalDataTable2[BufNode^.Parent^.Index]^._length := _Size;
                      11: FOSFile.globalDataTable3[BufNode^.Parent^.Index]^._length := _Size;
                    end;
                    SetNodeText(vtStructure, GetChild(BufNode^.Parent, 1));
                  end;
                end;
              btString:
                begin
                  wString(MyData^._Pointer^) := StringTowstring(edWString.Text);
                  SetNodeText(vtStructure, BufNode);
                end;
              btFloat:
                begin
                  if not TryStrToFloat(Trim(edFloat.Text), fTmp) then
                    MessageBox(Handle, PChar('Wrong float value: ' + Trim(edFloat.Text)), 'Error', MB_OK or MB_ICONERROR)
                  else
                  begin
                    if MyData^._Size = 8 then
                      Double(MyData^._Pointer^) := fTmp
                    else
                      Single(MyData^._Pointer^) := fTmp;
                    SetNodeText(vtStructure, BufNode);
                  end;
                end;
              btFlags:
                begin
                  iuInt32 := 0;
                  for i := 0 to 31 do
                    if IsChecked(tvFlags, i) then
                      iuInt32 := iuInt32 or (1 shl i);
                  uint32(MyData^._Pointer^) := iuInt32;
                  SetNodeText(vtStructure, BufNode);
                end;
            end;
          BufNode := nil;
        end
        else
          if lvForms.Items.Count > 0 then
          begin
            lvForms.Items.Count := 0;
            SetLength(FormViews, 0);
          end;
        {$IF Defined(USEREGION)}{$ENDREGION}{$IFEND}
      end;
  end;
  if bShow then
  begin
    if pcEditors.ActivePageIndex = tsMain.PageIndex then
      tsMainShow(Self);
    if pcEditors.ActivePageIndex = tsScreen.PageIndex then
      tsScreenShow(Self);
    if pcEditors.ActivePageIndex = tsForms.PageIndex then
      tsFormsShow(Self);
    if pcEditors.ActivePageIndex = tsInteger.PageIndex then
      tsIntegerShow(Self);
    if pcEditors.ActivePageIndex = tsReadCalc.PageIndex then
      tsReadCalcShow(Self);
    if pcEditors.ActivePageIndex = tsString.PageIndex then
      tsStringShow(Self);
    if pcEditors.ActivePageIndex = tsFloat.PageIndex then
      tsFloatShow(Self);
    if pcEditors.ActivePageIndex = tsFlags.PageIndex then
      tsFlagsShow(Self);
    if pcEditors.ActivePageIndex = tsGD01.PageIndex then
      tsGD01Show(Self);
    if pcEditors.ActivePageIndex = tsGD04.PageIndex then
      tsGD04Show(Self);
  end;
  pcEditors.Visible := bShow;
  with Sender do
  begin
    bShow := False;
    if GetFirstSelected <> nil then
      case GetNodeLevel(GetFirstSelected) of
        2: bShow := GetFirstSelected^.Parent^.Index in [11, 14, 16];
        3: bShow := GetFirstSelected^.Parent^.Parent^.Index in [7, 18];
      end;
    btnDelete.Visible := bShow;
    bShow := False;
    if GetFirstSelected <> nil then
      case GetNodeLevel(GetFirstSelected) of
        1: bShow := GetFirstSelected^.Index in [11, 14, 16];
        2: bShow := GetFirstSelected^.Parent^.Index in [7, 18];
      end;
    if bShow then
      if btnDelete.Visible then
        btnAdd.Left := btnDelete.Left + btnDelete.Width + 4
      else
        btnAdd.Left := btnDelete.Left;
    btnAdd.Visible := bShow;
  end;
end;

{$IF Defined(USEREGION)}{$REGION 'Tree view interface'}{$IFEND}
procedure TfMain.FillTree;
var
  pRoot, pLvl1, pLvl2: PVirtualNode;
begin
  with vtStructure, FOSFile do
  begin
    BeginUpdate;
    try
      RootNodeCount := 1;
      pRoot := GetFirst(False);
      if Assigned(pRoot) then
      begin
        ChildCount[pRoot] := 19;
        Expanded[pRoot] := True;
        InvalidateToBottom(pRoot);
        pLvl1 := pRoot.FirstChild;
        if Assigned(pLvl1) then
        repeat
          Expanded[pLvl1] := False;
          case pLvl1.Index of
            2: ChildCount[pLvl1] := 13; //THeader
            7: ChildCount[pLvl1] := 2; //TPluginInfo
            8: ChildCount[pLvl1] := 11; //TFileLocationTable
            9: ChildCount[pLvl1] := Length(globalDataTable1); //TGlobalData
            10: ChildCount[pLvl1] := Length(globalDataTable2); //TGlobalData
            11: ChildCount[pLvl1] := Length(changeForms); //TChangeForm
            12: ChildCount[pLvl1] := Length(globalDataTable3); //TGlobalData
            14: ChildCount[pLvl1] := Length(formIDArray); //TRefID
            16: ChildCount[pLvl1] := Length(visitedWorldspaceArray); //uint32
            18: ChildCount[pLvl1] := 2; //TUnknown3Table
          end;
          InvalidateToBottom(pLvl1);
          pLvl2 := pLvl1.FirstChild;
          if Assigned(pLvl2) then
          repeat
            Expanded[pLvl2] := False;
            case pLvl1.Index of
              7: if pLvl2.Index = 1 then
                   ChildCount[pLvl2] := Length(pluginInfo.plugins); //TFileLocationTable
              8: if pLvl2.Index = 10 then
                   ChildCount[pLvl2] := 15; //TFileLocationTable
              9: ChildCount[pLvl2] := 3; //TGlobalData
              10: ChildCount[pLvl2] := 3; //TGlobalData
              11: ChildCount[pLvl2] := 7; //TChangeForm
              12: ChildCount[pLvl2] := 3; //TGlobalData
              14: ChildCount[pLvl2] := 2; //TRefID
              18: ChildCount[pLvl2] := Length(unknown3Table.unknown); //TUnknown3Table
            end;
            InvalidateToBottom(pLvl2);
            pLvl2 := pLvl2.NextSibling;
          until not Assigned(pLvl2);
          pLvl1 := pLvl1.NextSibling;
        until not Assigned(pLvl1);
      end;
    finally
      EndUpdate;
    end;
  end;
end;

procedure TfMain.vtStructureGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var Text: WideString);
begin
  Text := GetNodeText(Sender, Node);
end;

procedure TfMain.vtStructureFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
var
  MyData: PMyRec;
begin
  MyData := Sender.GetNodeData(Node);
  Finalize(MyData^);
end;

procedure TfMain.vtStructureInitNode(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
var
  MyData: PMyRec;
begin
  with Sender, FOSFile do
  begin
    MyData := GetNodeData(Node);
    with MyData^ do
    begin
      _Caption := '';
      _Type := btNone;
      _Pointer := nil;
      _Size := 0;
      _Decompressed := 0;
      case GetNodeLevel(Node) of
        0: if (Node^.Index < 1) and (sFileName <> '') then
             _Caption := ExtractFileName(sFileName);
        1: GetCaptionLevel1(FOSFile, Node^.Index, MyData, Node);
        2: GetCaptionLevel2(FOSFile, Node^.Parent^.Index, Node^.Index, MyData, Node);
        3: GetCaptionLevel3(FOSFile, Node^.Parent^.Parent^.Index, Node^.Parent^.Index, Node^.Index, MyData, Node);
      end;
    end;
  end;
end;
{$IF Defined(USEREGION)}{$ENDREGION}{$IFEND}

procedure TfMain.vtStructureSearch(Sender: TBaseVirtualTree; Node: PVirtualNode; Text: string);
var
  Start: PVirtualNode;
  MyData: PMyRec;
begin
  Start := Node;
  Text := AnsiUpperCase('*' + Text + '*');
  stNotFound.Visible := False;
  stNotFound.Tag := 0;
  repeat
    Node := GetNextNode(Node, Sender.RootNode);
    if not (vsInitialized in Node^.States) then
      Sender.ReinitNode(Node, False);
    MyData := Sender.GetNodeData(Node);
    if Assigned(MyData) and MatchStrings(PChar(AnsiUpperCase(String(MyData^._Caption))), PChar(Text)) then
    begin
      Start := Node;
      while Start <> Sender.RootNode do
      begin
        Start := Start.Parent;
        Sender.Expanded[Start] := True;
      end;
      Sender.Selected[Node] := True;
      Sender.FocusedNode := Node;
      Sender.VisiblePath[Node] := True;
      stNotFound.Tag := 1;
      Break;
    end;
  until Node = Start;
  if stNotFound.Tag = 0 then
  begin
    stNotFound.Visible := True;
    PlaySound('APPGPFAULT', 0, SND_ASYNC);
  end;
end;

procedure TfMain.btnSearchClick(Sender: TObject);
var
  Node: PVirtualNode;
begin
  if Trim(edSearch.Text) <> '' then
  begin
    if vtStructure.GetFirstSelected <> nil then
      Node := vtStructure.GetFirstSelected
    else
      Node := vtStructure.RootNode;
    try
      Screen.Cursor := crHourGlass;
      vtStructureSearch(vtStructure, Node, edSearch.Text);
    finally
      Screen.Cursor := crDefault;
    end;
  end;
end;

procedure TfMain.DeleteSelectedObject;
var
  iIndex: integer;
  Node: PVirtualNode;
  bDeleted: Boolean;
begin
  with vtStructure, FOSFile do
    if GetFirstSelected <> nil then
    try
      Screen.Cursor := crHourGlass;
      Node := GetFirstSelected;
      iIndex := Node^.Index;
      bDeleted := False;
      if ((GetNodeLevel(Node) = 2) and (Node^.Parent^.Index in [11, 14, 16])) or
        ((GetNodeLevel(Node) = 3) and (Node^.Parent^.Parent^.Index in [7, 18])) then
      begin
        Selected[GetSelection(Node)] := True;
        FocusedNode := GetSelection(Node);
      end;
      case GetNodeLevel(Node) of
        2:
          case Node^.Parent^.Index of
            11: if iIndex < Length(changeForms) then
            begin
              Dec(fileLocationTable.changeFormCount);
              SetLength(changeForms[iIndex]^.data^, 0);
              Dispose(changeForms[iIndex]^.data);
              Dispose(changeForms[iIndex]);
              if iIndex < (Length(changeForms) - 1) then
                Move(changeForms[iIndex + 1], changeForms[iIndex], SizeOf(changeForms[iIndex]) * (Length(changeForms) - iIndex - 1));
              SetLength(changeForms, Length(changeForms) - 1);
              bDeleted := True;
            end;
            14: if iIndex < Length(formIDArray) then
            begin
              Dec(formIDArrayCount);
              Dispose(formIDArray[iIndex]);
              if iIndex < (Length(formIDArray) - 1) then
                Move(formIDArray[iIndex + 1], formIDArray[iIndex], SizeOf(formIDArray[iIndex]) * (Length(formIDArray) - iIndex - 1));
              SetLength(formIDArray, Length(formIDArray) - 1);
              bDeleted := True;
            end;
            16: if iIndex < Length(visitedWorldspaceArray) then
            begin
              Dec(visitedWorldspaceArrayCount);
              if iIndex < (Length(visitedWorldspaceArray) - 1) then
                Move(visitedWorldspaceArray[iIndex + 1], visitedWorldspaceArray[iIndex], SizeOf(visitedWorldspaceArray[iIndex]) * (Length(visitedWorldspaceArray) - iIndex - 1));
              SetLength(visitedWorldspaceArray, Length(visitedWorldspaceArray) - 1);
              bDeleted := True;
            end;
          end;
        3:
          case Node^.Parent^.Parent^.Index of
            7: if iIndex < Length(pluginInfo.plugins) then
            begin
              Dec(pluginInfo.pluginCount);
              pluginInfo.plugins[iIndex]^ := StringTowstring('');
              Dispose(pluginInfo.plugins[iIndex]);
              if iIndex < (Length(pluginInfo.plugins) - 1) then
                Move(pluginInfo.plugins[iIndex + 1], pluginInfo.plugins[iIndex], SizeOf(pluginInfo.plugins[iIndex]) * (Length(pluginInfo.plugins) - iIndex - 1));
              SetLength(pluginInfo.plugins, Length(pluginInfo.plugins) - 1);
              pluginInfoSize := SizeOf(pluginInfo.pluginCount);
              for iIndex := 0 to pluginInfo.pluginCount - 1 do
                pluginInfoSize := pluginInfoSize + SizeOf(pluginInfo.plugins[iIndex]^.w) + pluginInfo.plugins[iIndex]^.w;
              bDeleted := True;
            end;
            18: if iIndex < Length(Unknown3Table.unknown) then
            begin
              Dec(Unknown3Table.Count);
              Unknown3Table.unknown[iIndex]^ := StringTowstring('');
              Dispose(Unknown3Table.unknown[iIndex]);
              if iIndex < (Length(Unknown3Table.unknown) - 1) then
                Move(Unknown3Table.unknown[iIndex + 1], Unknown3Table.unknown[iIndex], SizeOf(Unknown3Table.unknown[iIndex]) * (Length(Unknown3Table.unknown) - iIndex - 1));
              SetLength(Unknown3Table.unknown, Length(Unknown3Table.unknown) - 1);
              unknown3TableSize := SizeOf(Unknown3Table.Count);
              for iIndex := 0 to Unknown3Table.count - 1 do
                unknown3TableSize := unknown3TableSize + SizeOf(Unknown3Table.unknown[iIndex]^.w) + Unknown3Table.unknown[iIndex]^.w;
              bDeleted := True;
            end;
          end;
      end;
      if bDeleted then
      begin
        iIndex := Integer(Node^.NextSibling);
        DeleteNode(Node, True);
        Node := Pointer(iIndex);
        if Node <> nil then
          repeat
            ReinitNode(Node, False);
            Node := Node^.NextSibling;
          until Node = nil;
        Repaint;
      end;
    finally
      Screen.Cursor := crDefault;
    end;
end;

procedure QSort(var Sort: array of integer; Low, Hi: integer);
var
  cmp, tmp, iLow, iHi : integer;
begin
  if Low > Hi then
    Exit;
  iLow := Low;
  iHi := Hi;
  cmp := Sort[(Low + Hi) div 2];
  repeat
    while Sort[iLow] < cmp do
      inc(iLow);
    while cmp < Sort[iHi] do
      dec(iHi);
    if iLow <= iHi then
    begin
      tmp := Sort[iLow];
      Sort[iLow] := Sort[iHi];
      Sort[iHi] := tmp;
      inc(iLow);
      dec(iHi)
    end;
  until iLow > iHi;
  if Low < iHi then
    QSort(Sort, Low, iHi);
  if iLow < Hi then
    QSort(Sort, iLow, Hi)
end;

procedure TfMain.DeleteSelectedForms;
var
  yItems: array of integer;
  i: integer;
  Node: PVirtualNode;
begin
  with FOSFile do
  try
    Screen.Cursor := crHourGlass;
    for i := 0 to lvForms.Items.Count - 1 do
      with lvForms.Items[i] do
        if Selected then
        begin
          SetLength(yItems, Length(yItems) + 1);
          yItems[Length(yItems) - 1] := Integer(Data);
        end;
    if Length(yItems) > 0 then
      with lvForms, vtStructure do
      begin
        QSort(yItems, 0, Length(yItems) - 1);
        Node := nil;
        while Length(yItems) > 0 do
        begin
          Node := GetChild(GetChild(GetFirst(False), 10), yItems[Length(yItems) - 1] - 1);
          if Node <> nil then
            with Node^ do
            begin
              Dec(fileLocationTable.changeFormCount);
              SetLength(changeForms[Index]^.data^, 0);
              Dispose(changeForms[Index]^.data);
              Dispose(changeForms[Index]);
              if Index < Cardinal(Length(changeForms) - 1) then
                Move(changeForms[Index + 1], changeForms[Index], SizeOf(changeForms[Index]) * (Length(changeForms) - Integer(Index) - 1));
              SetLength(changeForms, Length(changeForms) - 1);
              if Length(yItems) = 1 then
              begin
                i := Integer(Node^.NextSibling);
                DeleteNode(Node, True);
                Node := Pointer(i);
              end
              else
                DeleteNode(Node, False);
            end;
          SetLength(yItems, Length(yItems) - 1);
        end;
        if Node <> nil then
          repeat
            ReinitNode(Node, False);
            Node := Node^.NextSibling;
          until Node = nil;
        Repaint;
        if lvForms.Selected <> nil then
          i := lvForms.Selected.Index
        else
          i := -1;
        tsFormsShow(nil);
        if (i <> -1) and (i < lvForms.Items.Count) then
          lvForms.Selected := lvForms.Items[i];
      end;
  finally
    Screen.Cursor := crDefault;
  end;
end;

procedure TfMain.btnAddClick(Sender: TObject);
begin
  AddObject;
end;

procedure TfMain.AddObject;
var
  Node: PVirtualNode;
  bExists: Boolean;
  sValue: string;
  i, iValue: integer;
  cfForm: PChangeForm;
  fsForm: PFormIDArray;
begin
  with vtStructure, FOSFile do
    if GetFirstSelected <> nil then
    try
      Screen.Cursor := crHourGlass;
      Node := GetFirstSelected;
      case GetNodeLevel(Node) of
        1:
          case Node^.Index of
            11: with TfAddForm.Create(Application) do
            try
              New(cfForm);
              ZeroMemory(@cfForm^, SizeOf(cfForm^));
              bExists := True;
              if AddChangeForm(cfForm^, @FOSFile) then
              begin
                bExists := False;
                for i := 0 to Length(changeForms) - 1 do
                  if RefIDCompare(cfForm^.formID, changeForms[i]^.formID) = 0 then
                  begin
                    bExists := True;
                    Break;
                  end;
                if not bExists then
                begin
                  Inc(fileLocationTable.changeFormCount);
                  SetLength(changeForms, Length(changeForms) + 1);
                  changeForms[Length(changeForms) - 1] := cfForm;
                  AddChild(Node);
                  ChildCount[Node^.LastChild] := 7;
                  ReinitNode(Node^.LastChild, True);
                end
                else
                  MessageBox(Handle, PChar(RefIDToString(cfForm^.formID) + ' already exists in Change Forms'), 'Warning', MB_OK or MB_ICONWARNING)
              end;
              if bExists then
              begin
                if Assigned(cfForm^.data) then
                  Dispose(cfForm^.data);
                Dispose(cfForm);
              end;
            finally
              Release;
            end;
            14: with TfAddFormIDArray.Create(Application) do
            try
              New(fsForm);
              ZeroMemory(@fsForm^, SizeOf(fsForm^));
              bExists := True;
              if AddFormIDArray(fsForm^, @FOSFile) then
              begin
                bExists := False;
                for i := 0 to Length(formIDArray) - 1 do
                  if RefIDCompare(fsForm^.ID, formIDArray[i]^.ID) = 0 then
                  begin
                    bExists := True;
                    Break;
                  end;
                if not bExists then
                begin
                  Inc(formIDArrayCount);
                  SetLength(formIDArray, Length(formIDArray) + 1);
                  formIDArray[Length(formIDArray) - 1] := fsForm;
                  AddChild(Node);
                  ChildCount[Node^.LastChild] := 2;
                  ReinitNode(Node^.LastChild, True);
                end
                else
                  MessageBox(Handle, PChar(RefIDToString(fsForm^.ID) + ' already exists in FormID Array'), 'Warning', MB_OK or MB_ICONWARNING)
              end;
              if bExists then
                Dispose(fsForm);
            finally
              Release;
            end;
            16: if InputQuery('Add visited worldspace to array in FOS structure', 'Type visited worldspace', sValue) and (Trim(sValue) <> '')  then
              if not TryStrToInt(Trim(sValue), iValue) then
                MessageBox(Handle, PChar(Trim(sValue) + ' isn''t integer value'), 'Convert error', MB_OK or MB_ICONERROR)
              else
              begin
                bExists := False;
                for i := 0 to Length(visitedWorldspaceArray) - 1 do
                  if Cardinal(iValue) = visitedWorldspaceArray[i] then
                  begin
                    bExists := True;
                    Break;
                  end;
                if not bExists then
                begin
                  Inc(visitedWorldspaceArrayCount);
                  SetLength(visitedWorldspaceArray, Length(visitedWorldspaceArray) + 1);
                  visitedWorldspaceArray[Length(visitedWorldspaceArray) - 1] := iValue;
                  AddChild(Node);
                  ReinitNode(Node^.LastChild, False);
                end
                else
                  MessageBox(Handle, PChar(Trim(sValue) + ' already exists in visited worldspace array'), 'Warning', MB_OK or MB_ICONWARNING)
              end;
          end;
        2:
          case Node^.Parent^.Index of
            7: if InputQuery('Add plugin to FOS structure', 'Type plugin name', sValue) and (Trim(sValue) <> '') then
            begin
              bExists := False;
              sValue := Trim(sValue);
              for i := 0 to Length(pluginInfo.plugins) - 1 do
                if sValue = wstringToString(pluginInfo.plugins[i]^) then
                begin
                  bExists := True;
                  Break;
                end;
              if not bExists then
              begin
                Inc(pluginInfo.pluginCount);
                SetLength(pluginInfo.plugins, Length(pluginInfo.plugins) + 1);
                New(pluginInfo.plugins[Length(pluginInfo.plugins) - 1]);
                pluginInfo.plugins[Length(pluginInfo.plugins) - 1]^ := StringTowstring(sValue);
                pluginInfoSize := SizeOf(pluginInfo.pluginCount);
                for i := 0 to pluginInfo.pluginCount - 1 do
                  pluginInfoSize := pluginInfoSize + SizeOf(pluginInfo.plugins[i]^.w) + pluginInfo.plugins[i]^.w;
                AddChild(Node);
                ReinitNode(Node^.LastChild, False);
              end
              else
                MessageBox(Handle, PChar(sValue + ' already exists on plugins list'), 'Warning', MB_OK or MB_ICONWARNING)
            end;
            18: if InputQuery('Add unknown to FOS structure', 'Type unknown name', sValue) and (Trim(sValue) <> '')  then
            begin
              bExists := False;
              sValue := Trim(sValue);
              for i := 0 to Length(Unknown3Table.unknown) - 1 do
                if sValue = wstringToString(Unknown3Table.unknown[i]^) then
                begin
                  bExists := True;
                  Break;
                end;
              if not bExists then
              begin
                Inc(Unknown3Table.Count);
                SetLength(Unknown3Table.unknown, Length(Unknown3Table.unknown) + 1);
                New(Unknown3Table.unknown[Length(Unknown3Table.unknown) - 1]);
                Unknown3Table.unknown[Length(Unknown3Table.unknown) - 1]^ := StringTowstring(sValue);
                unknown3TableSize := SizeOf(Unknown3Table.Count);
                for i := 0 to Unknown3Table.count - 1 do
                  unknown3TableSize := unknown3TableSize + SizeOf(Unknown3Table.unknown[i]^.w) + Unknown3Table.unknown[i]^.w;
                AddChild(Node);
                ReinitNode(Node^.LastChild, False);
              end
              else
                MessageBox(Handle, PChar(sValue + ' already exists in Unknown3Table'), 'Warning', MB_OK or MB_ICONWARNING)
            end;
          end;
      end;
      Repaint;
    finally
      Screen.Cursor := crDefault;
    end;
end;

end.
