How to add .arc decompression to Inno Setup?

前端 未结 2 533
后悔当初
后悔当初 2020-11-29 11:46

I\'ve been trying to make an installer by Inno Setup which only supports zip/bzip/lzma/lzma2 compression methods. I packed my archive by FreeArc (output fi

相关标签:
2条回答
  • 2020-11-29 12:21

    This answer has been superseded by Inno Setup - How to add cancel button to decompressing page? that uses unarc.dll instead of driving the console Arc.exe.

    I'm keeping this answer, as its concept can be useful for other archive types.


    See the example below. It:

    • takes an ARC file, embeds it to the installer
    • during installation, the ARC file is extracted to a temporary folder
    • the files from the ARC file is extracted to the target folder
    #define ArcArchive "test.arc"
    
    [Files]
    Source: {#ArcArchive}; DestDir: "{tmp}"; Flags: nocompression deleteafterinstall
    Source: Arc.exe; Flags: dontcopy
    
    [Code]
    
    function BufferToAnsi(const Buffer: string): AnsiString;
    var
      W: Word;
      I: Integer;
    begin
      SetLength(Result, Length(Buffer) * 2);
      for I := 1 to Length(Buffer) do
      begin
        W := Ord(Buffer[I]);
        Result[(I * 2)] := Chr(W shr 8); { high byte }
        Result[(I * 2) - 1] := Chr(Byte(W)); { low byte }
      end;
    end;
    
    function SetTimer(
      Wnd: LongWord; IDEvent, Elapse: LongWord; TimerFunc: LongWord): LongWord;
      external 'SetTimer@user32.dll stdcall';
    function KillTimer(hWnd: LongWord; uIDEvent: LongWord): BOOL;
      external 'KillTimer@user32.dll stdcall';
    
    var
      ProgressPage: TOutputProgressWizardPage;
      ProgressFileName: string;
    
    procedure UpdateProgressProc(
      H: LongWord; Msg: LongWord; Event: LongWord; Time: LongWord);
    var
      S: AnsiString;
      L: Integer;
      P: Integer;
      Max: Integer;
      Progress: string;
      Buffer: string;
      Stream: TFileStream;
      Percent: Integer;
      Found: Boolean;
    begin
      Found := False;
      if not FileExists(ProgressFileName) then
      begin
        Log(Format('Progress file %s does not exist', [ProgressFileName]));
      end
        else
      begin
        try
          { Need shared read as the output file is locked for writting, }
          { so we cannot use LoadStringFromFile }
          Stream := TFileStream.Create(ProgressFileName, fmOpenRead or fmShareDenyNone);
          try
            L := Stream.Size;
            Max := 100*2014;
            if L > Max then
            begin
              Stream.Position := L - Max;
              L := Max;
            end;
            SetLength(Buffer, (L div 2) + (L mod 2));
            Stream.ReadBuffer(Buffer, L);
            S := BufferToAnsi(Buffer);
          finally
            Stream.Free;
          end;
    
          if S = '' then
          begin
            Log(Format('Progress file %s is empty', [ProgressFileName]));
          end;
        except
          Log(Format('Failed to read progress from file %s - %s', [
                     ProgressFileName, GetExceptionMessage]));
        end;
      end;
    
      if S <> '' then
      begin
        { Log(S); }
        P := Pos('Extracted', S);
        if P > 0 then
        begin
          Log('Extraction done');
          Percent := 100;
          Found := True;
        end
          else
        begin
          P := Pos('%', S);
          if P > 0 then
          begin
            repeat
              Progress := Copy(S, 1, P - 1);
              Delete(S, 1, P);
              P := Pos('%', S);
            until (P = 0);
    
            P := Length(Progress);
            while (P > 0) and
                  (((Progress[P] >= '0') and (Progress[P] <= '9')) or
                   (Progress[P] = '.')) do
            begin
              Dec(P);
            end;
    
            Progress := Copy(Progress, P + 1, Length(Progress) - P);
    
            P := Pos('.', Progress);
            if P > 0 then
            begin
              Progress := Copy(Progress, 1, P - 1);
            end;
    
            Percent := StrToInt(Progress);
            Log(Format('Percent: %d', [Percent]));
            Found := True;
          end;
        end;
      end;
    
      if not Found then
      begin
        Log('No new data found');
        { no new progress data, at least pump the message queue }
        ProgressPage.SetProgress(ProgressPage.ProgressBar.Position, 100);
      end
        else
      begin
        ProgressPage.SetProgress(Percent, 100);
        ProgressPage.SetText(Format('Extracted: %d%%', [Percent]), '');
      end;
    end;
    
    procedure ExtractArc;
    var
      ArcExtracterPath: string;
      ArcArchivePath: string;
      TempPath: string;
      CommandLine: string;
      Timer: LongWord;
      ResultCode: Integer;
      S: AnsiString;
      Message: string;
    begin
      ExtractTemporaryFile('Arc.exe');
    
      ProgressPage := CreateOutputProgressPage('Decompression', 'Decompressing archive...');
      ProgressPage.SetProgress(0, 100);
      ProgressPage.Show;
      try
        Timer := SetTimer(0, 0, 250, CreateCallback(@UpdateProgressProc));
    
        TempPath := ExpandConstant('{tmp}');
        ArcExtracterPath := TempPath + '\Arc.exe';
        ArcArchivePath := TempPath + '\{#ArcArchive}';
        ProgressFileName := ExpandConstant('{tmp}\progress.txt');
        Log(Format('Expecting progress in %s', [ProgressFileName]));
        CommandLine :=
          Format('"%s" x -y -o+ -dp"%s" "%s" > "%s"', [
            ArcExtracterPath, ExpandConstant('{app}'), ArcArchivePath, ProgressFileName]);
        Log(Format('Executing: %s', [CommandLine]));
        CommandLine := Format('/C "%s"', [CommandLine]);
        if not Exec(ExpandConstant('{cmd}'), CommandLine, '', SW_HIDE,
                    ewWaitUntilTerminated, ResultCode) then
        begin
          RaiseException('Cannot start extracter');
        end
          else
        if ResultCode <> 0 then
        begin
          LoadStringFromFile(ProgressFileName, S);
          Message := Format('Arc extraction failed failed with code %d', [ResultCode]);
          Log(Message);
          Log('Output: ' + S);
          RaiseException(Message);
        end
          else
        begin
          Log('Arc extraction done');
        end;
      finally
        { Clean up }
        Log('Arc extraction cleanup');
        KillTimer(0, Timer);
        ProgressPage.Hide;
        DeleteFile(ProgressFileName);
      end;
      Log('Arc extraction end');
    end;
    
    procedure CurStepChanged(CurStep: TSetupStep);
    begin
      if CurStep = ssPostInstall then
      begin
        ExtractArc;
      end;
    end;
    

    The code needs arc.exe (I've taken it from PeaZip portable package).

    For CreateCallback function, you need Inno Setup 6. If you are stuck with Inno Setup 5, you can use WrapCallback function from InnoTools InnoCallback library (and you need Unicode version of Inno Setup 5).


    Alternatively, to avoid double extraction, you can distribute the arc file along the installer.

    Just use {src} to resolve its path:

    ArcArchivePath := ExpandConstant('{src}\{#ArcArchive}');
    

    And remove the {#ArcArchive} entry from the [Files] section.


    It would be more robust to implement the extraction using unarc.dll, like seen in the FreeArc+InnoSetup package ISFreeArcExtract v.4.0.rar.

    0 讨论(0)
  • 2020-11-29 12:26

    Freearc Actually Comes with Inno Extraction Example http://freearc2.azurewebsites.net/InnoSetup.aspx

    0 讨论(0)
提交回复
热议问题