1. -- ************************************************************************
  2. -- Script:   ZIP_CLASS
  3. -- Version:  0.6
  4. -- Date:     2009-08-08
  5. -- Author:   Valentin Schmidt
  6. --
  7. -- implements ZIP compression according to PKWARE's ZIP File Format Specification (http://www.pkware.com/documents/casestudies/APPNOTE.TXT)
  8. -- data is e.g. compatible with WinZip, WinRAR and many other tools.
  9. --
  10. -- Notice: unfortunately the current implementation of the compress/uncompress functions of director's bytearray class use
  11. -- ZLIB (i.e. gzcompress) instead of raw DEFLATE compression. this is no limitation for compressing data (as
  12. -- DEFLATE/GZIP/ZIP files) since the 6 additional ZLIB bytes (2 bytes at the beginning, 4 adler32 checksum bytes at the end)
  13. -- just need to be removed to create valid DEFLATE compressed data (that can then be used to create GZIP/ZIP files). but
  14. -- unfortunately the bytearray's uncompress() function fails if the 4 adler32 checksum bytes at the end of the bytearray are
  15. -- missing or incorrect, and therefor INFLATE, GZIP and ZIP decompression is only possible if the adler32 checksums of the
  16. -- original file(s) are known.
  17. -- one way to lessen this strong limitation a little bit, so at least GZIP and ZIP files created with those scripts can be
  18. -- decompressed without requiring any additional info, is to store the adler32 checksums as comments inside the GZP/ZIP file.
  19. -- such files can be decompresssed with any 3rd party tool (which just ignore the comments) as well as with code based on
  20. -- byterrays's uncompress function.
  21. --
  22. -- Requirements:
  23. -- * fileIO xtra
  24. -- * fileIO wrapper functions file_get_bytes()/file_put_bytes()
  25. -- * str_replace() function
  26. -- * crc32_bytearray() function (or alternatively an xtra for faster CRC32 calculation, e.g. crypto xtra)
  27. --
  28. -- ************************************************************************
  29.  
  30. -- PACK
  31. property pDataSec
  32. property pCtrlDir
  33. property pOffset
  34.  
  35. -- UNPACK
  36. property pZipFile
  37. property pFileList
  38.  
  39. ----------------------------------------
  40. --
  41. ----------------------------------------
  42. on new me
  43.  
  44.   me.pDataSec = []
  45.   me.pCtrlDir = []
  46.   me.pOffset = 0
  47.  
  48.   me.pFileList = []
  49.  
  50.   return me
  51. end
  52.  
  53. --**************************************
  54. -- PUBLIC
  55. --**************************************
  56.  
  57. ----------------------------------------
  58. --
  59. ----------------------------------------
  60. on mAddFile (me, tFile, tFilename, tComment, tDate)
  61.   if voidP(tFilename) then tFilename=tFile
  62.   baData = file_get_bytes(tFile)  
  63.   me.mAddFileData(baData, tFilename, tComment, tDate)
  64. end
  65.  
  66. ----------------------------------------
  67. -- NOTICE:
  68. -- passed bytearray tData is changed (bytearray is compressed)!!!
  69. -- if this is not wanted, either create a copy or use tData.uncompress() to restore original state
  70. ----------------------------------------
  71. on mAddFileData (me, tData, tFilename, tComment, tDate)
  72.   if the platform contains "win" then
  73.     tFilename = str_replace("\", "/", tFilename)
  74.   else
  75.     tFilename = str_replace(":", "/", tFilename)
  76.   end if
  77.  
  78.   -- "local file header" segment
  79.   unc_len = tData.length
  80.   crc32   = crc32_bytearray(tData)
  81.   tData.compress()
  82.  
  83.   if tComment=#adler32 then
  84.     tData.position = tData.length-3
  85.     tData.endian=#bigEndian
  86.     tComment = string(tData.readInt32())
  87.   else
  88.     tComment = string(tComment)
  89.   end if
  90.  
  91.   c_len   = tData.length + 6  -- fix
  92.  
  93.   if voidP(tDate) then tDate = the systemdate
  94.   dosTime = dateToDosTime(tDate)
  95.  
  96.   fr = bytearray()    
  97.   fr.writeInt32(80 + 75*256 + 3*65536 + 4*16777216) -- \x50\x4b\x03\x04
  98.   fr.writeInt16(20)                 -- \x14\x00 = ver needed to extract
  99.   fr.writeInt16(0)                  -- \x00\x00 = gen purpose bit flag  
  100.   fr.writeInt16(8)                  -- \x08\x00 = compression method  
  101.   fr.writeInt32(dosTime)
  102.   fr.writeInt32(crc32)              -- crc32
  103.   fr.writeInt32(c_len)              -- compressed filesize
  104.   fr.writeInt32(unc_len)            -- uncompressed filesize
  105.   fr.writeInt16(tFilename.length)   -- length of filename
  106.   fr.writeInt16(0)                  -- extra field length
  107.   fr.writeRawString(tFilename, tFilename.length)
  108.  
  109.   -- "file data" segment    
  110.   fr.writeByteArray(tData, 3, tData.length-6) -- remove header and adler32 checksum
  111.    
  112.   -- add this entry to array
  113.   me.pDataSec.add(fr)
  114.  
  115.   -- now add to central directory record
  116.   cdrec = bytearray()  
  117.   cdrec.writeInt32(80 + 75*256 + 1*65536 + 2*16777216) -- \x50\x4b\x01\x02
  118.   cdrec.writeInt16(0)   -- \x00\x00 = version made by
  119.   cdrec.writeInt16(20)  -- \x14\x00 = version needed to extract
  120.   cdrec.writeInt16(0)   -- \x00\x00 = gen purpose bit flag
  121.   cdrec.writeInt16(8)   -- \x08\x00 = compression method
  122.   cdrec.writeInt32(dosTime)    -- last mod time & date
  123.   cdrec.writeInt32(crc32)      -- crc32
  124.   cdrec.writeInt32(c_len)      -- compressed filesize
  125.   cdrec.writeInt32(unc_len)    -- uncompressed filesize
  126.   cdrec.writeInt16(tFilename.length) -- length of filename
  127.   cdrec.writeInt16(0)          -- extra field length
  128.   cdrec.writeInt16(tComment.length) -- file comment length
  129.   cdrec.writeInt16(0)          -- disk number start
  130.   cdrec.writeInt16(0)          -- internal file attributes
  131.   cdrec.writeInt32(32)         -- external file attributes - "archive" bit set
  132.   cdrec.writeInt32(me.pOffset) -- relative offset of local header
  133.  
  134.   me.pOffset = me.pOffset + fr.length
  135.  
  136.   cdrec.writeRawString(tFilename, tFilename.length)
  137.   -- optional extra field
  138.   if tComment.length>0 then cdrec.writeRawString(tComment, tComment.length)
  139.  
  140.   -- save to central directory
  141.   me.pCtrlDir.add(cdrec)
  142.  
  143. end
  144.  
  145. ----------------------------------------
  146. --
  147. ----------------------------------------
  148. on mSaveZip(me, tFilename, tZipComment)
  149.   tZipComment = string(tZipComment)
  150.  
  151.   -- DATA SECTION
  152.   baZip = bytearray()
  153.   repeat with ba in me.pDataSec
  154.     baZip.writeByteArray(ba)
  155.   end repeat
  156.   dataSize = baZip.length
  157.  
  158.   -- CENTRAL DIR
  159.   repeat with ba in me.pCtrlDir
  160.     baZip.writeByteArray(ba)
  161.   end repeat
  162.   ctrldirSize = baZip.length - dataSize
  163.  
  164.   -- EOF CENTRAL DIR = \x50\x4b\x05\x06\x00\x00\x00\x00
  165.   baZip.writeInt32(80 + 75*256 + 5*65536 + 6*16777216)
  166.   baZip.writeInt32(0)
  167.  
  168.   baZip.writeInt16(me.pCtrlDir.count)  -- total # of entries "on this disk"
  169.   baZip.writeInt16(me.pCtrlDir.count)  -- total # of entries overall
  170.   baZip.writeInt32(ctrldirSize)        -- size of central dir
  171.   baZip.writeInt32(dataSize)           -- offset to start of central dir
  172.   baZip.writeInt16(tZipComment.length) -- zip file comment length
  173.   if tZipComment.length>0 then
  174.     baZip.writeRawString(tZipComment, tZipComment.length)
  175.   end if
  176.  
  177.   if string(tFilename)<>"" then
  178.     file_put_bytes(tFilename, baZip)
  179.   else
  180.     return baZip
  181.   end if
  182. end
  183.  
  184. ----------------------------------------
  185. --
  186. ----------------------------------------
  187. on mLoadZip(me, tZipFile)
  188.   me.pZipFile = tZipFile
  189.   me.pFileList = []
  190.  
  191.   fp = xtra("fileIO").new()
  192.   fp.openFile(tZipFile, 1)
  193.  
  194.   -- PARSE DATA SECTION
  195.   repeat while true
  196.     magic = fp.readByteArray(4)
  197.     if not(magic[1]=80 AND magic[2]=75 AND magic[3]=3 AND magic[4]=4) then -- magic: 'P' 'K' 3 4
  198.       fp.setPosition(fp.getPosition() - 4)
  199.       exit repeat
  200.     end if
  201.     tFile = [:]
  202.    
  203.     ba = fp.readByteArray(6+16+2+2)
  204.     ba.position = 7
  205.    
  206.     dtime = ba.readInt32()
  207.     crc32 = ba.readInt32()         -- crc32
  208.     c_len = ba.readInt32()         -- compressed filesize
  209.     unc_len = ba.readInt32()       -- uncompressed filesize    
  210.     filenameLen = ba.readInt16()   -- length of filename
  211.     extraFieldLen = ba.readInt16() -- extra field length
  212.    
  213.     ba = fp.readByteArray(filenameLen)
  214.     ba.position = 1
  215.     tFile["filename"] = ba.readRawString(ba.length)
  216.    
  217.     if extraFieldLen>0 then
  218.       ba = fp.readByteArray(extraFieldLen)
  219.     end if
  220.    
  221.     tFile["date"] = dosTimeToDate(dtime)
  222.     tFile["crc32"] = crc32
  223.     tFile["uncompressedSize"] = unc_len
  224.     tFile["compressedSize"] = c_len
  225.    
  226.     tFile["offset"] = fp.getPosition()
  227.     me.pFileList.add(tFile)
  228.     fp.setPosition(fp.getPosition() + c_len -12)
  229.   end repeat
  230.  
  231.   -- PARSE CENTRAL DIR
  232.   cnt = me.pFileList.count
  233.   repeat with i = 1 to cnt
  234.     tFile = me.pFileList[i]
  235.    
  236.     magic = fp.readByteArray(4)
  237.     if not(magic[1]=80 AND magic[2]=75 AND magic[3]=1 AND magic[4]=2) then exit repeat -- magic: 'P' 'K' 1 2
  238.    
  239.     fp.setPosition(fp.getPosition() + 8)
  240.     cdrec = fp.readByteArray(16+6)
  241.        
  242.     cdrec.position = 17
  243.     filenameLen = cdrec.readInt16()
  244.     cdrec.readInt16()
  245.     commentLen = cdrec.readInt16()
  246.    
  247.     fp.setPosition(fp.getPosition() + 12)
  248.    
  249.     tmp = fp.readByteArray(filenameLen)    
  250.     if commentLen>0 then
  251.       tmp = fp.readByteArray(commentLen)
  252.       tmp.position = 1
  253.       tFile["comment"] = tmp.readRawString(tmp.length)
  254.     end if
  255.    
  256.   end repeat
  257.   fp.closeFile()
  258.   fp = 0
  259.   return me.pFileList
  260. end
  261.  
  262. ----------------------------------------
  263. --
  264. ----------------------------------------
  265. on mUnpacktFile (me, tFileIndex, tDestDirectory, tAdler32)
  266.   if voidP(tDestDirectory) then tDestDirectory=_movie.path
  267.   else if the last char of tDestDirectory<>the last char of _movie.path then
  268.     put the last char of _movie.path after tDestDirectory
  269.   end if
  270.   if voidP(tAdler32) then tAdler32=#comment -- ???
  271.   tFile = me.pFileList[tFileIndex]  
  272.   if tAdler32=#comment then tAdler32=integer(tFile["comment"])
  273.   fp = xtra("fileIO").new()
  274.   fp.openFile(pZipFile, 1)
  275.   fp.setPosition(tFile["offset"])
  276.   ba = fp.readByteArray(tFile["compressedSize"]-12)
  277.   fp.closeFile()
  278.   fp = 0
  279.  
  280.   -- INFLATE BYTEARRAY
  281.   ret = bytearray(ba.length+6)
  282.   -- copy deflated data
  283.   ret[1] = 120
  284.   ret[2] = 218
  285.   ret.position = 3
  286.   ret.writeByteArray(ba)
  287.   ret.endian = #bigEndian
  288.   ret.writeInt32(tAdler32)
  289.   ret.uncompress()
  290.  
  291.   fn = tDestDirectory & tFile["filename"]
  292.   file_put_bytes(fn, ret)
  293.   return fn
  294. end
  295.  
  296. --**************************************
  297. -- UTILITIES
  298. --**************************************
  299.  
  300. ----------------------------------------
  301. --
  302. ----------------------------------------
  303. on dateToDosTime (tDate)
  304.   if (tDate.year < 1980) then
  305.     tDate = date(1980)
  306.   end if
  307.  
  308.   tYear = tDate.year-1980
  309.   tMonth = tDate.month
  310.   tDay = tDate.day
  311.   tHour = tDate.seconds/3600
  312.   tMin = (tDate.seconds mod 3600)/60
  313.   tSec = tDate.seconds mod 60
  314.  
  315.   d = tYear * 33554432
  316.   d = bitOr(d, tMonth * 2097152)
  317.   d = bitOr(d, tDay * 65536)
  318.   d = bitOr(d, tHour * 2048)
  319.   d = bitOr(d, tMin * 32)
  320.   d = bitOr(d, tSec / 2)
  321.  
  322.   return d  
  323. end
  324.  
  325. ----------------------------------------
  326. --
  327. ----------------------------------------
  328. on dosTimeToDate(dosTime)
  329.  
  330.   tSec = 2 * bitAnd(dosTime, 31)
  331.   tMin = bitAnd((dosTime / 32), 63)
  332.   tHour = bitAnd((dosTime / 2048), 31)
  333.  
  334.   tDay = bitAnd((dosTime / 65536), 31)
  335.   tMonth = bitAnd((dosTime / 2097152), 15)
  336.   tYear = bitAnd((dosTime / 33554432), 127) + 1980
  337.  
  338.   tDate = date(tYear, tMonth, tDay)
  339.   tDate.seconds = tHour*3600  + tMin*60 + tSec
  340.  
  341.   return tDate
  342. end
  343.  
[raw code]