Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
558 views
in Technique[技术] by (71.8m points)

compression - How to add .arc decompression to Inno Setup?

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 file extension is .arc but renamed it to .bin) but Inno Setup is not able to extract it. I searched on internet how to implant arc decompression into Inno Setup but all sites refer to FreeArc official website which is dead for a while. All I need is the code to use the necessary dll files to give Inno Setup the ability to decompress arc archives plus the list of those dll files needed to do so. I appreciate any help.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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).

Decompressing


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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...