{
    This file is part of the Free Pascal run time library.
    Copyright (c) 2008 by Giulio Bernardi

    Version information resource type

    See the file COPYING.FPC, included in this distribution,
    for details about the copyright.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 **********************************************************************}

unit versionresource;

{$MODE OBJFPC}

interface

uses
  SysUtils, Classes, resource, versiontypes;

type
  TVerBlockHeader = packed record
    length    : word;
    vallength : word;
    valtype   : word;
    key       : string;
  end;

type

  { TVersionResource }

  TVersionResource = class(TAbstractResource)
  private
    fType : TResourceDesc;
    fName : TResourceDesc;
    fFixedInfo : TVersionFixedInfo;
    fStringFileInfo : TVersionStringFileInfo;
    fVarFileInfo : TVersionVarFileInfo;
    function SwapVersion(const aData : qword) : qword;
    procedure AlignDWordReading;
    procedure AlignDWordWriting;
    function GetFileInfo : TVersionStringFileInfo;
    function GetFixedInfo : TVersionFixedInfo;
    function GetVarInfo : TVersionVarFileInfo;
    procedure CheckDataLoaded;
    procedure LoadData;
    procedure LoadFixedInfos;
    function ReadBlockHeader(out aBlock : TVerBlockHeader) : integer;
    function ReadStringFileInfo(toread : integer) : integer;
    function ReadVarFileInfo(toread : integer) : integer;
    function ReadStringTable(toread : integer;aName : string) : integer;
    function ReadWideString: string;
    procedure WriteFixedBlockLength(const position : int64);
    procedure WriteData;
    procedure WriteFixedInfos;
    procedure WriteStringFileInfo;
    procedure WriteStringTable(aTable : TVersionStringTable);
    procedure WriteStringEntry(const aKey, aValue : string);
    procedure WriteVarFileInfo;
    procedure WriteVarEntry(aEntry : TVerTranslationInfo);
    procedure WriteWideString(const aString : string);
  protected
    function GetType : TResourceDesc; override;
    function GetName : TResourceDesc; override;
    function ChangeDescTypeAllowed(aDesc : TResourceDesc) : boolean; override;
    function ChangeDescValueAllowed(aDesc : TResourceDesc) : boolean; override;
    procedure NotifyResourcesLoaded; override;
  public
    constructor Create; override;
    constructor Create(aType,aName : TResourceDesc); override;
    destructor Destroy; override;
    procedure UpdateRawData; override;
    property FixedInfo : TVersionFixedInfo read GetFixedInfo;
    property StringFileInfo : TVersionStringFileInfo read GetFileInfo;
    property VarFileInfo : TVersionVarFileInfo read GetVarInfo;
  end;


implementation

uses
  resfactory;
  
type
  TVSFixedFileInfo = packed record
    signature : longword;
    verMinor  : word;
    verMajor  : word;
    FileVersion : qword;
    ProductVersion : qword;
    FileFlagsMask : longword;
    FileFlags     : longword;
    FileOS        : longword;
    FileType      : longword;
    FileSubType   : longword;
    FileDate      : qword;
  end;


{ TVersionResource }

function TVersionResource.SwapVersion(const aData: qword): qword;
begin
  {$IFDEF ENDIAN_BIG}
  Result:=Swap(aData);                      //MSLW is stored first...
  Result:=SwapEndian(Result);
  {$ELSE}
  Result:=Swap(longword(hi(aData)));
  Result:=Result shl 32;
  Result:=Result or Swap(longword(lo(aData)));
  {$ENDIF}
end;

procedure TVersionResource.AlignDWordReading;
var toskip : integer;
begin
  toskip:=4-(RawData.Position mod 4);
  if toskip<>4 then RawData.Seek(toskip,soFromCurrent);
end;

procedure TVersionResource.AlignDWordWriting;
var topad : integer;
    lw : longword;
begin
  lw:=0;
  topad:=4-(RawData.Position mod 4);
  if topad<>4 then RawData.WriteBuffer(lw,topad);
end;

function TVersionResource.GetFileInfo: TVersionStringFileInfo;
begin
  CheckDataLoaded;
  Result:=fStringFileInfo;
end;

function TVersionResource.GetFixedInfo: TVersionFixedInfo;
begin
  CheckDataLoaded;
  Result:=fFixedInfo;
end;

function TVersionResource.GetVarInfo: TVersionVarFileInfo;
begin
  CheckDataLoaded;
  Result:=fVarFileInfo;
end;

procedure TVersionResource.CheckDataLoaded;
begin
  if fFixedInfo<>nil then exit;
  //if we have no data stream, create empty structures
  if RawData.Size=0 then
  begin
    fFixedInfo:=TVersionFixedInfo.Create;
    fStringFileInfo:=TVersionStringFileInfo.Create;
    fVarFileInfo:=TVersionVarFileInfo.Create;
  end
  else LoadData;
end;

procedure TVersionResource.LoadData;
var tmp : integer;
    toread : word;
    block : TVerBlockHeader;
    i : integer;
begin
  RawData.Position:=0;
  tmp:=ReadBlockHeader(block);
                                        //block.key should be 'VS_VERSION_INFO'
  toread:=block.length;
  LoadFixedInfos;
  AlignDWordReading;
  dec(toread,RawData.Position);
  
  fStringFileInfo:=TVersionStringFileInfo.Create;
  fVarFileInfo:=TVersionVarFileInfo.Create;

  for i:=1 to 2 do
  begin
    if toread<=0 then exit;
    tmp:=ReadBlockHeader(block);
    dec(toread,tmp);
    if block.key='StringFileInfo' then tmp:=ReadStringFileInfo(block.length-tmp)
    else if block.key='VarFileInfo' then tmp:=ReadVarFileInfo(block.length-tmp);
    dec(toread,tmp);
  end;

end;

procedure TVersionResource.LoadFixedInfos;
var infodata : TVSFixedFileInfo;
begin
  RawData.ReadBuffer(infodata,sizeof(infodata));
  infodata.FileVersion:=SwapVersion(infodata.FileVersion);
  infodata.ProductVersion:=SwapVersion(infodata.ProductVersion);
  infodata.FileDate:=Swap(infodata.FileDate);             //MSLW is stored first...
  {$IFDEF ENDIAN_BIG}
  infodata.signature:=SwapEndian(infodata.signature);
  infodata.verMinor:=SwapEndian(infodata.verMinor);
  infodata.verMajor:=SwapEndian(infodata.verMajor);
  infodata.FileFlagsMask:=SwapEndian(infodata.FileFlagsMask);
  infodata.FileFlags:=SwapEndian(infodata.FileFlags);
  infodata.FileOS:=SwapEndian(infodata.FileOS);
  infodata.FileType:=SwapEndian(infodata.FileType);
  infodata.FileSubType:=SwapEndian(infodata.FileSubType);
  infodata.FileDate:=SwapEndian(infodata.FileDate);
  {$ENDIF}
  fFixedInfo:=TVersionFixedInfo.Create;
  fFixedInfo.FileVersion:=TFileProductVersion(infodata.FileVersion);
  fFixedInfo.ProductVersion:=TFileProductVersion(infodata.ProductVersion);
  fFixedInfo.FileFlagsMask:=infodata.FileFlagsMask;
  fFixedInfo.FileFlags:=infodata.FileFlags;
  fFixedInfo.FileOS:=infodata.FileOS;
  fFixedInfo.FileType:=infodata.FileType;
  fFixedInfo.FileSubType:=infodata.FileSubType;
  fFixedInfo.FileDate:=infodata.FileDate;
end;

function TVersionResource.ReadBlockHeader(out aBlock: TVerBlockHeader
  ): integer;
var before : int64;
begin
  before:=RawData.Position;
  RawData.ReadBuffer(aBlock,6);
  {$IFDEF ENDIAN_BIG}
  aBlock.length:=SwapEndian(aBlock.length);
  aBlock.vallength:=SwapEndian(aBlock.vallength);
  aBlock.valtype:=SwapEndian(aBlock.valtype);
  {$ENDIF}
  aBlock.key:=ReadWideString;
  AlignDWordReading;
  Result:=RawData.Position-before;
end;

function TVersionResource.ReadStringFileInfo(toread : integer) : integer;
var block : TVerBlockHeader;
    tmp : integer;
begin
  Result:=0;
  while toread>0 do
  begin
    tmp:=ReadBlockHeader(block);
    dec(toread,tmp); inc(Result,tmp);
    tmp:=ReadStringTable(block.length-tmp,block.key);
    dec(toread,tmp); inc(Result,tmp);
  end;
end;

function TVersionResource.ReadVarFileInfo(toread : integer) : integer;
var block : TVerBlockHeader;
    tmp : integer;
    vinfo : TVerTranslationInfo;
    before : int64;
    isBlockHeaderRead: boolean;
begin
  Result:=0;
  isBlockHeaderRead:=false;
  while toread>0 do
  begin
    before:=RawData.Position;
    if not isBlockHeaderRead then
    begin
      ReadBlockHeader(block);
      isBlockHeaderRead:=true;
    end;
    if (block.valtype<>0) or (block.key<>'Translation') then
      RawData.Seek(block.vallength,soFromCurrent)
    else
    begin
      RawData.ReadBuffer(vinfo,sizeof(vinfo));
      {$IFDEF ENDIAN_BIG}
      vinfo.language:=SwapEndian(vinfo.language);
      vinfo.codepage:=SwapEndian(vinfo.codepage);
      {$ENDIF}
      fVarFileInfo.Add(vinfo);
    end;
    AlignDWordReading;
    tmp:=RawData.Position-before;
    dec(toread,tmp); inc(Result,tmp);
  end;
end;

function TVersionResource.ReadStringTable(toread: integer; aName: string
  ): integer;
var strtable : TVersionStringTable;
    tmp : integer;
    block : TVerBlockHeader;
    value : string;
    before : int64;
begin
  Result:=0;
  strtable:=TVersionStringTable.Create(aName);
  fStringFileInfo.Add(strtable);
  while toread>0 do
  begin
    before:=RawData.Position;
    ReadBlockHeader(block);
    value:=ReadWideString;
    AlignDWordReading;
    tmp:=RawData.Position-before;
    dec(toread,tmp); inc(Result,tmp);
    strtable.Add(block.key,value);
  end;
end;

function TVersionResource.ReadWideString: string;
var w : word;
    ws : widestring;
begin
  ws:='';
  w:=0;
  repeat
    RawData.ReadBuffer(w,2);
    if w = 0 then break;
    {$IFDEF ENDIAN_BIG}
    w:=SwapEndian(w);
    {$ENDIF}
    ws:=ws+widechar(w);
  until false;
  Result:=ws;
end;

procedure TVersionResource.WriteFixedBlockLength(const position: int64);
var after : int64;
    len : word;
begin
  after:=RawData.Position;
  len:=after-position;
  {$IFDEF ENDIAN_BIG}
  len:=SwapEndian(len);
  {$ENDIF}
  RawData.Position:=position;
  RawData.WriteBuffer(len,2);
  RawData.Position:=after;
end;

procedure TVersionResource.WriteData;
var block : TVerBlockHeader;
begin
  RawData.Size:=0;
  RawData.Position:=0;

  block.length:=0;
  block.vallength:=$34;
  block.valtype:=0;
  block.key:='VS_VERSION_INFO';
  {$IFDEF ENDIAN_BIG}
  block.vallength:=SwapEndian(block.vallength);
  block.valtype:=SwapEndian(block.valtype);
  {$ENDIF}

  RawData.WriteBuffer(block,6);
  WriteWideString(block.key);
  AlignDWordWriting;

  WriteFixedInfos;
  AlignDWordWriting;

  if fStringFileInfo.Count>0 then WriteStringFileInfo;
  if fVarFileInfo.Count>0 then WriteVarFileInfo;

  WriteFixedBlockLength(0);
end;

procedure TVersionResource.WriteFixedInfos;
var infodata : TVSFixedFileInfo;
begin
  infodata.signature:=$FEEF04BD;
  infodata.verMinor:=0;
  infodata.verMajor:=1;
  infodata.FileVersion:=qword(fFixedInfo.FileVersion);
  infodata.ProductVersion:=qword(fFixedInfo.ProductVersion);
  infodata.FileFlagsMask:=fFixedInfo.FileFlagsMask;
  infodata.FileFlags:=fFixedInfo.FileFlags;
  infodata.FileOS:=fFixedInfo.FileOS;
  infodata.FileType:=fFixedInfo.FileType;
  infodata.FileSubType:=fFixedInfo.FileSubType;
  infodata.FileDate:=fFixedInfo.FileDate;
  {$IFDEF ENDIAN_BIG}
  infodata.signature:=SwapEndian(infodata.signature);
  infodata.verMinor:=SwapEndian(infodata.verMinor);
  infodata.verMajor:=SwapEndian(infodata.verMajor);
  infodata.FileFlagsMask:=SwapEndian(infodata.FileFlagsMask);
  infodata.FileFlags:=SwapEndian(infodata.FileFlags);
  infodata.FileOS:=SwapEndian(infodata.FileOS);
  infodata.FileType:=SwapEndian(infodata.FileType);
  infodata.FileSubType:=SwapEndian(infodata.FileSubType);
  infodata.FileDate:=SwapEndian(infodata.FileDate);
  {$ENDIF}
  infodata.FileVersion:=SwapVersion(infodata.FileVersion);
  infodata.ProductVersion:=SwapVersion(infodata.ProductVersion);
  infodata.FileDate:=Swap(infodata.FileDate);             //MSLW is stored first...
  RawData.WriteBuffer(infodata,sizeof(infodata));
end;

procedure TVersionResource.WriteStringFileInfo;
var block : TVerBlockHeader;
    i : integer;
    before : int64;
begin
  before:=RawData.Position;
  block.length:=0;
  block.vallength:=0;
  block.valtype:=1;
  block.key:='StringFileInfo';
  {$IFDEF ENDIAN_BIG}
  block.vallength:=SwapEndian(block.vallength);
  block.valtype:=SwapEndian(block.valtype);
  {$ENDIF}
  RawData.WriteBuffer(block,6);
  WriteWideString(block.key);
  AlignDWordWriting;
  
  for i:=0 to fStringFileInfo.Count-1 do
    WriteStringTable(fStringFileInfo[i]);
  
  WriteFixedBlockLength(before);
end;

procedure TVersionResource.WriteStringTable(aTable: TVersionStringTable);
var block : TVerBlockHeader;
    i : integer;
    before : int64;
begin
  before:=RawData.Position;
  block.length:=0;
  block.vallength:=0;
  block.valtype:=1;
  block.key:=aTable.Name;
  {$IFDEF ENDIAN_BIG}
  block.vallength:=SwapEndian(block.vallength);
  block.valtype:=SwapEndian(block.valtype);
  {$ENDIF}
  RawData.WriteBuffer(block,6);
  WriteWideString(block.key);
  AlignDWordWriting;

  for i:=0 to aTable.Count-1 do
    WriteStringEntry(aTable.Keys[i],aTable.ValuesByIndex[i]);

  WriteFixedBlockLength(before);
end;

procedure TVersionResource.WriteStringEntry(const aKey, aValue: string);
var block : TVerBlockHeader;
    before: int64;
begin
  before:=RawData.Position;
  block.length:=0;
  block.vallength:=length(aValue)+1;
  block.valtype:=1;
  block.key:=aKey;
  {$IFDEF ENDIAN_BIG}
  block.vallength:=SwapEndian(block.vallength);
  block.valtype:=SwapEndian(block.valtype);
  {$ENDIF}
  RawData.WriteBuffer(block,6);
  WriteWideString(block.key);
  AlignDWordWriting;
  WriteWideString(aValue);
  AlignDWordWriting;

  WriteFixedBlockLength(before);
end;

procedure TVersionResource.WriteVarFileInfo;
var block : TVerBlockHeader;
    i : integer;
    before : int64;
begin
  before:=RawData.Position;
  block.length:=0;
  block.vallength:=0;
  block.valtype:=1;
  block.key:='VarFileInfo';
  {$IFDEF ENDIAN_BIG}
  block.vallength:=SwapEndian(block.vallength);
  block.valtype:=SwapEndian(block.valtype);
  {$ENDIF}
  RawData.WriteBuffer(block,6);
  WriteWideString(block.key);
  AlignDWordWriting;

  for i:=0 to fVarFileInfo.Count-1 do
    WriteVarEntry(fVarFileInfo[i]);

  WriteFixedBlockLength(before);
end;

procedure TVersionResource.WriteVarEntry(aEntry: TVerTranslationInfo);
var block : TVerBlockHeader;
    before: int64;
begin
  before:=RawData.Position;
  block.length:=0;
  block.vallength:=4;
  block.valtype:=0;
  block.key:='Translation';
  {$IFDEF ENDIAN_BIG}
  block.vallength:=SwapEndian(block.vallength);
  block.valtype:=SwapEndian(block.valtype);
  aEntry.language:=SwapEndian(aEntry.language);
  aEntry.codepage:=SwapEndian(aEntry.codepage);
  {$ENDIF}
  RawData.WriteBuffer(block,6);
  WriteWideString(block.key);
  AlignDWordWriting;
  RawData.WriteBuffer(aEntry,sizeof(aEntry));
  WriteFixedBlockLength(before);
end;

procedure TVersionResource.WriteWideString(const aString: string);
var ws : widestring;
    w : word;
    i : integer;
begin
  ws:=aString;
  for i:=1 to length(ws) do
  begin
    w:=word(ws[i]);
    {$IFDEF ENDIAN_BIG}
    w:=SwapEndian(w);
    {$ENDIF}
    RawData.WriteBuffer(w,2);
  end;
  w:=0;
  RawData.WriteBuffer(w,2);
end;

function TVersionResource.GetType: TResourceDesc;
begin
  Result:=fType;
end;

function TVersionResource.GetName: TResourceDesc;
begin
  Result:=fName;
end;

function TVersionResource.ChangeDescTypeAllowed(aDesc: TResourceDesc): boolean;
begin
  Result:=false;
end;

function TVersionResource.ChangeDescValueAllowed(aDesc: TResourceDesc
  ): boolean;
begin
  Result:=false;
end;

procedure TVersionResource.NotifyResourcesLoaded;
begin
end;

constructor TVersionResource.Create;
begin
  inherited Create;
  fType:=TResourceDesc.Create(RT_VERSION);
  fName:=TResourceDesc.Create(1);
  SetDescOwner(fType);
  SetDescOwner(fName);
  fFixedInfo:=nil;
  fStringFileInfo:=nil;
  fVarFileInfo:=nil;
end;

constructor TVersionResource.Create(aType, aName: TResourceDesc);
begin
  Create;
end;

destructor TVersionResource.Destroy;
begin
  fType.Free;
  fName.Free;
  if fFixedInfo<>nil then fFixedInfo.Free;
  if fStringFileInfo<>nil then fStringFileInfo.Free;
  if fVarFileInfo<>nil then fVarFileInfo.Free;
  inherited Destroy;
end;

procedure TVersionResource.UpdateRawData;
begin
  if fFixedInfo=nil then exit;
  WriteData;
  FreeAndNil(fFixedInfo);
  FreeAndNil(fStringFileInfo);
  FreeAndNil(fVarFileInfo);
end;

initialization
  TResourceFactory.RegisterResourceClass(RT_VERSION,TVersionResource);

end.
