Lazarus

Programming => General => Topic started by: geraldholdsworth on June 04, 2025, 06:44:20 pm

Title: TZipper - adding to existing archive
Post by: geraldholdsworth on June 04, 2025, 06:44:20 pm
This has been covered a few times in the past, but no solution (apart from using Abrevia) have been forthcoming...that I can see.

So, I looked into this. Essentially, a ZIP file is just a collection of compressed files, each file having it's own file detail entry. Therefore, it should just be a simple case of combining two or more ZIP files to create one big ZIP file. All that needs to be done is to combine the central library at the end and re-create the End of Central Library entry.

With this in mind, and my code already using TZipper to ZIP files, I changed it so that the method ZIPs the file to a temporary file, rather than the existing one. I've now written a ZIP combiner method:
Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls;
  9.  
  10. type
  11.  
  12.  { TForm1 }
  13.  
  14.  TForm1 = class(TForm)
  15.   Button1: TButton;
  16.   Button2: TButton;
  17.   Memo1: TMemo;
  18.   procedure Button1Click(Sender: TObject);
  19.   procedure CombineZIP(files: array of String);
  20.   procedure Button2Click(Sender: TObject);
  21.  private
  22.  
  23.  public
  24.   const
  25.    Lfolder='/Path/To/Your/Folder';
  26.  end;
  27.  
  28. var
  29.  Form1: TForm1;
  30.  
  31. implementation
  32.  
  33. {$R *.lfm}
  34.  
  35. { TForm1 }
  36.  
  37. procedure TForm1.Button1Click(Sender: TObject);
  38. begin
  39.  Memo1.Lines.Clear;
  40.  CombineZIP([LFolder+'/Test-empty.zip',LFolder+'/Test-archive 1.zip']);
  41.  CombineZIP([LFolder+'/Output.zip'    ,LFolder+'/Test-archive 2.zip']);
  42. end;
  43.  
  44. procedure TForm1.CombineZIP(files: array of String);
  45.  //Finds the central library and returns the end of central library
  46.  function FindCL(var EoCL: Integer;var buffer: array of Byte): Integer;
  47.  var
  48.   i: Integer=0;
  49.  begin
  50.   //Start with a default (i.e., not found)
  51.   Result:=-1;
  52.   EoCL:=-1;
  53.   //Can't have a file smaller than 22 bytes
  54.   if Length(buffer)<22 then exit;
  55.   //Start here
  56.   i:=Length(buffer)-3;
  57.   //And work backwards until we find the EoCL marker
  58.   repeat
  59.     dec(i);
  60.   until((buffer[i  ]=$50)
  61.      and(buffer[i+1]=$4B)
  62.      and(buffer[i+2]=$05)
  63.      and(buffer[i+3]=$06))
  64.      or (i=0);
  65.   //Found OK?
  66.   if (buffer[i  ]=$50)
  67.   and(buffer[i+1]=$4B)
  68.   and(buffer[i+2]=$05)
  69.   and(buffer[i+3]=$06) then
  70.   begin
  71.    //Mark it
  72.    EoCL:=i;
  73.    //Retreive where the central library starts
  74.    Result:=buffer[i+$10]
  75.           +buffer[i+$11]<<8
  76.           +buffer[i+$12]<<16
  77.           +buffer[i+$13]<<24;
  78.   end;
  79.  end;
  80. //Main method definitions
  81. var
  82.  input     : array[0..1] of TFileStream;
  83.  output    : TFileStream;
  84.  inbuffer  : array[0..1] of array of Byte;
  85.  outbuffer : array of Byte;
  86.  ptr       : Integer=0;    
  87.  cnt       : Integer=0;
  88.  fileptr   : Cardinal=0;
  89.  CL        : array[0..1] of Integer;
  90.  EoCL      : array[0..1] of Integer;
  91.  temp      : Cardinal=0;
  92.  numfiles  : Cardinal=0;
  93.  CLsize    : Cardinal=0;
  94.  filesize  : Cardinal=0;
  95.  ok        : Boolean=True;
  96. //Main method starts here
  97. begin
  98.  //Read in the files
  99.  for ptr:=0 to 1 do
  100.  begin
  101.   Memo1.Lines.Add('Opening '+files[ptr]);
  102.   input[ptr]:=TFileStream.Create(files[ptr],fmOpenRead OR fmShareDenyNone);
  103.   input[ptr].Position:=0;
  104.   SetLength(inbuffer[ptr],input[ptr].Size);
  105.   input[ptr].Read(inbuffer[ptr][0],input[ptr].Size);
  106.   input[ptr].Free;
  107.   //Get the position of the central library for each
  108.   CL[ptr]:=FindCL(EoCL[ptr],inbuffer[ptr]);
  109.   ok:=(ok)AND(CL[ptr]<>-1)AND(EoCL[ptr]<>-1);
  110.   Memo1.Lines.Add('File '+IntToStr(ptr+1)+' size                  : '+IntToHex(Length(inbuffer[ptr]),8));
  111.   if CL[ptr]<>-1 then
  112.    Memo1.Lines.Add('Central Library file '+IntToStr(ptr+1)+'       : '+IntToHex(CL[ptr],8));
  113.   if EoCL[ptr]<>-1 then
  114.   begin
  115.    Memo1.Lines.Add('End of Central Library file '+IntToStr(ptr+1)+': '+IntToHex(EoCL[ptr],8));
  116.    //Count the number of files stored
  117.    inc(numfiles,inbuffer[ptr][EoCL[ptr]+$A]+inbuffer[ptr][EoCL[ptr]+$B]<<8);
  118.   end;
  119.  end;
  120.  //Create the output file
  121.  if ok then
  122.  begin
  123.   Memo1.Lines.Add('Total number of files        : '+IntToStr(numfiles));
  124.   //This will be the eventual central library size
  125.   CLsize:=(EoCL[0]-CL[0])+(EoCL[1]-CL[1]);
  126.   Memo1.Lines.Add('Eventual Central Library size: '+IntToHex(CLsize,8));
  127.   //This will be the eventual file size
  128.   filesize:=CL[0]+CL[1]+CLsize+22;
  129.   Memo1.Lines.Add('Eventual file size           : '+IntToHex(filesize,8));
  130.   SetLength(outbuffer,filesize);
  131.   //Write the files. The files from the second ZIP goes where the first CL was
  132.   fileptr:=0;
  133.   for cnt:=0 to 1 do
  134.   begin
  135.    Memo1.Lines.Add('Files from ZIP '+IntToStr(cnt+1)+' at          : '+IntToHex(fileptr,8));
  136.    for ptr:=0 to CL[cnt]-1 do outbuffer[fileptr+ptr]:=inbuffer[cnt][ptr];
  137.    inc(fileptr,CL[cnt]);
  138.   end;
  139.   //Write the CLs
  140.   for cnt:=0 to 1 do
  141.   begin
  142.    Memo1.Lines.Add('CL from ZIP '+IntToStr(cnt+1)+' at             : '+IntToHex(fileptr,8));
  143.    //We'll need to find each file entry and adjust by adding CL1 to the adddress
  144.    for ptr:=CL[cnt] to EoCL[cnt]-1 do
  145.    begin
  146.     outbuffer[fileptr-CL[cnt]+ptr]:=inbuffer[cnt][ptr];
  147.     if cnt>0 then
  148.      //Found a file?
  149.      if (inbuffer[cnt][ptr-$2E]=$50)
  150.      and(inbuffer[cnt][ptr-$2D]=$4B)
  151.      and(inbuffer[cnt][ptr-$2C]=$01)
  152.      and(inbuffer[cnt][ptr-$2B]=$02)then
  153.      begin
  154.       //Get the data offset
  155.       temp:=inbuffer[cnt][ptr-4]
  156.            +inbuffer[cnt][ptr-3]<<8
  157.            +inbuffer[cnt][ptr-2]<<16
  158.            +inbuffer[cnt][ptr-1]<<24;
  159.       Memo1.Lines.Add('CL file found at             : '+IntToHex((fileptr-CL[cnt]+ptr)-$2E,8));
  160.       Memo1.Lines.Add('Data offset                  : '+IntToHex(temp,8));
  161.       //Adjust the data offset
  162.       inc(temp,CL[cnt-1]);
  163.       Memo1.Lines.Add('New data offset              : '+IntToHex(temp,8));
  164.       //Save back
  165.       outbuffer[(fileptr-CL[cnt]+ptr)-4]:= temp AND $000000FF;
  166.       outbuffer[(fileptr-CL[cnt]+ptr)-3]:=(temp AND $0000FF00)>>8;
  167.       outbuffer[(fileptr-CL[cnt]+ptr)-2]:=(temp AND $00FF0000)>>16;
  168.       outbuffer[(fileptr-CL[cnt]+ptr)-1]:=(temp AND $FF000000)>>24;
  169.      end;
  170.    end;
  171.    inc(fileptr,CL[cnt]);
  172.   end;
  173.   //Write the central directory
  174.   fileptr:=filesize-22;
  175.   outbuffer[fileptr    ]:=$50;
  176.   outbuffer[fileptr+$01]:=$4B;
  177.   outbuffer[fileptr+$02]:=$05;
  178.   outbuffer[fileptr+$03]:=$06;
  179.   outbuffer[fileptr+$08]:= numfiles AND $00FF;
  180.   outbuffer[fileptr+$09]:=(numfiles AND $FF00)>>8;
  181.   outbuffer[fileptr+$0A]:= numfiles AND $00FF;
  182.   outbuffer[fileptr+$0B]:=(numfiles AND $FF00)>>8;
  183.   outbuffer[fileptr+$0C]:= CLsize AND $000000FF;
  184.   outbuffer[fileptr+$0D]:=(CLsize AND $0000FF00)>>8;
  185.   outbuffer[fileptr+$0E]:=(CLsize AND $00FF0000)>>16;
  186.   outbuffer[fileptr+$0F]:=(CLsize AND $FF000000)>>24;
  187.   outbuffer[fileptr+$10]:= (CL[0]+CL[1]) AND $000000FF;
  188.   outbuffer[fileptr+$11]:=((CL[0]+CL[1]) AND $0000FF00)>>8;
  189.   outbuffer[fileptr+$12]:=((CL[0]+CL[1]) AND $00FF0000)>>16;
  190.   outbuffer[fileptr+$13]:=((CL[0]+CL[1]) AND $FF000000)>>24;
  191.   //Save the data to a file
  192.   output:=TFileStream.Create(Lfolder+'/Output.zip',fmCreate OR fmShareDenyNone);
  193.   output.Position:=0;
  194.   output.Write(outbuffer[0],Length(outbuffer));
  195.   output.Free;
  196.   //Output the notes
  197.   Memo1.Lines.SaveToFile(LFolder+'/Output.txt');
  198.  end;
  199. end;
  200.  
  201. procedure TForm1.Button2Click(Sender: TObject);
  202. //Create an empty ZIP file
  203. var
  204.  outbuffer: array of Byte;
  205.  output   : TFileStream;
  206. begin
  207.  Memo1.Clear;
  208.  SetLength(outbuffer,22);
  209.  outbuffer[$00]:=$50;
  210.  outbuffer[$01]:=$4B;
  211.  outbuffer[$02]:=$05;
  212.  outbuffer[$03]:=$06;
  213.  output:=TFileStream.Create(Lfolder+'/Test-empty.zip',fmCreate OR fmShareDenyNone);
  214.  output.Position:=0;
  215.  output.Write(outbuffer[0],Length(outbuffer));
  216.  output.Free;
  217.  Memo1.Lines.Add('Empty ZIP file created');
  218. end;
  219.  
  220. end.
  221.  
When Button1 is clicked, it first combines the empty ZIP file (created by Button2) with one of the prepared ZIP files (which would be the temporary ZIP created as described), and outputs to a separate file. It then does it again, but this time using the newly created ZIP file and adding another file. This simulates adding two files to an empty ZIP, one by one.

Of course, there are no checks to ensure the ZIP files are valid, aside from checking for where the End of Central Library and Central Library are.

And, yes, there are probably better ways of doing this. Apologies if something like this has already been posted before.

I'll probably add to this later on with a method to validate a ZIP file.
Title: Re: TZipper - adding to existing archive
Post by: geraldholdsworth on June 05, 2025, 10:51:19 am
And, as promised, something to validate a ZIP file:
Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls, StdCtrls;
  9.  
  10. type
  11.  
  12.  { TForm1 }
  13.  
  14.  TForm1 = class(TForm)
  15.   Panel1: TPanel;
  16.   Memo1: TMemo;
  17.   function FindCL(var EoCL: Integer;var buffer: array of Byte): Integer;
  18.   procedure FormDropFiles(Sender: TObject; const FileNames: array of string);
  19.   procedure ValidateZIPFile(filename: String);
  20.  private
  21.  
  22.  public
  23.  
  24.  end;
  25.  
  26. var
  27.  Form1: TForm1;
  28.  
  29. implementation
  30.  
  31. {$R *.lfm}
  32.  
  33. { TForm1 }
  34.  
  35.  
  36. //Finds the central library and returns the end of central library
  37. function TForm1.FindCL(var EoCL: Integer;var buffer: array of Byte): Integer;
  38. var
  39.  i: Integer=0;
  40. begin
  41.  //Start with a default (i.e., not found)
  42.  Result:=-1;
  43.  EoCL:=-1;
  44.  //Can't have a file smaller than 22 bytes
  45.  if Length(buffer)<22 then exit;
  46.  //Start here
  47.  i:=Length(buffer)-3;
  48.  //And work backwards until we find the EoCL marker
  49.  repeat
  50.    dec(i);
  51.  until((buffer[i  ]=$50)
  52.     and(buffer[i+1]=$4B)
  53.     and(buffer[i+2]=$05)
  54.     and(buffer[i+3]=$06))
  55.     or (i=0);
  56.  //Found OK?
  57.  if (buffer[i  ]=$50)
  58.  and(buffer[i+1]=$4B)
  59.  and(buffer[i+2]=$05)
  60.  and(buffer[i+3]=$06) then
  61.  begin
  62.   //Mark it
  63.   EoCL:=i;
  64.   //Retreive where the central library starts
  65.   Result:=buffer[i+$10]
  66.          +buffer[i+$11]<<8
  67.          +buffer[i+$12]<<16
  68.          +buffer[i+$13]<<24;
  69.  end;
  70. end;
  71.  
  72. procedure TForm1.FormDropFiles(Sender: TObject; const FileNames: array of string
  73.  );
  74. var
  75.  i: Integer=0;
  76. begin
  77.  Memo1.Lines.Clear;
  78.  for i:=0 to Length(FileNames)-1 do
  79.  begin
  80.   ValidateZIPFile(FileNames[i]);
  81.   if i<>Length(FileNames)-1 then
  82.    Memo1.Lines.Add('----------------------------------------------------------');
  83.  end;
  84. end;
  85.  
  86. procedure TForm1.ValidateZIPFile(filename: String);
  87. var
  88.  Lfile   : TFileStream;
  89.  Lbuffer : array of Byte;
  90.  CL      : Integer=-1;
  91.  EoCL    : Integer=-1;
  92.  valid   : Boolean=False;
  93.  check   : Boolean=False;
  94.  temp    : Cardinal=0;
  95.  numfiles: Integer=0;
  96.  index   : Integer=0;
  97.  ptr     : Cardinal=0;
  98.  data    : Cardinal=0;
  99.  size    : Cardinal=0;
  100.  usize   : Cardinal=0;
  101.  //Check the length of the extra field
  102.  function CheckExtraField(addr,len: Cardinal): Boolean;
  103.  var
  104.   total: Cardinal=0;
  105.  begin
  106.   Result:=False;
  107.   Memo1.Lines.Add('Extra field offset           : '+IntToHex(addr,8));
  108.   Memo1.Lines.Add('Extra field given length     : '+IntToHex(len,8));
  109.   while(total<len)and(addr<Length(Lbuffer)-4)do
  110.   begin
  111.    //Extra field has pairs of 2 byte tag + 2 byte length
  112.    inc(total,4+Lbuffer[addr+2]+Lbuffer[addr+3]<<8);
  113.    inc(addr,4+Lbuffer[addr+2]+Lbuffer[addr+3]<<8);
  114.   end;
  115.   Memo1.Lines.Add('Extra field calculated length: '+IntToHex(total,8));
  116.   Result:=total=len;
  117.  end;
  118. begin
  119.  //Read the file into the buffer
  120.  Memo1.Lines.Add('Opening file '+ExtractFilename(filename));
  121.  Lfile:=TFileStream.Create(filename,fmOpenRead OR fmShareDenyNone);
  122.  Lfile.Position:=0;
  123.  SetLength(Lbuffer,LFile.Size);
  124.  Lfile.Read(Lbuffer[0],Lfile.Size);
  125.  Lfile.Free;
  126.  //File length
  127.  Memo1.Lines.Add('File length                  : '+IntToHex(Length(Lbuffer),8));
  128.  //Find the central library
  129.  CL:=FindCL(EoCL,Lbuffer);
  130.  if CL=-1   then Memo1.Lines.Add('Central Library not found');
  131.  if EoCL=-1 then Memo1.Lines.Add('End of Central Library not found');
  132.  if(CL<>-1)and(EoCL<>-1)then
  133.  begin
  134.   valid:=True;
  135.   Memo1.Lines.Add('Central Library              : '+IntToHex(CL,8));
  136.   Memo1.Lines.Add('End of Central Library       : '+IntToHex(EoCL,8));
  137.   //Size of Central Library
  138.   temp:=Lbuffer[EoCL+$C]
  139.        +Lbuffer[EoCL+$D]<<8
  140.        +Lbuffer[EoCL+$E]<<16
  141.        +Lbuffer[EoCL+$F]<<24;
  142.   Memo1.Lines.Add('Central Library size         : '+IntToHex(temp,8));
  143.   //Test to see if it matches the references already found
  144.   check:=temp=EoCL-CL;
  145.   if check then Memo1.Lines.Add('Matches references')
  146.   else Memo1.Lines.Add('Does not match references');
  147.   valid:=(check)AND(valid);
  148.   //Total number of files
  149.   numfiles:=Lbuffer[EoCL+$A]
  150.            +Lbuffer[EoCL+$B]<<8;
  151.   Memo1.Lines.Add('Total number of files        : '+IntToStr(numfiles));
  152.   Memo1.Lines.Add('Checking files');
  153.   ptr:=CL;
  154.   index:=0;
  155.   while(index<numfiles)and(ptr<EoCL)do
  156.   begin
  157.    Memo1.Lines.Add('File #'+IntToStr(index+1)+' at offset '+IntToHex(ptr,8));
  158.    //Check the signature
  159.    check:=(Lbuffer[ptr]=$50)
  160.        and(Lbuffer[ptr+1]=$4B)
  161.        and(Lbuffer[ptr+2]=$01)
  162.        and(Lbuffer[ptr+3]=$02);
  163.    if check then Memo1.Lines.Add('Valid signature')
  164.             else Memo1.Lines.Add('Invalid signature');
  165.    valid:=(check)AND(valid);
  166.    //Check the extra field
  167.    temp:=Lbuffer[ptr+$1E]+Lbuffer[ptr+$1F]<<8;
  168.    check:=True;
  169.    if temp>0 then
  170.     check:=CheckExtraField(ptr+$2E+Lbuffer[ptr+$1C]+Lbuffer[ptr+$1D]<<8,temp);
  171.    if check then Memo1.Lines.Add('Extra field size match')
  172.             else Memo1.Lines.Add('Extra field size mis-match');
  173.    valid:=(check)AND(valid);
  174.    //Compressed size
  175.    size:=Lbuffer[ptr+$14]
  176.         +Lbuffer[ptr+$15]<<8
  177.         +Lbuffer[ptr+$16]<<16
  178.         +Lbuffer[ptr+$17]<<24;
  179.    Memo1.Lines.Add('Compressed size              : '+IntToHex(size,8));
  180.    //Uncompressed size
  181.    usize:=Lbuffer[ptr+$18]
  182.          +Lbuffer[ptr+$19]<<8
  183.          +Lbuffer[ptr+$1A]<<16
  184.          +Lbuffer[ptr+$1B]<<24;
  185.    Memo1.Lines.Add('Uncompressed size            : '+IntToHex(usize,8));
  186.    //Data pointer
  187.    data:=Lbuffer[ptr+$2A]
  188.         +Lbuffer[ptr+$2B]<<8
  189.         +Lbuffer[ptr+$2C]<<16
  190.         +Lbuffer[ptr+$2D]<<24;
  191.    Memo1.Lines.Add('Data pointer                 : '+IntToHex(data,8));
  192.    //Check the local file entry
  193.    Memo1.Lines.Add('Checking local file entry');
  194.    check:=(Lbuffer[data]=$50)
  195.        and(Lbuffer[data+1]=$4B)
  196.        and(Lbuffer[data+2]=$03)
  197.        and(Lbuffer[data+3]=$04);
  198.    if check then Memo1.Lines.Add('Valid signature')
  199.             else Memo1.Lines.Add('Invalid signature');
  200.    valid:=(check)AND(valid);
  201.    //Check compressed size
  202.    check:=Lbuffer[data+$12]
  203.          +Lbuffer[data+$13]<<8
  204.          +Lbuffer[data+$14]<<16
  205.          +Lbuffer[data+$15]<<24=size;
  206.    if check then Memo1.Lines.Add('Compressed size match')
  207.             else Memo1.Lines.Add('Compressed size mis-match');
  208.    valid:=(check)AND(valid);
  209.    //Check uncompressed size
  210.    check:=Lbuffer[data+$16]
  211.          +Lbuffer[data+$17]<<8
  212.          +Lbuffer[data+$18]<<16
  213.          +Lbuffer[data+$19]<<24=usize;
  214.    if check then Memo1.Lines.Add('Uncompressed size match')
  215.             else Memo1.Lines.Add('Uncompressed size mis-match');
  216.    valid:=(check)AND(valid);
  217.    //Check the extra field
  218.    temp:=Lbuffer[data+$1C]+Lbuffer[data+$1D]<<8;
  219.    check:=True;
  220.    if temp>0 then
  221.     check:=CheckExtraField(data+$1E+Lbuffer[data+$1A]+Lbuffer[data+$1B]<<8,temp);
  222.    if check then Memo1.Lines.Add('Extra field size match')
  223.             else Memo1.Lines.Add('Extra field size mis-match');
  224.    valid:=(check)AND(valid);
  225.    //Next entry - here we work out where it should be
  226.    temp:=ptr+$2E
  227.             +Lbuffer[ptr+$1C]+Lbuffer[ptr+$1D]<<8
  228.             +Lbuffer[ptr+$1E]+Lbuffer[ptr+$1F]<<8
  229.             +Lbuffer[ptr+$20]+Lbuffer[ptr+$21]<<8;
  230.    //Now we find the next marker
  231.    repeat
  232.     inc(ptr,1);
  233.    until(ptr=EoCL)or((Lbuffer[ptr  ]=$50)
  234.                   and(Lbuffer[ptr+1]=$4B)
  235.                   and(Lbuffer[ptr+2]=$01)
  236.                   and(Lbuffer[ptr+3]=$02));
  237.    //And check if they match
  238.    check:=ptr=temp;
  239.    if check then Memo1.Lines.Add('Central Library entry size match')
  240.             else Memo1.Lines.Add('Central Library entry size mis-match');
  241.    valid:=(check)AND(valid);
  242.    //Next file
  243.    inc(index);
  244.   end;
  245.   //Check we got all the files
  246.   check:=index=numfiles;
  247.   if check then Memo1.Lines.Add('Correct number of files found')
  248.            else Memo1.Lines.Add('Incorrect number of files');
  249.   valid:=(check)AND(valid);
  250.  end;
  251.  if valid then Memo1.Lines.Add('File is a valid ZIP file')
  252.           else Memo1.Lines.Add('File is an invalid ZIP file');
  253. end;
  254.  
  255. end.
Title: Re: TZipper - adding to existing archive
Post by: gues1 on June 05, 2025, 02:07:06 pm
I'll start by saying that I don't know the internal path of a zip, but does it work even if the two files use two different compression methods (such as "deflate" and "LZMA")?
Title: Re: TZipper - adding to existing archive
Post by: geraldholdsworth on June 05, 2025, 03:17:22 pm
Should do. These routines don't touch the compressed data at all, and only touch the Local File Entry (just before each data), and the Central Library (or directory) at the end, which holds all of the file details and where to find the data.

What they won't do is deal with ZIP64 or mulit-disc ZIP files...yet.
Title: Re: TZipper - adding to existing archive
Post by: geraldholdsworth on June 05, 2025, 03:50:21 pm
If you want some methods without the reporting to Memo, etc., I've put them into a single unit:
Code: Pascal  [Select][+][-]
  1. unit ZIPTools;
  2. {
  3. ZIP Tools V1.00
  4. Written by Gerald J Holdsworth
  5.  
  6. This source is free software; you can redistribute it and/or modify it under
  7. the terms of the GNU General Public Licence as published by the Free
  8. Software Foundation; either version 3 of the Licence, or (at your option)
  9. any later version.
  10.  
  11. This code is distributed in the hope that it will be useful, but WITHOUT ANY
  12. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  13. FOR A PARTICULAR PURPOSE.  See the GNU General Public Licence for more
  14. details.
  15.  
  16. A copy of the GNU General Public Licence is available on the World Wide Web
  17. at <http://www.gnu.org/copyleft/gpl.html>. You can also obtain it by writing
  18. to the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
  19. Boston, MA 02110-1335, USA.
  20. }
  21.  
  22. {$mode ObjFPC}{$H+}
  23.  
  24. interface
  25.  
  26. uses
  27.  Classes,SysUtils;
  28.  
  29. function FindCL(var EoCL: Integer;var buffer: array of Byte): Integer;
  30. function ValidateZIPFile(var buffer: array of Byte): Boolean;
  31. function CombineZIP(files: array of String; outputfile: String): Boolean;
  32.  
  33. implementation
  34.  
  35. //Finds the central library and returns the end of central library
  36. function FindCL(var EoCL: Integer;var buffer: array of Byte): Integer;
  37. var
  38.  i: Integer=0;
  39. begin
  40.  //Start with a default (i.e., not found)
  41.  Result:=-1;
  42.  EoCL:=-1;
  43.  //Can't have a file smaller than 22 bytes
  44.  if Length(buffer)<22 then exit;
  45.  //Start here
  46.  i:=Length(buffer)-3;
  47.  //And work backwards until we find the EoCL marker
  48.  repeat
  49.    dec(i);
  50.  until((buffer[i  ]=$50)
  51.     and(buffer[i+1]=$4B)
  52.     and(buffer[i+2]=$05)
  53.     and(buffer[i+3]=$06))
  54.     or (i=0);
  55.  //Found OK?
  56.  if (buffer[i  ]=$50)
  57.  and(buffer[i+1]=$4B)
  58.  and(buffer[i+2]=$05)
  59.  and(buffer[i+3]=$06) then
  60.  begin
  61.   //Mark it
  62.   EoCL:=i;
  63.   //Retreive where the central library starts
  64.   Result:=buffer[i+$10]
  65.          +buffer[i+$11]<<8
  66.          +buffer[i+$12]<<16
  67.          +buffer[i+$13]<<24;
  68.  end;
  69. end;
  70.  
  71. //Validate a ZIP file
  72. function ValidateZIPFile(var buffer: array of Byte): Boolean;
  73. var
  74.  Lfile   : TFileStream;
  75.  CL      : Integer=-1;
  76.  EoCL    : Integer=-1;
  77.  check   : Boolean=False;
  78.  temp    : Cardinal=0;
  79.  numfiles: Integer=0;
  80.  index   : Integer=0;
  81.  ptr     : Cardinal=0;
  82.  data    : Cardinal=0;
  83.  size    : Cardinal=0;
  84.  usize   : Cardinal=0;
  85.  //Check the length of the extra field
  86.  function CheckExtraField(addr,len: Cardinal): Boolean;
  87.  var
  88.   total: Cardinal=0;
  89.  begin
  90.   Result:=False;
  91.   while(total<len)and(addr<Length(buffer)-4)do
  92.   begin
  93.    //Extra field has pairs of 2 byte tag + 2 byte length
  94.    inc(total,4+buffer[addr+2]+buffer[addr+3]<<8);
  95.    inc(addr,4+buffer[addr+2]+buffer[addr+3]<<8);
  96.   end;
  97.   Result:=total=len;
  98.  end;
  99. begin
  100.  Result:=False;
  101.  //Minimum size of a ZIP file is 22 bytes
  102.  if Length(buffer)>=22 then
  103.  begin
  104.   //Find the central library
  105.   CL:=FindCL(EoCL,buffer);
  106.   if(CL<>-1)and(EoCL<>-1)then
  107.   begin
  108.    Result:=True;
  109.    //Size of Central Library
  110.    temp:=buffer[EoCL+$C]
  111.         +buffer[EoCL+$D]<<8
  112.         +buffer[EoCL+$E]<<16
  113.         +buffer[EoCL+$F]<<24;
  114.    //Test to see if it matches the references already found
  115.    Result:=(temp=EoCL-CL)AND(Result);
  116.    //Total number of files
  117.    numfiles:=buffer[EoCL+$A]
  118.             +buffer[EoCL+$B]<<8;
  119.    ptr:=CL;
  120.    index:=0;
  121.    while(index<numfiles)and(ptr<EoCL)do
  122.    begin
  123.     //Check the signature
  124.     Result:=(buffer[ptr  ]=$50)
  125.          and(buffer[ptr+1]=$4B)
  126.          and(buffer[ptr+2]=$01)
  127.          and(buffer[ptr+3]=$02)
  128.          AND(Result);
  129.     //Check the extra field
  130.     temp:=buffer[ptr+$1E]+buffer[ptr+$1F]<<8;
  131.     if temp>0 then
  132.      Result:=(CheckExtraField(ptr+$2E+buffer[ptr+$1C]+buffer[ptr+$1D]<<8,temp))
  133.          AND(Result);
  134.     //Compressed size
  135.     size:=buffer[ptr+$14]
  136.          +buffer[ptr+$15]<<8
  137.          +buffer[ptr+$16]<<16
  138.          +buffer[ptr+$17]<<24;
  139.     //Uncompressed size
  140.     usize:=buffer[ptr+$18]
  141.           +buffer[ptr+$19]<<8
  142.           +buffer[ptr+$1A]<<16
  143.           +buffer[ptr+$1B]<<24;
  144.     //Data pointer
  145.     data:=buffer[ptr+$2A]
  146.          +buffer[ptr+$2B]<<8
  147.          +buffer[ptr+$2C]<<16
  148.          +buffer[ptr+$2D]<<24;
  149.     //Check the local file entry
  150.     Result:=(buffer[data]=$50)
  151.          and(buffer[data+1]=$4B)
  152.          and(buffer[data+2]=$03)
  153.          and(buffer[data+3]=$04)
  154.          AND(Result);
  155.     //Check compressed size
  156.     Result:=(buffer[data+$12]
  157.             +buffer[data+$13]<<8
  158.             +buffer[data+$14]<<16
  159.             +buffer[data+$15]<<24=size)
  160.          AND(Result);
  161.     //Check uncompressed size
  162.     Result:=(buffer[data+$16]
  163.             +buffer[data+$17]<<8
  164.             +buffer[data+$18]<<16
  165.             +buffer[data+$19]<<24=usize)
  166.          AND(Result);
  167.     //Check the extra field
  168.     temp:=buffer[data+$1C]+buffer[data+$1D]<<8;
  169.     if temp>0 then
  170.      Result:=(CheckExtraField(data+$1E+buffer[data+$1A]+buffer[data+$1B]<<8,temp))
  171.           AND(Result);
  172.     //Next entry - here we work out where it should be
  173.     temp:=ptr+$2E
  174.              +buffer[ptr+$1C]+buffer[ptr+$1D]<<8
  175.              +buffer[ptr+$1E]+buffer[ptr+$1F]<<8
  176.              +buffer[ptr+$20]+buffer[ptr+$21]<<8;
  177.     //Now we find the next marker
  178.     repeat
  179.      inc(ptr);
  180.     until(ptr=EoCL)or((buffer[ptr  ]=$50)
  181.                    and(buffer[ptr+1]=$4B)
  182.                    and(buffer[ptr+2]=$01)
  183.                    and(buffer[ptr+3]=$02));
  184.     //And check if they match
  185.     Result:=(ptr=temp)AND(Result);
  186.     //Next file
  187.     inc(index);
  188.    end;
  189.    //Check we got all the files
  190.    Result:=(index=numfiles)AND(Result);
  191.   end;
  192.  end;
  193. end;
  194.  
  195. //Merges two ZIP files
  196. function CombineZIP(files: array of String; outputfile: String): Boolean;
  197. var
  198.  input     : array[0..1] of TFileStream;
  199.  output    : TFileStream;
  200.  inbuffer  : array[0..1] of array of Byte;
  201.  outbuffer : array of Byte;
  202.  ptr       : Integer=0;
  203.  cnt       : Integer=0;
  204.  fileptr   : Cardinal=0;
  205.  CL        : array[0..1] of Integer;
  206.  EoCL      : array[0..1] of Integer;
  207.  temp      : Cardinal=0;
  208.  numfiles  : Cardinal=0;
  209.  CLsize    : Cardinal=0;
  210.  filesize  : Cardinal=0;
  211. begin
  212.  Result:=False;
  213.  if Length(files)>=2 then //Only works with two files, the rest are ignored
  214.  begin
  215.   Result:=True;
  216.   //Read in the files
  217.   for ptr:=0 to 1 do
  218.   begin
  219.    input[ptr]:=TFileStream.Create(files[ptr],fmOpenRead OR fmShareDenyNone);
  220.    input[ptr].Position:=0;
  221.    SetLength(inbuffer[ptr],input[ptr].Size);
  222.    input[ptr].Read(inbuffer[ptr][0],input[ptr].Size);
  223.    input[ptr].Free;
  224.    //Get the position of the central library for each
  225.    CL[ptr]:=FindCL(EoCL[ptr],inbuffer[ptr]);
  226.    Result:=(Result)AND(CL[ptr]<>-1)AND(EoCL[ptr]<>-1);
  227.    //Count the number of files stored
  228.    if EoCL[ptr]<>-1 then
  229.     inc(numfiles,inbuffer[ptr][EoCL[ptr]+$A]+inbuffer[ptr][EoCL[ptr]+$B]<<8);
  230.   end;
  231.   //Create the output file
  232.   if Result then
  233.   begin
  234.    //This will be the eventual central library size
  235.    CLsize:=(EoCL[0]-CL[0])+(EoCL[1]-CL[1]);
  236.    //This will be the eventual file size
  237.    filesize:=CL[0]+CL[1]+CLsize+22;
  238.    SetLength(outbuffer,filesize);
  239.    //Write the files. The files from the second ZIP goes where the first CL was
  240.    fileptr:=0;
  241.    for cnt:=0 to 1 do
  242.    begin
  243.     for ptr:=0 to CL[cnt]-1 do outbuffer[fileptr+ptr]:=inbuffer[cnt][ptr];
  244.     inc(fileptr,CL[cnt]);
  245.    end;
  246.    //Write the CLs
  247.    for cnt:=0 to 1 do
  248.    begin
  249.     //We'll need to find each file entry and adjust by adding CL1 to the adddress
  250.     for ptr:=CL[cnt] to EoCL[cnt]-1 do
  251.     begin
  252.      outbuffer[fileptr-CL[cnt]+ptr]:=inbuffer[cnt][ptr];
  253.      if cnt>0 then
  254.       //Found a file?
  255.       if (inbuffer[cnt][ptr-$2E]=$50)
  256.       and(inbuffer[cnt][ptr-$2D]=$4B)
  257.       and(inbuffer[cnt][ptr-$2C]=$01)
  258.       and(inbuffer[cnt][ptr-$2B]=$02)then
  259.       begin
  260.        //Get the data offset
  261.        temp:=inbuffer[cnt][ptr-4]
  262.             +inbuffer[cnt][ptr-3]<<8
  263.             +inbuffer[cnt][ptr-2]<<16
  264.             +inbuffer[cnt][ptr-1]<<24;
  265.        //Adjust the data offset
  266.        inc(temp,CL[cnt-1]);
  267.        //Save back
  268.        outbuffer[(fileptr-CL[cnt]+ptr)-4]:= temp AND $000000FF;
  269.        outbuffer[(fileptr-CL[cnt]+ptr)-3]:=(temp AND $0000FF00)>>8;
  270.        outbuffer[(fileptr-CL[cnt]+ptr)-2]:=(temp AND $00FF0000)>>16;
  271.        outbuffer[(fileptr-CL[cnt]+ptr)-1]:=(temp AND $FF000000)>>24;
  272.       end;
  273.     end;
  274.     inc(fileptr,CL[cnt]);
  275.    end;
  276.    //Write the central directory
  277.    fileptr:=filesize-22;
  278.    outbuffer[fileptr    ]:=$50;
  279.    outbuffer[fileptr+$01]:=$4B;
  280.    outbuffer[fileptr+$02]:=$05;
  281.    outbuffer[fileptr+$03]:=$06;
  282.    outbuffer[fileptr+$08]:= numfiles AND $00FF;
  283.    outbuffer[fileptr+$09]:=(numfiles AND $FF00)>>8;
  284.    outbuffer[fileptr+$0A]:= numfiles AND $00FF;
  285.    outbuffer[fileptr+$0B]:=(numfiles AND $FF00)>>8;
  286.    outbuffer[fileptr+$0C]:= CLsize AND $000000FF;
  287.    outbuffer[fileptr+$0D]:=(CLsize AND $0000FF00)>>8;
  288.    outbuffer[fileptr+$0E]:=(CLsize AND $00FF0000)>>16;
  289.    outbuffer[fileptr+$0F]:=(CLsize AND $FF000000)>>24;
  290.    outbuffer[fileptr+$10]:= (CL[0]+CL[1]) AND $000000FF;
  291.    outbuffer[fileptr+$11]:=((CL[0]+CL[1]) AND $0000FF00)>>8;
  292.    outbuffer[fileptr+$12]:=((CL[0]+CL[1]) AND $00FF0000)>>16;
  293.    outbuffer[fileptr+$13]:=((CL[0]+CL[1]) AND $FF000000)>>24;
  294.    //Save the data to a file
  295.    output:=TFileStream.Create(outputfile,fmCreate OR fmShareDenyNone);
  296.    output.Position:=0;
  297.    output.Write(outbuffer[0],Length(outbuffer));
  298.    output.Free;
  299.   end;
  300.  end;
  301. end;
  302.  
  303. end.
Note that the ValidateZIPFile method can take a long time if there are lots of files in the archive.
Title: Re: TZipper - adding to existing archive
Post by: geraldholdsworth on June 06, 2025, 06:06:34 pm
Slight error in the code.
Line 171 (in the top post), or line 274 (in my last post) , which reads:
Code: Pascal  [Select][+][-]
  1. inc(fileptr,CL[cnt]);
works better if you change it to:
Code: Pascal  [Select][+][-]
  1. inc(fileptr,EoCL[cnt]-CL[cnt]);
I think...
Title: Re: TZipper - adding to existing archive
Post by: domasz on June 07, 2025, 12:53:51 pm
GNU General Public Licence
Thanks, but useless.
Title: Re: TZipper - adding to existing archive
Post by: jamie on June 07, 2025, 03:29:37 pm
I think I once did this by using an UnZip stream going to a Zip Stream to build the existing and then add to etc.

I may look that up to see where I did this.

Jamie
TinyPortal © 2005-2018