unit game_edit;

{$mode ObjFPC}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ComCtrls, ExtCtrls,
  StdCtrls, Grids,

  ms_shell_hack,

  game_info,
  param_sfo_gui;

type

  { TfrmGameEditor }

  TfrmGameEditor = class(TForm)
    BtnOk: TButton;
    BtnCancel: TButton;
    EditPages: TPageControl;
    GridMain: TStringGrid;
    GridMounts: TStringGrid;
    GridParamSfo: TStringGrid;
    TabMain: TTabSheet;
    TabMounts: TTabSheet;
    TabParamSfo: TTabSheet;
    procedure BtnOkClick(Sender: TObject);
    procedure BtnCancelClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
    procedure FormInit(UpdateTitle:Boolean);
    procedure FormSave;
    procedure LoadParamSfo(UpdateTitle:Boolean);
    procedure GridSelectEditor(Sender:TObject;aCol,aRow:Integer;var Editor:TWinControl);
    procedure GridEditingDone(Sender: TObject);
  private
    Fapp0_row   :Integer;
    FName_row   :Integer;
    FTitleId_row:Integer;
    FVersion_row:Integer;

    Fapp0p:RawByteString;
  public
    OnSave  :TNotifyEvent;
    Item    :TGameItem;
    ParamSfo:TParamSfoFile;
  end;

var
  frmGameEditor: TfrmGameEditor;

implementation

{$R *.lfm}

uses
 TypInfo,
 Rtti;

{ TfrmGameEditor }

type
 TFieldInfo=class(TComponent)
  procedure SelectEditor(Grid:TStringGrid;aCol,aRow:Integer;var Editor:TWinControl); virtual; abstract;
 end;

 TFieldInfoPath=class(TFieldInfo)
  procedure SelectEditor(Grid:TStringGrid;aCol,aRow:Integer;var Editor:TWinControl); override;
 end;

 TButtonPath=class(TButton)
  public
   aRow:Integer;
   Form  :TWinControl;
   Editor:TWinControl;
   procedure EditorExit(Sender:TObject);
   procedure OpenDir(Sender:TObject);
 end;

procedure TButtonPath.EditorExit(Sender:TObject);
begin
 Editor.OnEnter:=nil;
 Editor.OnExit :=nil;
 if not Focused then
 begin
  Hide;
 end;
end;

type
 TMyStringGrid=class(TStringGrid)
  function  NewBtn:TButtonPath;
  procedure EditorDirEnter(Sender:TObject);
 end;

procedure TButtonPath.OpenDir(Sender:TObject);
var
 d:TSelectDirectoryDialog;
 Cookie:Pointer;
begin
 Cookie:=RegisterDllHack;

 d:=nil;

 try
  d:=TSelectDirectoryDialog.Create(nil);

  with TMyStringGrid(Editor.Parent) do
  begin
   d.InitialDir:=Cells[1,aRow];
  end;

  d.Options:=[ofPathMustExist,ofEnableSizing,ofViewDetail];

  if d.Execute then
  with TMyStringGrid(Editor.Parent) do
  begin
   Cells[1,aRow]:=d.FileName;
  end;

 except
  //
 end;
 FreeAndNil(d);

 UnregisterDllHack(Cookie);

 TfrmGameEditor(Self.Form).LoadParamSfo(True);

 Hide;
end;

function TMyStringGrid.NewBtn:TButtonPath;
var
 i:Integer;
 btn:TButtonPath;
begin
 btn:=nil;

 if (ComponentCount<>0) then
 for i:=0 to ComponentCount-1 do
 begin
  if Components[i] is TButtonPath then
  begin
   btn:=TButtonPath(Components[i]);
   Break;
  end;
 end;

 if (btn=nil) then
 begin
  btn:=TButtonPath.Create(Self);

  btn.Form:=TWinControl(Self.Tag);

  btn.AutoSize:=True;
  btn.Caption:='...';

  btn.Parent:=Self;

  btn.BorderSpacing.Top   :=-3;
  btn.BorderSpacing.Right :=-3;
  btn.BorderSpacing.Bottom:=-3;
 end;

 btn.aRow:=Self.Row;

 btn.AnchorSide[akTop].Side:=asrTop;
 btn.AnchorSide[akTop].Control:=Editor;

 btn.AnchorSide[akRight].Side:=asrBottom;
 btn.AnchorSide[akRight].Control:=Editor;

 btn.AnchorSide[akBottom].Side:=asrBottom;
 btn.AnchorSide[akBottom].Control:=Editor;

 btn.Anchors:=[akTop,akRight,akBottom];

 btn.Editor:=Editor;

 Result:=btn;
end;

procedure TMyStringGrid.EditorDirEnter(Sender:TObject);
var
 btn:TButtonPath;
begin
 btn:=NewBtn;

 btn.OnClick:=@btn.OpenDir;

 Editor.OnExit:=@btn.EditorExit;

 btn.Show;
end;

procedure TFieldInfoPath.SelectEditor(Grid:TStringGrid;aCol,aRow:Integer;var Editor:TWinControl);
begin
 Editor.OnEnter:=@TMyStringGrid(Grid).EditorDirEnter;
end;

procedure AddRow(Grid:TStringGrid;const name,value:RawByteString;info:TFieldInfo);
var
 i:Integer;
begin
 i:=Grid.RowCount;
 Grid.RowCount:=i+1;
 Grid.Cells[0,i]:=name;
 Grid.Cells[1,i]:=value;
 Grid.Objects[0,i]:=info;
end;

procedure TfrmGameEditor.FormInit(UpdateTitle:Boolean);
var
 fip:TFieldInfoPath;

 i:TRttiPropertyIterator;
 p:TRttiProperty;
begin
 EditPages.ActivePageIndex:=0;

 GridMain.Clear;
 GridMounts.Clear;

 //TypInfo.SetRawByteStrProp();

 //AddRow(GridMain,'Name:',nil);

 //
 fip:=TFieldInfoPath.Create(Self);
 //

 i:=Item.FGameInfo.GetPropertyIterator;
 try
  while (i.GetProperty<>nil) do
  begin

   p:=i.GetProperty;
   case p.Name of
    'Name'   :FName_row   :=GridMain.RowCount;
    'TitleId':FTitleId_row:=GridMain.RowCount;
    'Version':FVersion_row:=GridMain.RowCount;
    else;
   end;

   AddRow(GridMain,p.Name+':',p.GetValue(Item.FGameInfo).AsString,nil);

   i.Next;
  end;
 finally
  i.free;
 end;

 i:=Item.FMountList.GetPropertyIterator;
 try
  while (i.GetProperty<>nil) do
  begin

   p:=i.GetProperty;

   case p.Name of
    'app0':Fapp0_row:=GridMounts.RowCount;
    else;
   end;

   AddRow(GridMounts,'/'+p.Name,p.GetValue(Item.FMountList).AsString,fip);

   i.Next;
  end;
 finally
  i.free;
 end;

 LoadParamSfo(UpdateTitle);

 Show;
end;

procedure TfrmGameEditor.FormSave;
var
 i:TRttiPropertyIterator;
 p:TRttiProperty;
begin
 i:=Item.FGameInfo.GetPropertyIterator;
 try
  while (i.GetProperty<>nil) do
  begin

   p:=i.GetProperty;
   p.SetValue(Item.FGameInfo,GridMain.Cells[1,i.i]);

   i.Next;
  end;
 finally
  i.free;
 end;

 i:=Item.FMountList.GetPropertyIterator;
 try
  while (i.GetProperty<>nil) do
  begin

   p:=i.GetProperty;
   p.SetValue(Item.FMountList,GridMounts.Cells[1,i.i]);

   i.Next;
  end;
 finally
  i.free;
 end;

end;

function GetGridVal(Grid:TStringGrid;ARow:Integer):RawByteString;
begin
 Result:='';
 if (ARow<0) and (ARow>=Grid.RowCount) then Exit;
 //
 Result:=Grid.Cells[1,ARow];
end;

procedure SetGridVal(Grid:TStringGrid;ARow:Integer;const value:RawByteString);
begin
 if (ARow<0) and (ARow>=Grid.RowCount) then Exit;
 //
 Grid.Cells[1,ARow]:=value;
end;

procedure TfrmGameEditor.LoadParamSfo(UpdateTitle:Boolean);
var
 i:Integer;
 V:RawByteString;
begin
 V:=GetGridVal(GridMounts,Fapp0_row);

 if (Fapp0p=V) then Exit;

 FreeAndNil(ParamSfo);

 ParamSfo:=LoadParamSfoFile(ExcludeTrailingPathDelimiter(V)+
                            DirectorySeparator+
                            'sce_sys'+
                            DirectorySeparator+
                            'param.sfo');

 Fapp0p:=V;

 GridParamSfo.Clear;

 if (ParamSfo=nil) then
 begin
  SetGridVal(GridMain,FTitleId_row,'???');
  SetGridVal(GridMain,FVersion_row,'???');
  Exit;
 end;

 if (Length(ParamSfo.params)=0) then Exit;
 For i:=0 to High(ParamSfo.params) do
 begin
  if (ParamSfo.params[i].format=SFO_FORMAT_UINT32) then
  begin
   V:='0x'+HexStr(ParamSfo.params[i].GetUInt,8);
  end else
  begin
   V:=Trim(ParamSfo.params[i].GetString);
  end;
  AddRow(GridParamSfo,ParamSfo.params[i].name,V,nil);
 end;

 GridParamSfo.AutoSizeColumn(0);

 //
 if not UpdateTitle then Exit;

 V:=ParamSfo.GetString('TITLE');
 SetGridVal(GridMain,FName_row,V);

 V:=ParamSfo.GetString('TITLE_ID');
 SetGridVal(GridMain,FTitleId_row,V);

 V:=ParamSfo.GetString('APP_VER');
 SetGridVal(GridMain,FVersion_row,V);
end;

procedure TfrmGameEditor.GridSelectEditor(Sender:TObject;aCol,aRow:Integer;var Editor:TWinControl);
var
 obj:TObject;
begin
 obj:=TStringGrid(Sender).Objects[0,aRow];
 if (obj=nil) then Exit;
 if not obj.InheritsFrom(TFieldInfo) then Exit;
 //
 TStringGrid(Sender).Tag:=PtrInt(Self);
 //
 TFieldInfo(obj).SelectEditor(TStringGrid(Sender),aCol,aRow,Editor);
end;

procedure TfrmGameEditor.GridEditingDone(Sender: TObject);
begin
 LoadParamSfo(True);
end;

procedure TfrmGameEditor.BtnOkClick(Sender: TObject);
begin
 FormSave;
 Hide;
 if Assigned(OnSave) then
 begin
  OnSave(Self);
 end;
 Close;
end;

procedure TfrmGameEditor.BtnCancelClick(Sender: TObject);
begin
 Close;
end;

procedure TfrmGameEditor.FormClose(Sender:TObject;var CloseAction:TCloseAction);
begin
 if (Item<>nil) then
 begin
  if Item.FLock then
  begin
   Item.FLock:=False;
  end else
  begin
   FreeAndNil(Item);
  end;
 end;
 //
 FreeAndNil(ParamSfo);
 //
 CloseAction:=caFree;
end;

end.

