unit Msgs;

{
  Inno Setup
  Copyright (C) 1997-2007 Jordan Russell
  Portions by Martijn Laan
  For conditions of distribution and use, see LICENSE.TXT.

  Message file handling functions

  $jrsoftware: issrc/Projects/Msgs.pas,v 1.20 2009/03/23 13:31:17 mlaan Exp $
}

interface

uses
  MsgIDs, Struct;

const
  SNewLine = #13#10;  { line break }
  SNewLine2 = #13#10#13#10;  { double line break }

var
  SetupMessages: array[TSetupMessageID] of String;
  MessagesLangOptions: TMessagesLangOptions;

function FmtMessage(S: PChar; const Args: array of String): String;
function FmtSetupMessage(const ID: TSetupMessageID; const Args: array of String): String;
function FmtSetupMessage1(const ID: TSetupMessageID; const Arg1: String): String;
procedure AssignSetupMessages(const P; const Size: Cardinal);
procedure LoadSetupMessages(const Filename: String; const Offset: Cardinal;
  const LoadMessagesLangOptions: Boolean);
function IntToHexStr8(I: Integer): String;

const
  { You don't have to translate these. The only time they are used is when an
    error occurs before or while the messages file is loaded. Otherwise, it
    uses the corresponding messages in the messages file. }
  SSetupFileMissing = 'The file %1 is missing from the installation directory. ' +
    'Please correct the problem or obtain a new copy of the program.';
  SSetupFileCorrupt = 'The setup files are corrupted. Please obtain a new ' +
    'copy of the program.';
  SSetupFileCorruptOrWrongVer = 'The setup files are corrupted, or are ' +
    'incompatible with this version of Setup. Please correct the problem or ' +
    'obtain a new copy of the program.';
  SMsgsFileMissing = 'Messages file "%s" is missing. Please correct ' +
    'the problem or obtain a new copy of the program.';

implementation

uses
  Windows, SysUtils, Compress, CmnFunc2, FileClass;

const
  SMsgsFileTooLarge = 'Internal error: Messages file is too large';

function FmtMessage(S: PChar; const Args: array of String): String;
var
  P: PChar;
  Z: String;
begin
  Result := '';
  if S = nil then Exit;
  while True do begin
    P := StrScan(S, '%');
    if P = nil then begin
      Result := Result + S;
      Break;
    end;
    if P <> S then begin
      SetString(Z, S, P - S);
      Result := Result + Z;
      S := P;
    end;
    Inc(P);
    if CharInSet(P^, ['1'..'9']) and (Ord(P^) - Ord('1') <= High(Args)) then begin
      Result := Result + Args[Ord(P^) - Ord('1')];
      Inc(S, 2);
    end
    else begin
      Result := Result + '%';
      Inc(S);
      if P^ = '%' then
        Inc(S);
    end;
  end;
end;

function FmtSetupMessage(const ID: TSetupMessageID; const Args: array of String): String;
begin
  Result := FmtMessage(PChar(SetupMessages[ID]), Args);
end;

function FmtSetupMessage1(const ID: TSetupMessageID; const Arg1: String): String;
begin
  Result := FmtSetupMessage(ID, [Arg1]);
end;

procedure FreeSetupMessages;
var
  I: TSetupMessageID;
begin
  for I := Low(SetupMessages) to High(SetupMessages) do
    SetupMessages[I] := '';
end;

procedure Corrupted;
begin
  raise Exception.Create(SSetupFileCorrupt);
end;

procedure AssignSetupMessages(const P; const Size: Cardinal);
{ Takes message data, and assigns the individual messages to SetupMessages. }
var
  Header: ^TMessagesHeader;
  M, EndP: PChar;
  I: TSetupMessageID;
  L: Integer;
begin
  if (Size <= SizeOf(TMessagesHdrID) + SizeOf(TMessagesHeader)) or
     (TMessagesHdrID(P) <> MessagesHdrID) then
    Corrupted;
  Cardinal(Header) := Cardinal(@P) + SizeOf(TMessagesHdrID);
  if (Header.TotalSize <> not Header.NotTotalSize) or
     (Cardinal(Header.TotalSize) <> Size) or
     (Header.NumMessages <> (Ord(High(SetupMessages)) - Ord(Low(SetupMessages)) + 1)) then
    Corrupted;
  Cardinal(M) := Cardinal(Header) + SizeOf(TMessagesHeader);
  Cardinal(EndP) := Cardinal(@P) + Cardinal(Header.TotalSize);
  if (GetCRC32(M^, (EndP - M) * SizeOf(Char)) <> Header.CRCMessages) or
     (EndP[-1] <> #0) then
    Corrupted;
  for I := Low(SetupMessages) to High(SetupMessages) do begin
    if M >= EndP then
      Corrupted;
    L := StrLen(M);
    SetString(SetupMessages[I], M, L);
    Inc(M, L + 1);
  end;
end;

procedure LoadSetupMessages(const Filename: String; const Offset: Cardinal;
  const LoadMessagesLangOptions: Boolean);
{ Loads Setup messages from an uncompressed file }
var
  F: TFile;
  TestID: TMessagesHdrID;
  Header: TMessagesHeader;
  P: Pointer;
begin
  FreeSetupMessages;
  if not NewFileExists(Filename) then
    raise Exception.CreateFmt(SMsgsFileMissing, [Filename]);
  F := TFile.Create(Filename, fdOpenExisting, faRead, fsRead);
  try
    F.Seek(Offset);
    if F.Read(TestID, SizeOf(TestID)) <> SizeOf(TestID) then
      Corrupted;
    if TestID <> MessagesHdrID then
      Corrupted;
    if F.Read(Header, SizeOf(Header)) <> SizeOf(Header) then
      Corrupted;
    if (Header.TotalSize <> not Header.NotTotalSize) or
       (Header.TotalSize <= SizeOf(TestID) + SizeOf(Header)) or
       (Header.NumMessages <> (Ord(High(SetupMessages)) - Ord(Low(SetupMessages)) + 1)) then
      Corrupted;

    F.Seek(Offset);
    GetMem(P, Header.TotalSize);
    try
      if F.Read(P^, Header.TotalSize) <> Header.TotalSize then
        Corrupted;
      AssignSetupMessages(P^, Header.TotalSize);
    finally
      FreeMem(P);
    end;

    if LoadMessagesLangOptions then begin
      if F.Read(MessagesLangOptions, SizeOf(MessagesLangOptions)) <> SizeOf(MessagesLangOptions) then
        Corrupted;
      if MessagesLangOptions.ID <> MessagesLangOptionsID then
        Corrupted;
    end;
  finally
    F.Free;
  end;
end;

function IntToHexStr8(I: Integer): String;
begin
  FmtStr(Result, '0x%.8x', [I]);
end;

initialization
finalization
  FreeSetupMessages;
end.
