program DLLInstall;

{ Jednoduchý prográmek zajišťující instalaci potřebných verzí DLL knihoven
  oleaut32.dll a comctl32.dll na Windows 95. Zásadním problémem, který řeší,
  je nemožnost změny knihoven v systému, když jsou používány. Ve Windows na to
  existuje mechanismus, kdy se knihovnám dají dočasné názvy a zapíší se
  informace o instalaci do souboru wininit.ini. Při příštím startu si Windows
  knihovny z dočansých souborů aktualizují. Detaily procesu se dají najít
  v nápovědě k Win32 API. }

{$R *.res}

uses
  Windows, SysUtils, Classes;

const
  DLLDir = 'DLL';                // název adresáře s DLL knihovnami
  AppName: PChar = 'DLLInstall'; // název aplikace (do dialogů)

var
  SystemDirectory: string;       // systémový adresář
  WindowsDirectory: string;      // adresář Windows
  ReplacedSomething: Boolean;    // zda program vůbec nějaké knihovny nahradil

{ Nastaví správné hodnoty do proměnných SystemDirectory a WindowsDirectory. }
procedure SetDirectories;
begin
  SetLength(SystemDirectory, MAX_PATH);
  SetLength(SystemDirectory, GetSystemDirectory(@SystemDirectory[1], MAX_PATH));
  SetLength(WindowsDirectory, MAX_PATH);
  SetLength(WindowsDirectory, GetWindowsDirectory(@WindowsDirectory[1], MAX_PATH));
end;

{ Vydá True, pokud je danou knihovnu potřeba nahradit novou verzí. To je tehdy,
  když v sysému neexistuje (teoreticky nemožný případ) nebo pokud je její datum
  starší, než datum kniovny dodávané s programem.

  FileDateToDateTime používám proto, že FileAge vrací Integer, což je typ se
  zanménkem, a nikde není přesně řečeno, jak přesně získaná data provnávat.
  Typ TDataTime má naproti tomu přesně definovaný formát. Možná by stačilo
  výsldek FileAge přetypovat na Cardinal, ale nechci to riskovat. }
function DLLNeedsReplacing(FileName: string): Boolean;
begin
  Result := not FileExists(SystemDirectory + '\' + FileName) or (
    FileDateToDateTime(FileAge(SystemDirectory + '\' + FileName)) <
    FileDateToDateTime(FileAge(ExtractFilePath(ParamStr(0)) + DLLDir + '\' + FileName)));
end;

{ Způsobí nahrazení dané DLL. Jednak zkopíruje verzi dodávanou s The Life! do
  dočasného souboru adruhak zapíše potřebné informace do wininit.ini. Vydá True
  pokus se vše povedlo, jinak vrátí False. }
function ReplaceDLL(AFileName: string): Boolean;
var
  Lines: TStringList;
  index: Integer;
begin
  ReplacedSomething := True;

  Result := False;

  if not CopyFile(PChar(ExtractFilePath(ParamStr(0)) + DLLDir + '\' + AFileName),
    PChar(SystemDirectory + '\' + ChangeFileExt(AFileName, '.tmp')),
    False) then Exit;

  Result := True;

  Lines := TStringList.Create;
  with Lines do
    try
      try
        if FileExists(WindowsDirectory + '\wininit.ini') then
          LoadFromFile(WindowsDirectory + '\wininit.ini');

        { Data musí být v sekci [rename]. Pokud už v sobuoru je, zapíší se na
          její počáek, pokd tam není, tak se vytvoří. }
        index := Lines.IndexOf('[rename]');
        if index = - 1 then
        begin
          Add('[rename]');
          index := Count - 1;
        end;
        { Zde je důležité pořadí! Řádka s "NUL=..." musí být před tou druhou,
          aby se ta druhá zapsala v souboru před ní. Jinak by se smazal
          dočasný soubor před svou instalací jako DLL. }
        Insert(index + 1, 'NUL=' + SystemDirectory + '\' + ChangeFileExt(AFileName, '.tmp'));
        Insert(index + 1, SystemDirectory + '\' + AFileName + '=' +
          SystemDirectory + '\' + ChangeFileExt(AFileName, '.tmp'));
        SaveToFile(WindowsDirectory + '\wininit.ini');
      finally
        Free;
      end
    except
      Result := False;
    end;
end;

{ Provede vše potřebné k instalaci dané DLL. Pokud něco selže, zobrazí hlášení
  a ukončí program s chybovým kódem 1. }
procedure ProcessDLL(FileName: string);
begin
  if DLLNeedsReplacing(FileName) then
    if not ReplaceDLL(FileName) then
    begin
      MessageBox(0, PChar('Chyba při upgradu knihovny "' + FileName + '".'), AppName, MB_ICONSTOP);
      Halt(1);
    end;
end;

begin
  { Program běží jen na Windows 95. }
  if (Win32Platform <> VER_PLATFORM_WIN32_WINDOWS)
    or (Win32MajorVersion <> 4)
    or (Win32MinorVersion <> 0) then
  begin
    MessageBox(0, 'Program DLLInstall je určen pouze pro Windows 95. Není potřeba ho spouštět na jiných operačních systémech.'
      + #13#10#13#10'Bližší informace najdete v nápovědě The Life!', AppName, MB_ICONINFORMATION);
    Exit;
  end;

  SetDirectories;
  ProcessDLL('oleaut32.dll');
  ProcessDLL('comctl32.dll');

  { Pokud je nutné instalovat aspoň jednu knihovnu, nabídne restart počítače.
    Jinak oznámí, že je všechno OK. }
  if ReplacedSomething then
  begin
    if MessageBox(0, PChar('Upgrade DLL knihoven byl dokončen. Před spuštěním The Life! je nutno restartovat počítač, aby se změny projevily.'
      + #13#10#13#10'Chcete restartovat počátač nyní?'), AppName, MB_ICONQUESTION + MB_YESNO) = ID_YES then
      ExitWindowsEx(EWX_REBOOT, 0);
  end
  else
    MessageBox(0, PChar('Všechny DLL knihovny potřebné pro běh The Life! jsou již nainstalovány v dostatečné verzi.'), AppName, MB_ICONINFORMATION);
end.