'===========================================================================
' Subject: PACK/UNPACK V1.0                   Date: 09-12-97 (12:55)       
'  Author: Sami Kyostila                      Code: QB, QBasic, PDS        
'  Origin: hiteck@mail.freenet.hut.fi       Packet: MISC.ABC
'===========================================================================
'----------------------------------------------------------------------------
'               Pack/UnPack v1.0 by Sami Ky”stil„ - 1997
'----------------------------------------------------------------------------
'        You may use this program freely, but give me some credit
'  I take no responsibility for any possible damage this program may cause.
'----------------------------------------------------------------------------
'
' Here is a useful program that archives files into a single file, so you
' don't have to mess with hundreds of data files. It can run in "NoPrint"-
' mode, so you can use it in your programs without the user noticing anything.
'
' A single archive can hold a maximum of 999 files, each one up to
' 999,999,999,999 bytes in size. See sub Pack for the archive structure
' description.
'
' Use FUNCTION Pack to add files to an archive/create new archive and FUNCTION
' UnPack to extract files from archives. Extracting each file everytime
' you need it in your programs can be very slow, so I've included FUNCTION
' PackOffset, which returns the start byte offset of a file in an archive,
' so you can read the file directly from the archive without having to
' extract it first.
'----------------------------------------------------------------------------



DECLARE FUNCTION FixFileName$ (File$, Extension$)
DEFINT A-Z
DECLARE FUNCTION PackList% (Archive$)
DECLARE FUNCTION PackOffset! (Archive$, Filename$, Length!)
DECLARE FUNCTION Pack (Archive$, Filename$, NoPrint)
DECLARE FUNCTION Unpack (Archive$, Filename$, NoPrint)
DECLARE FUNCTION Exist% (File$)



CLS
COLOR 8
PRINT STRING$(80, CHR$(196))
COLOR 10
PRINT "                   Pack/UnPack v1.0 by Sami Ky”stil„ - 1997"
COLOR 8
PRINT STRING$(80, CHR$(196))
PRINT "          You may use this program freely, but give me some credit"
COLOR 12
PRINT "  I take no responsibility for any possible damage this program may cause."
COLOR 8
PRINT STRING$(80, CHR$(196))
PRINT
COLOR 7
PRINT " Here is a useful program that archives files into a single file, so you"
PRINT " don't have to mess with hundreds of data files. It can run in NoPrint-"
PRINT " mode, so you can use it in your programs without the user noticing anything."
PRINT
PRINT " A single archive can hold a maximum of";
COLOR 14: PRINT 999; : COLOR 7
PRINT "files, each one up to";
COLOR 14: PRINT " 999,999,999,999 ";
COLOR 7
PRINT "bytes in size. See sub Pack for the archive structure"
PRINT " description."
PRINT
PRINT " Use ";
COLOR 14
PRINT "FUNCTION Pack";
COLOR 7
PRINT " to add files to an archive/create new archive and";
COLOR 14
PRINT " FUNCTION"
PRINT " UnPack ";
COLOR 7
PRINT "to extract files from archives. Extracting each file everytime"
PRINT " you need it in your programs can be very slow, so I've included ";
COLOR 14
PRINT "FUNCTION"
PRINT " PackOffset";
COLOR 7
PRINT ", which returns the start byte offset of a file in an archive,"
PRINT " so you can read the file directly from the archive without having to"
PRINT " extract it first."
PRINT
COLOR 9
PRINT "                                         Press any key to continue"
DO: LOOP UNTIL INKEY$ <> ""
CLS
COLOR 7
PRINT " Enter a temporary archive filename to be used in this demo:"
PRINT " If the file exists, it will be overwritten. The default extension"
PRINT " is .PAK"
PRINT
COLOR 14
INPUT "> ", Archive$
IF LTRIM$(RTRIM$(Archive$)) = "" THEN END
LOCATE 5, 3
PRINT FixFileName$(Archive$, "PAK")
COLOR 7

PRINT
PRINT " Enter an existing filename to be used in this demo:"
PRINT " The file can be a binary or a plain ASCII one."
PRINT
PRINT
COLOR 14
INPUT "> ", File1$
IF LTRIM$(RTRIM$(File1$)) = "" THEN END
COLOR 7

PRINT
PRINT " Enter a second existing filename to be used in this demo:"
PRINT " The file can be a binary or a plain ASCII one."
PRINT
COLOR 14
INPUT "> ", File2$
IF LTRIM$(RTRIM$(File2$)) = "" THEN END
COLOR 7



ON ERROR GOTO OhGreat

CONST True = 1
CONST False = NOT True

TYPE PakHeader
  Version AS STRING * 5             'Version
  Total AS STRING * 3               'Total files in archive
END TYPE

DIM SHARED CurrentVersion AS STRING * 5
CurrentVersion = "Pak10"            'Current version signature


IF Exist(Archive$) = True THEN KILL Archive$

ErrorFlag = Pack(Archive$, File1$, 0)               'Add file 1 to archive
IF ErrorFlag <> 0 THEN PRINT " Error"; ErrorFlag: END

ErrorFlag = Pack(Archive$, File2$, 0)               'Add file 2 to archive
IF ErrorFlag <> 0 THEN PRINT " Error"; ErrorFlag: END

ErrorFlag = PackList(Archive$)                      'View contents
IF ErrorFlag <> 0 THEN PRINT " Error"; ErrorFlag: END
SLEEP

ErrorFlag = Unpack(Archive$, "*", 0)                'Extract both files
IF ErrorFlag <> 0 THEN PRINT " Error"; ErrorFlag: END

IF Exist(Archive$) = True THEN KILL Archive$


END


OhGreat:
CLS
PRINT " - Error number";
COLOR 14
PRINT ERR;
COLOR 7
PRINT "has occured."
PRINT "   Program execution";
COLOR 14
PRINT " aborted."
END

FUNCTION Exist% (File$)
'----------------------------------------------------------------------------
'                  Checks if a file exists - Sami Ky”stil„ 1997
'----------------------------------------------------------------------------
'
'    File$ - File to check
'
'----------------------------------------------------------------------------
'  Returns True if file exists, else False
'----------------------------------------------------------------------------


IF File$ = "" THEN Exist = False: EXIT FUNCTION
Checkfile = FREEFILE
OPEN File$ FOR BINARY AS #Checkfile
IF LOF(Checkfile) = 0 THEN
  Exist = False
  CLOSE #Checkfile
  KILL File$
ELSE
  Exist = True
  CLOSE #Checkfile
END IF

END FUNCTION

FUNCTION FixFileName$ (File$, Extension$)
'----------------------------------------------------------------------------
'                  Fixes the filename extension
'----------------------------------------------------------------------------
'
'      File$ - Filename
' Extension$ - Extension
'
'----------------------------------------------------------------------------
'
' If File$'s extension is different than Extension$, then it will be changed
'
' Example:
'
'   File$ = "c:\temp\temp.abc"
'   Extension$ = "exe"
'   Returns: "c:\temp\temp.exe"
'
'----------------------------------------------------------------------------



IF INSTR(File$, ".") > 0 AND INSTR(File$, ".") < LEN(File$) THEN
  IF RIGHT$(UCASE$(File$), LEN(Extension$)) <> UCASE$(Extension$) THEN
    File$ = LEFT$(File$, INSTR(File$, ".")) + Extension$
  END IF
ELSEIF INSTR(File$, ".") = LEN(File$) THEN
  File$ = File$ + Extension$
ELSE
  File$ = File$ + "." + Extension$
END IF

FixFileName$ = File$

END FUNCTION

FUNCTION Pack (Archive$, Filename$, NoPrint)
'----------------------------------------------------------------------------
'   Packs files into an archive, binary or plain text - Sami Ky”stil„ 1997
'----------------------------------------------------------------------------
'
'  Archive$ - Filename of the archive, if the archive exists, then it will
'             be appended. (Max number of files in an archive is 999)
' Filename$ - File to be added to archive. May include a path, but it will
'             be stored without the path. (Max file size 999,999,999,999
'             bytes, should be enough :)
'   NoPrint - If 1, then no text will be printed
'
'----------------------------------------------------------------------------
'                        Returned error codes
'----------------------------------------------------------------------------
'
' 1 = Input file not found
' 2 = Input file is already in archive
'
'----------------------------------------------------------------------------
'                           Archive structure
'----------------------------------------------------------------------------
'
' Header:
'
'   Version (5 bytes)                    - "Pak10"
'   Number of files in archive (3 bytes) - "001"
'
' Data:
'
'   Filename (12 bytes)                  - "MYFILE.EXE  "
'   File size (12 bytes)                 - "000000001234"
'   ...
'   Data area (length indicated above)
'   ...
'
' Next file data (header not repeated)
'
'----------------------------------------------------------------------------


DIM Buffer AS STRING * 1000
BufferSize = LEN(Buffer)

DIM byte AS STRING * 1
DIM Pak AS PakHeader


IF NoPrint = 0 THEN
  CLS
  COLOR 8
  PRINT STRING$(80, CHR$(196))
  COLOR 7
  PRINT "                 Pack/UnPack Freeware by ";
  COLOR 15
  PRINT "Sami Ky”stil„ ";
  COLOR 7
  PRINT "- 1997"
  PRINT "                        Version signature: ";
  COLOR 14
  PRINT CurrentVersion
  COLOR 4
  PRINT "             Use this program as you wish, but give me some credit."
  COLOR 8
  PRINT STRING$(80, CHR$(196))
  COLOR 7
  PRINT
END IF

IF Exist(Filename$) = False THEN Pack = 1: EXIT FUNCTION
IF Exist(Archive$) = False THEN
  new = 1
  f1 = FREEFILE
  OPEN Archive$ FOR BINARY AS #f1
  PUT #f1, 1, CurrentVersion
  Dummy$ = "000"
  PUT #f1, , Dummy$
  CLOSE #f1
  new = 1
ELSE
  new = 0
END IF


f1 = FREEFILE
OPEN Archive$ FOR BINARY AS #f1
f2 = FREEFILE
OPEN Filename$ FOR BINARY AS #f2
Row = CSRLIN + 1

FOR i = LEN(Filename$) TO 1 STEP -1
  IF MID$(Filename$, i, 1) = "\" THEN Filename$ = MID$(Filename$, i + 1, 255)
NEXT
Filename$ = UCASE$(Filename$)


  IF NoPrint = 0 THEN
    IF new = 0 THEN PRINT " "; CHR$(254); " Appending to archive "; Archive$; " at position"; : COLOR 14: PRINT ; LOF(f1): COLOR 7
    IF new = 1 THEN PRINT " "; CHR$(254); " Creating archive "; Archive$: COLOR 7
  END IF

  IF PackOffset(Archive$, Filename$, -1) <> -1 THEN
    IF NoPrint = 0 THEN PRINT " "; CHR$(254); " "; Filename$; " is already in archive "; Archive$
    Pack = 2
    CLOSE #f1
    CLOSE #f2
    EXIT FUNCTION
  END IF

  GET #f1, 1, Pak
  TotalFiles = VAL(Pak.Total)
  TotalFiles = TotalFiles + 1
  Total$ = STRING$(3 - LEN(LTRIM$(RTRIM$(STR$(TotalFiles)))), "0") + LTRIM$(RTRIM$(STR$(TotalFiles)))

  IF NoPrint = 0 THEN PRINT "   Adding "; Filename$; "...";

  PUT #f1, 6, Total$

  Length$ = STRING$(12 - LEN(LTRIM$(RTRIM$(STR$(LOF(f2))))), "0") + LTRIM$(RTRIM$(STR$(LOF(f2))))
  File2$ = Filename$ + STRING$(12 - LEN(Filename$), " ")

  CLOSE f1
  OPEN Archive$ FOR BINARY AS #f1

  SEEK #f1, LOF(f1)

  PUT #f1, SEEK(f1) + 1, File2$
  PUT #f1, , Length$
  PPos = POS(0)
  COLOR 14

  FOR i! = 1 TO LOF(f2) STEP BufferSize
    GET #f2, , Buffer
    Clip! = LOF(f2) + 1 - i!
    IF Clip! <= BufferSize THEN
      Buffer2$ = LEFT$(Buffer, LOF(f2) + 1 - i!)
      PUT #f1, , Buffer2$
    ELSE
      PUT #f1, , Buffer
    END IF
    IF NoPrint = 0 THEN
      IF i! \ BufferSize MOD 50 = 0 THEN LOCATE Row, PPos: PRINT LTRIM$(RTRIM$(STR$(INT((i! / LOF(f2)) * 100)))); "%"
    END IF
  NEXT


  IF NoPrint = 0 THEN LOCATE Row, PPos: COLOR 14: PRINT LTRIM$(STR$(LOF(f2))); : COLOR 7: PRINT " bytes"
  CLOSE #f1
  CLOSE #f2

END FUNCTION

FUNCTION PackList (Archive$)
'----------------------------------------------------------------------------
'              List contents of an archive - Sami Ky”stil„ 1997
'----------------------------------------------------------------------------
'
' Archive$ - The archive to be scanned
'
'----------------------------------------------------------------------------
'                        Returned error codes
'----------------------------------------------------------------------------
'
' 1 - Archive not found
'
'----------------------------------------------------------------------------

DIM Pak AS PakHeader


CLS
COLOR 8
PRINT STRING$(80, CHR$(196))
COLOR 7
PRINT "                 Pack/UnPack Freeware by ";
COLOR 15
PRINT "Sami Ky”stil„ ";
COLOR 7
PRINT "- 1997"
PRINT "                        Version signature: ";
COLOR 14
PRINT CurrentVersion
COLOR 4
PRINT "             Use this program as you wish, but give me some credit."
COLOR 8
PRINT STRING$(80, CHR$(196))
COLOR 7
PRINT

IF Exist(Archive$) = False THEN PackList = 1: EXIT FUNCTION
f1 = FREEFILE
OPEN Archive$ FOR BINARY AS #f1
DIM byte AS STRING * 1


GET #f1, 1, Pak

DIM File AS STRING * 12
DIM FileLen AS STRING * 12


offset! = 9
CFile = 1

IF NoPrint = 0 THEN
  PRINT " - Archive version signature: ",
  COLOR 14
  PRINT Pak.Version
  COLOR 7
  PRINT " - Total files in archive: ", ,
  COLOR 14
  PRINT VAL(Pak.Total)
  COLOR 7
  PRINT " - Archive size:",
  COLOR 14
  PRINT USING " ###,###,###,###,###"; LOF(f1);
  PRINT " bytes"
  COLOR 7
  PRINT " "; CHR$(254); " Contents of archive "; Archive$
END IF

COLOR 9
PRINT "   File          Length       Start         End"
COLOR 7

DO
  GET #f1, , File
  GET #f1, , FileLen
  offset! = offset! + 24

  PRINT "   "; File; CHR$(179); VAL(FileLen), ; CHR$(179); SEEK(f1), ; CHR$(179); SEEK(f1) + VAL(FileLen)

  CFile = CFile + 1
  offset! = offset! + VAL(FileLen)
  SEEK #f1, offset!
  IF CFile > VAL(Pak.Total) THEN EXIT DO
LOOP
CLOSE #f1

END FUNCTION

FUNCTION PackOffset! (Archive$, Filename$, Length!)
'----------------------------------------------------------------------------
' Returns the offset of a file in an archive created by Pack - Sami Ky”stil„
' Use this to find out where a file starts in an archive, so you can read it
' without extracting it first.
'----------------------------------------------------------------------------
'
'  Archive$ - Filename of the archive
' Filename$ - Filename to search for
'    Length - Returns the length of the file
'
'----------------------------------------------------------------------------
'                           Returned error codes
'----------------------------------------------------------------------------
'
' If returns -1, then the file has not been found in the archive
'
'----------------------------------------------------------------------------

Filename$ = UCASE$(LTRIM$(RTRIM$(Filename$)))

IF Exist(Archive$) = False THEN EXIT FUNCTION
DIM Pak AS PakHeader

f1 = FREEFILE
OPEN Archive$ FOR BINARY AS #f1

DIM byte AS STRING * 1
DIM File AS STRING * 12
DIM FileLen AS STRING * 12

GET #f1, , Pak

offset! = 9
CFile = 1

DO
  GET #f1, , File
  GET #f1, , FileLen
  offset! = offset! + 24

  IF Filename$ = RTRIM$(File) THEN EXIT DO
  CFile = CFile + 1
  offset! = offset! + VAL(FileLen)
  SEEK #f1, offset!
  IF CFile > VAL(Pak.Total) THEN offset! = -1: EXIT DO
LOOP

CLOSE #f1

PackOffset! = offset!
Length! = VAL(FileLen)

END FUNCTION

FUNCTION Unpack (Archive$, Filename$, NoPrint)
'----------------------------------------------------------------------------
'   UnPacks files from an archive created by Pack - Sami Ky”stil„ 1997
'----------------------------------------------------------------------------
'
'  Archive$ - Filename of the archive
' Filename$ - File to extracted from the archive. If includes a "*", then
'             all files are extracted
'   NoPrint - If 1, then no text will be printed and if a file exists, the
'             process will be aborted automatically.
'
'----------------------------------------------------------------------------
'                        Returned error codes
'----------------------------------------------------------------------------
'
' 1 = File exists and NoPrint-mode on
' 2 = User abort
' 3 = Archive not found
'
'----------------------------------------------------------------------------

DIM Buffer AS STRING * 1000
BufferSize = LEN(Buffer)


IF NoPrint = 0 THEN
  CLS
  COLOR 8
  PRINT STRING$(80, CHR$(196))
  COLOR 7
  PRINT "                 Pack/UnPack Freeware by ";
  COLOR 15
  PRINT "Sami Ky”stil„ ";
  COLOR 7
  PRINT "- 1997"
  PRINT "                        Version signature: ";
  COLOR 14
  PRINT CurrentVersion
  COLOR 4
  PRINT "             Use this program as you wish, but give me some credit."
  COLOR 8
  PRINT STRING$(80, CHR$(196))
  COLOR 7
  PRINT
END IF

IF Exist(Archive$) = False THEN Unpack = 3: EXIT FUNCTION
f1 = FREEFILE
OPEN Archive$ FOR BINARY AS #f1
DIM byte AS STRING * 1
GlobalYes = 0


IF INSTR(Filename$, "*") > 0 THEN All = 1 ELSE All = 0

DIM Pak AS PakHeader


GET #f1, 1, Pak

DIM File AS STRING * 12
DIM FileLen AS STRING * 12


offset! = 9
CFile = 1

IF NoPrint = 0 THEN
  PRINT " - Archive version signature: ",
  COLOR 14
  PRINT Pak.Version
  COLOR 7
  PRINT " - Total files in archive: ", ,
  COLOR 14
  PRINT VAL(Pak.Total)
  COLOR 7
  PRINT " - Archive size:",
  COLOR 14
  PRINT USING " ###,###,###,###,###"; LOF(f1);
  PRINT " bytes"
  COLOR 7
  PRINT " "; CHR$(254); " Unpacking archive "; Archive$
END IF


IF All = 1 THEN
  DO
    GET #f1, , File
    GET #f1, , FileLen
    offset! = offset! + 24

    IF Exist(RTRIM$(File)) = True THEN
      IF NoPrint = 1 THEN CLOSE #f1: CLOSE #f2: Unpack = 1: EXIT FUNCTION
      COLOR 4
      PRINT "     Overwrite "; RTRIM$(File); "? [Yes/No/All/Quit] ";
      COLOR 12
      IF GlobalYes = 1 THEN PRINT "Yes": GOTO ProcessFile
Again:
      k$ = UCASE$(INPUT$(1))
      IF k$ <> "Y" AND k$ <> "N" AND k$ <> "Q" AND k$ <> "A" THEN GOTO Again
      IF k$ = "Q" THEN PRINT "Quit": CLOSE #f1: CLOSE #f2: Unpack = 2: EXIT FUNCTION
      IF k$ = "A" THEN PRINT "Yes": GlobalYes = 1: KILL RTRIM$(File)
      IF k$ = "Y" THEN PRINT "Yes": KILL RTRIM$(File)
      IF k$ = "N" THEN PRINT "No": GOTO NextFile
    END IF

ProcessFile:
    Row = CSRLIN
    f2 = FREEFILE
    OPEN RTRIM$(File) FOR BINARY AS #f2
    
    IF NoPrint = 0 THEN
      LOCATE Row, 1
      COLOR 7
      PRINT "   "; CHR$(250); " Extracting file "; RTRIM$(File); "...";
    END IF

    PPos = POS(0)
    COLOR 14

    FOR i! = 1 TO VAL(FileLen) STEP BufferSize
      GET #f1, , Buffer
      Clip! = VAL(FileLen) + 1 - i!
      IF Clip! <= BufferSize THEN
        Buffer2$ = LEFT$(Buffer, VAL(FileLen) + 1 - i!)
        PUT #f2, , Buffer2$
      ELSE
        PUT #f2, , Buffer
      END IF
      IF NoPrint = 0 THEN
        IF i! \ BufferSize MOD 50 = 0 THEN LOCATE Row, PPos: PRINT LTRIM$(RTRIM$(STR$(INT((i! / VAL(FileLen)) * 100)))); "%"
      END IF
    NEXT

    CLOSE f2

    IF NoPrint = 0 THEN
      COLOR 14
      LOCATE Row, PPos
      PRINT VAL(FileLen);
      COLOR 7
      PRINT "bytes"
    END IF

NextFile:
    CFile = CFile + 1
    offset! = offset! + VAL(FileLen)
    SEEK #f1, offset!
    IF CFile > VAL(Pak.Total) THEN EXIT DO
  LOOP
ELSE
  File = UCASE$(Filename$)
  IF Exist(RTRIM$(File)) = True THEN
    IF NoPrint = 1 THEN CLOSE #f1: Unpack = 1: EXIT FUNCTION
    COLOR 4
    PRINT "     Overwrite "; RTRIM$(File); "? [Yes/No/Quit] ";
    COLOR 12
Again2:
    k$ = UCASE$(INPUT$(1))
    IF k$ <> "Y" AND k$ <> "N" AND k$ <> "Q" THEN GOTO Again2
    IF k$ = "Q" THEN PRINT "Quit": CLOSE #f1: CLOSE #f2: Unpack = 2: EXIT FUNCTION
    IF k$ = "Y" THEN PRINT "Yes": KILL RTRIM$(File)
    IF k$ = "N" THEN PRINT "No": CLOSE #f1: CLOSE #f2: Unpack = 2: EXIT FUNCTION
  END IF

  Row = CSRLIN
  f2 = FREEFILE
  OPEN RTRIM$(File) FOR BINARY AS #f2
  SEEK #f1, PackOffset(Archive$, Filename$, Length!)
  IF NoPrint = 0 THEN
    LOCATE Row, 1
    COLOR 7
    PRINT "   "; CHR$(250); " Extracting file "; RTRIM$(File); "...";
  END IF
   
  PPos = POS(0)
  COLOR 14


  FOR i! = 1 TO Length! STEP BufferSize
    GET #f1, , Buffer
    Clip! = Length! + 1 - i!
    IF Clip! <= BufferSize THEN
      Buffer2$ = LEFT$(Buffer, Length! + 1 - i!)
      PUT #f2, , Buffer2$
    ELSE
      PUT #f2, , Buffer
    END IF
    IF NoPrint = 0 THEN
      IF i! \ BufferSize MOD 50 = 0 THEN LOCATE Row, PPos: PRINT LTRIM$(RTRIM$(STR$(INT((i! / Length!) * 100)))); "%"
    END IF
  NEXT

  CLOSE f2

  IF NoPrint = 0 THEN
    COLOR 14
    LOCATE Row, PPos
    PRINT Length!;
    COLOR 7
    PRINT "bytes"
  END IF

END IF


CLOSE #f1

END FUNCTION
