Metropoli BBS
VIEWER: unpack.pas MODE: TEXT (CP437)
{ ╞═══════════════════════════════════════════════════════════╡ }
{ │  ZiPPAS 4.1 -- PKZIP 2.x Unpacking Objects for TP/BP 7.0  │ }
{ ╞═══════════════════════════════════════════════════════════╡ }
{ │       Copyright (c) 1993-95 by Solar Designer \ BPC       │ }
{ ╞═══════════════════════════════════════════════════════════╡ }

{$IfNDef VER70}
 !!! WARNING !!!
 !!! ' This unit should be compiled with TP/BP version 7.0  ' !!!
 !!! ' or 7.01. It is incompatible with older versions, and ' !!!
 !!! ' may not be compatible with some future ones.         ' !!!
{$EndIf}

unit Unpack;
interface
uses
   DOS, Objects, Memory;

{$L unzipmz.obj} { Modified PKUNZJR.COM }
{$L unzipmi.obj} { INT 21h handler }

   type
{ This record is returned by TUnpack.GetInfo }
      TPackedInfo =
      record
         ZipId     :LongInt;
         Unknown1  :Array [1..4] of Byte;
         Files     :Word;
         Unknown2  :Array [1..12] of Byte;
         Ok        :Boolean;
      end;

{ TUnpack -- abstract object }
      PUnpack =    ^TUnpack;
      TUnpack =
      object(TObject)

         FileName  :PString; { The assigned file name }
         Status    :Byte;    { One of the uzXXXX constants -- check after unpacking }

{ These fields can be modified directly }
         Messages  :Boolean; { Enable PKUNZJR messages }
         NeededName:PString; { Unpack only this file }
         Start     :LongInt; { Start unpacking from this offset in ZIP file }
         ZipId     :LongInt; { ZIP signature (default is 'PK'#5#6) }

         constructor Init(const AFileName:FNameStr);

         destructor  Done;   virtual;

{ Initialize the unpacker for loading ZIP from the overlay of the EXE file }
         procedure   InitOverlay;

{ Unpack all files in archive }
         procedure   Unpack;

{ Unpack only the specified file }
         procedure   Unpack1(const Name:String);

{ Get some archive information }
         procedure   GetInfo(var Info  :TPackedInfo);

{ Abstract method to store unpacked data -- should be overriden }
         procedure   Write(var Buf;
                           Count       :Word);   virtual;

{ Virtual method called when starting to unpack a new file }
{ The default method does nothing }
         procedure   New  (var Name    :String); virtual;

{ Virtual method to check if a given file should be unpacked or skipped }
{ The default method checks the NeededName field }
         function    Query(var Name    :String)  :Boolean; virtual;

      private

         procedure   WriteFn;
         procedure   NewFn;
         procedure   QueryFn;

      end;

{ TUnpackStream -- used for unpacking to any Turbo Vision stream }
      PUnpackStream = ^TUnpackStream;
      TUnpackStream =
      object(TUnpack)

         Stream    :PStream;

         constructor Init(const AFileName:FNameStr;
                          var AStream  :TStream);

{ Overriden abstract method -- writes unpacked data to the stream }
         procedure   Write(var Buf;
                           Count       :Word);   virtual;

      end;

{ This record is used internally }
      PUItem=      ^TUItem;
      TUItem=
      record
         Next      :PUItem;
         Pos       :LongInt;
         Name      :NameStr;
      end;

{ TUnpackList -- provides a convenient way to access packed files by name }
      PUnpackList =^TUnpackList;
      TUnpackList =
      object(TUnpackStream)

         List      :PUItem;
         Pos       :NameStr; { Current stream position (file name) }

{ Seek the assigned stream to the start of packed file 'Name' }
{ The name must be in upper case! }
         procedure   Seek (const Name  :NameStr);

{ Overriden virtual method -- stores the name to list }
         procedure   New  (var Name    :String); virtual;

         destructor  Done; virtual;
      end;

   const
{ Error codes for TUnpack.Status }
      uzOK=        $00;
      uzIOError=   $02;
      uzInvFormat= $03;
      uzCRCError=  $04;
      uzNoMem=     $80;
      uzCritError= $81;

   const
{ Amount of memory allocated for PKUNZJR's stack }
      UnpackMemSize                    :Word =   $C400 + $50;

implementation

   type
      TPSPRec=
      record
         Terminate,
         MemSize             :Word;
         Extra1,
         FarCall             :Byte;
         Dispatcher,
         ExitAddr,
         CtrlBreak,
         CritError           :Pointer;
         Extra2              :Array [1..22] of Byte;
         EnvironSeg          :Word;
         Extra3              :Array [1..46] of Byte;
         FCB                 :Array [1..2, 1..16] of Byte;
         Extra4              :Array [1..4] of Byte;
         ComLine             :String[127];
      end;

   var
      WriteUnpacked, WriteNew,
      QueryUnpack, SelfPtr             :Pointer;
      ZipMessages                      :Boolean;
      ReadStart                        :LongInt;

   procedure UnZipMemProc; far; External;
   procedure UnZipIntProc; far; External;

   procedure ProgramDS; External;

   const
      UZIntNum=    $81;
      UZProcSize=  2750;
      UZSignOfs1=  $966;
      UZSignOfs2=  $D7;
      MaxWord=     $FFFF;

      pUZMemProc   :Pointer=           @UnZipMemProc;

   procedure ExitUnZip; far; assembler;
   asm
      mov  ax,4C00h + uzCritError
      int  UZIntNum
   end;

   procedure SaveAddr; assembler;
   asm
      dd   0       { proc addr }
      dd   0       { ss/sp }
      dw   0       { bp }
   end;

{ TUnpack }

   procedure TUnpack.WriteFn;
   assembler;
   asm
      push si
      push dx
      push cx

      les  di,Self
      push es
      push di

      mov  di,es:[di]
      call dword ptr [di+(vmtHeaderSize+4)]      { Write }
   end;

   procedure MakeString;
   assembler;
   asm
      push ds
      mov  ds,si
      mov  si,dx
      push ss
      pop  es
      cld

      push di
      inc  di
      xor  cx,cx
@@1:
      lodsb
      or   al,al
      jz   @@2
      stosb
      inc  cx
      jmp  @@1
@@2:
      pop  di
      mov  es:[di],cl

      pop  ds
   end;

   procedure RemoveExt(var Name        :String);
   var
      i            :Integer;
   begin
      i:=1;
      while (i<=Byte(Name[0])) and (Name[i]<>'.') do Inc(i);
      if Name[i]='.' then Name[0]:=Char(i-1);
   end;

   procedure TUnpack.NewFn;
   assembler;
   var
      NamePas      :FNameStr;
   asm
      mov  di,bp
      sub  di,80
      call MakeString

      push ss
      mov  di,bp
      sub  di,80
      push di

      les  di,Self
      push es
      push di

      mov  di,es:[di]
      call dword ptr [di+(vmtHeaderSize+8)]      { New }
   end;

   procedure TUnpack.QueryFn;
   assembler;
   var
      NamePas      :FNameStr;
   asm
      mov  di,bp
      sub  di,80
      call MakeString

      push ss
      mov  di,bp
      sub  di,80
      push di

      les  di,Self
      push es
      push di

      mov  di,es:[di]
      call dword ptr [di+(vmtHeaderSize+12)]     { Query }
   end;

   constructor TUnpack.Init;
   begin
      Inherited Init;
      FileName:=NewStr(AFileName);
      asm
         les  di,Self
         mov  word ptr es:[di].TUnpack.ZipId,'KP'
         mov  word ptr es:[di].TUnpack.ZipId+2,0605h
      end;
   end;

   procedure TUnpack.InitOverlay;
   var
      Header       :record
         ExeId     :Array [0..1] of Char;
         PartPage,
         PageCnt   :Word;
      end;
      S            :TDosStream;
   begin
      DisposeStr(FileName);
      FileName:=NewStr(ParamStr(0));

      S.Init(FileName^, stOpenRead);
      S.Read(Header, SizeOf(Header));
      S.Done;

      Start:=LongMul(Header.PageCnt, 512);
      if Header.PartPage<>0 then Dec(Start, 512 - Header.PartPage);
   end;

   destructor TUnpack.Done;
   begin
      DisposeStr(FileName);
   end;

   procedure TUnpack.Write;
   begin
      Abstract;
   end;

   procedure TUnpack.New;
   begin
   end;

   function TUnpack.Query;
   begin
      if NeededName=nil then Query:=True else
      begin
         RemoveExt(Name);
         Query:=(Name=NeededName^);
      end;
   end;

   procedure TUnpack.Unpack;
   var
      SaveUZInt    :Pointer;
      pUnZip       :Pointer;
      UZProcPTR    :^TPSPRec;
      ZipExitCode  :Byte;
   begin
      UZProcPtr:=MemAllocSeg(UnpackMemSize);
      if UZProcPtr=nil then
      begin
         Status:=uzNoMem; Exit;
      end;
      FillChar(UZProcPtr^, UnpackMemSize, 0);
      pUnZip:=PTR(PtrRec(UZProcPtr).Seg, SizeOf(TPSPRec));

      Move(pUZMemProc^, pUnZip^, UZProcSize);

      asm
         les  di,Self
         mov  ax,word ptr es:[di].TUnpack.ZipId
         mov  dx,word ptr es:[di].TUnpack.ZipId+2
         les  di,pUnZip
         mov  word ptr es:[di+UZSignOfs1],ax
         mov  word ptr es:[di+UZSignOfs2],dx
      end;

      GetIntVec(UZIntNum, SaveUZInt);
      SetIntVec(UZIntNum, Addr(UnZipIntProc));

      asm
         mov  ah,62h
         int  21h
         les  di,UZProcPtr
         push ds
         mov  ds,bx
         xor  si,si
         mov  cx,40h
         cld
     rep movsw
         pop  ds
      end;

      with UZProcPtr^ do
      begin
         ExitAddr:=@ExitUnZip; CtrlBreak:=@ExitUnZip; CritError:=@ExitUnZip;
         ComLine:=FileName^+#13#10; Dec(ComLine[0], 2);
      end;

      asm
         mov  word ptr ProgramDS,ds

         les  di,Self
         mov  word ptr SelfPtr,di
         mov  word ptr SelfPtr+2,es
      end;

      ZipMessages:=Messages;
      WriteUnpacked:=@TUnpack.WriteFn; WriteNew:=@TUnpack.NewFn;
      QueryUnpack:=@TUnpack.QueryFn;

      ReadStart:=Start;

      asm
         push ds

         mov  ax,word ptr pUnZip
         mov  word ptr SaveAddr,ax
         mov  ax,word ptr pUnZip+2
         mov  word ptr SaveAddr+2,ax

         mov  word ptr SaveAddr+4,ss
         mov  word ptr SaveAddr+6,sp
         mov  word ptr SaveAddr+8,bp

         mov  ax,word ptr UZProcPtr+2

         cli
         mov  ss,ax
         mov  dx,UnpackMemSize
         dec  dx
         mov  sp,dx
         sti

         mov  es,ax
         mov  ds,ax

         call dword ptr cs:SaveAddr

         cli
         mov  ss,word ptr SaveAddr+4
         mov  sp,word ptr SaveAddr+6
         sti
         mov  bp,word ptr SaveAddr+8

         pop  ds

         mov  ZipExitCode, al
      end;
      if ZipExitCode=1 then ZipExitCode:=0; { Ignore the "Not all files" error }
      Status:=ZipExitCode;

      SetIntVec(UZIntNum, SaveUZInt);

      FreeMem(UZProcPTR, UnpackMemSize);
   end;

   procedure TUnpack.Unpack1;
   begin
      NeededName:=@Name;
      Unpack;
      NeededName:=nil;
   end;

   procedure TUnpack.GetInfo;
   var
      S            :TDosStream;
   begin
      S.Init(FileName^, stOpenRead);
      S.Seek(S.GetSize - (SizeOf(TPackedInfo)-1));
      S.Read(Info, SizeOf(TPackedInfo)-1);
      S.Done;
      Info.Ok:=(S.Status=stOk) and (Info.ZipId=Self.ZipId);
   end;

{ TUnpackStream }

   constructor TUnpackStream.Init;
   begin
      Inherited Init(AFileName);
      Stream:=@AStream;
   end;

   procedure TUnpackStream.Write;
   begin
      Stream^.Write(Buf, Count);
   end;

{ TUnpackList }

   procedure TUnpackList.New;
   var
      Last         :PUItem;
   begin
      RemoveExt(Name);

      Last:=List;
      System.New(List);
      List^.Next:=Last;
      List^.Pos:=Stream^.GetPos;
      List^.Name:=Name;
   end;

   destructor TUnpackList.Done;
   var
      Last         :PUItem;
   begin
      while List<>nil do
      begin
         Last:=List; List:=Last^.Next; Dispose(Last);
      end;

      Inherited Done;
   end;

   procedure TUnpackList.Seek;
   var
      Current      :PUItem;
   begin
      Current:=List;
      while (Current<>nil) and (Current^.Name<>Name) do
         Current:=Current^.Next;
      if Current<>nil then
      begin
         Stream^.Seek(Current^.Pos);
         Pos:=Name;
      end
      else Stream^.Seek(0);
   end;

end.
[ RETURN TO DIRECTORY ]