diff --git a/extlibs/love-zip/CRC32.lua b/extlibs/love-zip/CRC32.lua new file mode 100644 index 0000000..ad64c02 --- /dev/null +++ b/extlibs/love-zip/CRC32.lua @@ -0,0 +1,316 @@ +--CRC32.lua by RamiLego4Game +--[[ +Made for use in LuaJIT (Requires the bitop library). + +local CRC32 = require("CRC32") + +local fileCRC = CRC32(DataString) + +References: +https://en.wikipedia.org/wiki/Cyclic_redundancy_check +http://www.ipgp.fr/~tuchais/earthworm/v6.3/src/data_sources/naqs2ew/crc32.c +]] + +--[[ +MIT License + +Copyright (c) 2018 Rami Sabbagh + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]] + +local bit = require("bit") + +local bxor, band, rshift = bit.bxor, bit.band, bit.rshift + +local strByte = string.byte + +local crctable = { + 0x00000000, + 0x77073096, + 0xEE0E612C, + 0x990951BA, + 0x076DC419, + 0x706AF48F, + 0xE963A535, + 0x9E6495A3, + 0x0EDB8832, + 0x79DCB8A4, + 0xE0D5E91E, + 0x97D2D988, + 0x09B64C2B, + 0x7EB17CBD, + 0xE7B82D07, + 0x90BF1D91, + 0x1DB71064, + 0x6AB020F2, + 0xF3B97148, + 0x84BE41DE, + 0x1ADAD47D, + 0x6DDDE4EB, + 0xF4D4B551, + 0x83D385C7, + 0x136C9856, + 0x646BA8C0, + 0xFD62F97A, + 0x8A65C9EC, + 0x14015C4F, + 0x63066CD9, + 0xFA0F3D63, + 0x8D080DF5, + 0x3B6E20C8, + 0x4C69105E, + 0xD56041E4, + 0xA2677172, + 0x3C03E4D1, + 0x4B04D447, + 0xD20D85FD, + 0xA50AB56B, + 0x35B5A8FA, + 0x42B2986C, + 0xDBBBC9D6, + 0xACBCF940, + 0x32D86CE3, + 0x45DF5C75, + 0xDCD60DCF, + 0xABD13D59, + 0x26D930AC, + 0x51DE003A, + 0xC8D75180, + 0xBFD06116, + 0x21B4F4B5, + 0x56B3C423, + 0xCFBA9599, + 0xB8BDA50F, + 0x2802B89E, + 0x5F058808, + 0xC60CD9B2, + 0xB10BE924, + 0x2F6F7C87, + 0x58684C11, + 0xC1611DAB, + 0xB6662D3D, + 0x76DC4190, + 0x01DB7106, + 0x98D220BC, + 0xEFD5102A, + 0x71B18589, + 0x06B6B51F, + 0x9FBFE4A5, + 0xE8B8D433, + 0x7807C9A2, + 0x0F00F934, + 0x9609A88E, + 0xE10E9818, + 0x7F6A0DBB, + 0x086D3D2D, + 0x91646C97, + 0xE6635C01, + 0x6B6B51F4, + 0x1C6C6162, + 0x856530D8, + 0xF262004E, + 0x6C0695ED, + 0x1B01A57B, + 0x8208F4C1, + 0xF50FC457, + 0x65B0D9C6, + 0x12B7E950, + 0x8BBEB8EA, + 0xFCB9887C, + 0x62DD1DDF, + 0x15DA2D49, + 0x8CD37CF3, + 0xFBD44C65, + 0x4DB26158, + 0x3AB551CE, + 0xA3BC0074, + 0xD4BB30E2, + 0x4ADFA541, + 0x3DD895D7, + 0xA4D1C46D, + 0xD3D6F4FB, + 0x4369E96A, + 0x346ED9FC, + 0xAD678846, + 0xDA60B8D0, + 0x44042D73, + 0x33031DE5, + 0xAA0A4C5F, + 0xDD0D7CC9, + 0x5005713C, + 0x270241AA, + 0xBE0B1010, + 0xC90C2086, + 0x5768B525, + 0x206F85B3, + 0xB966D409, + 0xCE61E49F, + 0x5EDEF90E, + 0x29D9C998, + 0xB0D09822, + 0xC7D7A8B4, + 0x59B33D17, + 0x2EB40D81, + 0xB7BD5C3B, + 0xC0BA6CAD, + 0xEDB88320, + 0x9ABFB3B6, + 0x03B6E20C, + 0x74B1D29A, + 0xEAD54739, + 0x9DD277AF, + 0x04DB2615, + 0x73DC1683, + 0xE3630B12, + 0x94643B84, + 0x0D6D6A3E, + 0x7A6A5AA8, + 0xE40ECF0B, + 0x9309FF9D, + 0x0A00AE27, + 0x7D079EB1, + 0xF00F9344, + 0x8708A3D2, + 0x1E01F268, + 0x6906C2FE, + 0xF762575D, + 0x806567CB, + 0x196C3671, + 0x6E6B06E7, + 0xFED41B76, + 0x89D32BE0, + 0x10DA7A5A, + 0x67DD4ACC, + 0xF9B9DF6F, + 0x8EBEEFF9, + 0x17B7BE43, + 0x60B08ED5, + 0xD6D6A3E8, + 0xA1D1937E, + 0x38D8C2C4, + 0x4FDFF252, + 0xD1BB67F1, + 0xA6BC5767, + 0x3FB506DD, + 0x48B2364B, + 0xD80D2BDA, + 0xAF0A1B4C, + 0x36034AF6, + 0x41047A60, + 0xDF60EFC3, + 0xA867DF55, + 0x316E8EEF, + 0x4669BE79, + 0xCB61B38C, + 0xBC66831A, + 0x256FD2A0, + 0x5268E236, + 0xCC0C7795, + 0xBB0B4703, + 0x220216B9, + 0x5505262F, + 0xC5BA3BBE, + 0xB2BD0B28, + 0x2BB45A92, + 0x5CB36A04, + 0xC2D7FFA7, + 0xB5D0CF31, + 0x2CD99E8B, + 0x5BDEAE1D, + 0x9B64C2B0, + 0xEC63F226, + 0x756AA39C, + 0x026D930A, + 0x9C0906A9, + 0xEB0E363F, + 0x72076785, + 0x05005713, + 0x95BF4A82, + 0xE2B87A14, + 0x7BB12BAE, + 0x0CB61B38, + 0x92D28E9B, + 0xE5D5BE0D, + 0x7CDCEFB7, + 0x0BDBDF21, + 0x86D3D2D4, + 0xF1D4E242, + 0x68DDB3F8, + 0x1FDA836E, + 0x81BE16CD, + 0xF6B9265B, + 0x6FB077E1, + 0x18B74777, + 0x88085AE6, + 0xFF0F6A70, + 0x66063BCA, + 0x11010B5C, + 0x8F659EFF, + 0xF862AE69, + 0x616BFFD3, + 0x166CCF45, + 0xA00AE278, + 0xD70DD2EE, + 0x4E048354, + 0x3903B3C2, + 0xA7672661, + 0xD06016F7, + 0x4969474D, + 0x3E6E77DB, + 0xAED16A4A, + 0xD9D65ADC, + 0x40DF0B66, + 0x37D83BF0, + 0xA9BCAE53, + 0xDEBB9EC5, + 0x47B2CF7F, + 0x30B5FFE9, + 0xBDBDF21C, + 0xCABAC28A, + 0x53B39330, + 0x24B4A3A6, + 0xBAD03605, + 0xCDD70693, + 0x54DE5729, + 0x23D967BF, + 0xB3667A2E, + 0xC4614AB8, + 0x5D681B02, + 0x2A6F2B94, + 0xB40BBE37, + 0xC30C8EA1, + 0x5A05DF1B, + 0x2D02EF8D, +} + +return function(data) + local crc32 = 0xFFFFFFFF + + for char in string.gmatch(data, ".") do + local byte = strByte(char) + + local nLookupIndex = band(bxor(crc32, byte), 0xFF) + 1 + crc32 = bxor(rshift(crc32, 8), crctable[nLookupIndex]) + end + + crc32 = bxor(crc32, 0xFFFFFFFF) + + return crc32 +end diff --git a/extlibs/love-zip/init.lua b/extlibs/love-zip/init.lua new file mode 100644 index 0000000..f70af20 --- /dev/null +++ b/extlibs/love-zip/init.lua @@ -0,0 +1,465 @@ +local libpath = ... + +local crc32 = require(libpath .. ".CRC32") +local bit = require("bit") + +local bor, band, lshift, rshift, tohex = bit.bor, bit.band, bit.lshift, bit.rshift, bit.tohex + +--[[ + + [local file header 1] + [encryption header 1] + [file data 1] + [data descriptor 1] + . + . + . + [local file header n] + [encryption header n] + [file data n] + [data descriptor n] + [archive decryption header] + [archive extra data record] + [central directory header 1] + . + . + . + [central directory header n] + [zip64 end of central directory record] + [zip64 end of central directory locator] + [end of central directory record] + +]] + +--== Helper functions ==-- + +local function newStringFile(data) + local str = data or "" + + local file = {} + + local pos = 0 + + function file:getSize() + return #str + end + function file:seek(p) + pos = p + end + function file:tell() + return pos + end + function file:read(bytes) + if bytes then + if pos + bytes > #str then + bytes = #str - pos + end + + local substr = str:sub(pos + 1, pos + bytes) + + pos = pos + bytes + + return substr, bytes + else + return str + end + end + + function file:write(d, s) + str = str:sub(1, pos) .. d .. str:sub(pos + #d + 1, -1) + + pos = pos + #d + + return #d + end + + function file:flush() end + function file:close() end + + return file +end + +local function decodeNumber(str, bigEndian) + local num = 0 + + if not bigEndian then + str = str:reverse() + end + + for char in string.gmatch(str, ".") do + local byte = string.byte(char) + + num = lshift(num, 8) + num = bor(num, byte) + end + + return num +end + +local function encodeNumber(num, len, bigEndian) + local chars = {} + + for i = 1, len do + chars[i] = string.char(band(num, 255)) + num = rshift(num, 8) + end + + chars = table.concat(chars) + + if bigEndian then + chars = chars:reverse() + end + + return chars +end + +--== Internal functions ==-- + +local function writeFile(zipFile, fileName, fileData, modTime, extraField, fileComment, attributes) + local fileOffset = zipFile:tell() + + --[[ + Local file header: + + local file header signature 4 bytes (0x04034b50) + version needed to extract 2 bytes + general purpose bit flag 2 bytes + compression method 2 bytes + last mod file time 2 bytes + last mod file date 2 bytes + crc-32 4 bytes + compressed size 4 bytes + uncompressed size 4 bytes + file name length 2 bytes + extra field length 2 bytes + + file name (variable size) + extra field (variable size) + + ]] + + zipFile:write("\80\75\3\4") --local file header signature - 4 bytes - (0x04034b50) + + zipFile:write(encodeNumber(20, 2)) --version needed to extract - 2 bytes + --2.0 - File is compressed using Deflate compression + + zipFile:write(string.char(2) .. string.char(8)) --general purpose bit flag - 2 bytes + --[[(For Methods 8 and 9 - Deflating) + Bit 2 Bit 1 + 0 0 Normal (-en) compression option was used. + 0 1 Maximum (-exx/-ex) compression option was used. <---- + 1 0 Fast (-ef) compression option was used. + 1 1 Super Fast (-es) compression option was used. + ]] + --[[Bit 11: Language encoding flag (EFS). If this bit is set, + the filename and comment fields for this file + MUST be encoded using UTF-8. (see APPENDIX D)]] + + zipFile:write(encodeNumber(8, 2)) --compression method - 2 bytes + --8 - The file is Deflated + + zipFile:write(encodeNumber(0, 2)) --last mod file time - 2 bytes + --Leave as zero, it doesn't worth calculating. + + zipFile:write(encodeNumber(0, 2)) --last mod file date - 2 bytes + --Leave as zero, it doesn't worth calculating. + + local fileCRC32 = crc32(fileData) + + zipFile:write(encodeNumber(fileCRC32, 4)) --crc-32 - 4 bytes + --crc32 of uncompressed file data. + + local compressedData = love.data.compress("string", "deflate", fileData, 9) + + zipFile:write(encodeNumber(#compressedData, 4)) --compressed size - 4 bytes + + zipFile:write(encodeNumber(#fileData, 4)) --uncompressed size - 4 bytes + + zipFile:write(encodeNumber(fileName and #fileName or 0, 2)) --file name length - 2 bytes + + zipFile:write(encodeNumber(extraField and #extraField or 0, 2)) --extra field length - 2 bytes + + zipFile:write(fileName or "") --file name (variable size) + + zipFile:write(extraField or "") --extra field (variable size) + + --File data + zipFile:write(compressedData) + + --[[ + Data descriptor: + + crc-32 4 bytes + compressed size 4 bytes + uncompressed size 4 bytes + ]] + + zipFile:write("\80\75\7\8") --signature - 4 bytes - (0x08074b50) + + zipFile:write(encodeNumber(fileCRC32, 4)) --crc-32 - 4 bytes + --crc32 of uncompressed file data. + + zipFile:write(encodeNumber(#compressedData, 4)) --compressed size - 4 bytes + + zipFile:write(encodeNumber(#fileData, 4)) --uncompressed size - 4 bytes + + --Return file info, used when writing the centeral directory. + return { + fileOffset = fileOffset, + modTime = modTime or 0, + fileCRC32 = fileCRC32, + compressedSize = #compressedData, + uncompressedSize = #fileData, + fileName = fileName or "", + extraField = extraField or "", + comment = fileComment or "", + attributes = attributes or 0, + } +end + +local function writeCenteralDirectory(zipFile, filesInfos) + local centeralDirectorySize = 0 + local centeralDirectoryOffset = zipFile:tell() + + --[[ + Central directory structure: + + central file header signature 4 bytes (0x02014b50) + version made by 2 bytes + version needed to extract 2 bytes + general purpose bit flag 2 bytes + compression method 2 bytes + last mod file time 2 bytes + last mod file date 2 bytes + crc-32 4 bytes + compressed size 4 bytes + uncompressed size 4 bytes + file name length 2 bytes + extra field length 2 bytes + file comment length 2 bytes + disk number start 2 bytes + internal file attributes 2 bytes + external file attributes 4 bytes + relative offset of local header 4 bytes + + file name (variable size) + extra field (variable size) + file comment (variable size) + ]] + + for fileID, fileInfo in pairs(filesInfos) do + zipFile:write("\80\75\1\2") --central file header signature - 4 bytes - (0x02014b50) + + --version made by - 2 bytes + zipFile:write(string.char(63)) --Spec file version: 6.3.4 + zipFile:write(string.char(3)) --3 - UNIX + + zipFile:write(encodeNumber(20, 2)) --version needed to extract - 2 bytes + --2.0 - File is compressed using Deflate compression + + zipFile:write(string.char(2) .. string.char(8)) --general purpose bit flag - 2 bytes + --[[(For Methods 8 and 9 - Deflating) + Bit 2 Bit 1 + 0 0 Normal (-en) compression option was used. + 0 1 Maximum (-exx/-ex) compression option was used. <---- + 1 0 Fast (-ef) compression option was used. + 1 1 Super Fast (-es) compression option was used. + ]] + --[[Bit 11: Language encoding flag (EFS). If this bit is set, + the filename and comment fields for this file + MUST be encoded using UTF-8. (see APPENDIX D)]] + + zipFile:write(encodeNumber(8, 2)) --compression method - 2 bytes + --8 - The file is Deflated + + zipFile:write(encodeNumber(0, 2)) --last mod file time - 2 bytes + --Leave as zero, it doesn't worth calculating. + + zipFile:write(encodeNumber(0, 2)) --last mod file date - 2 bytes + --Leave as zero, it doesn't worth calculating. + + zipFile:write(encodeNumber(fileInfo.fileCRC32, 4)) --crc-32 - 4 bytes + --crc32 of uncompressed file data. + + zipFile:write(encodeNumber(fileInfo.compressedSize, 4)) --compressed size - 4 bytes + + zipFile:write(encodeNumber(fileInfo.uncompressedSize, 4)) --uncompressed size - 4 bytes + + zipFile:write(encodeNumber(#fileInfo.fileName, 2)) --file name length - 2 bytes + + zipFile:write(encodeNumber(#fileInfo.extraField, 2)) --extra field length - 2 bytes + + zipFile:write(encodeNumber(#fileInfo.comment, 2)) --file comment length - 2 bytes + + zipFile:write(encodeNumber(0, 2)) --disk number start - 2 bytes + + zipFile:write(encodeNumber(0, 2)) --internal file attributes - 2 bytes + + zipFile:write(encodeNumber(fileInfo.attributes, 4)) --external file attributes - 4 bytes + + zipFile:write(encodeNumber(fileInfo.fileOffset, 4)) --relative offset of local header - 4 bytes + + zipFile:write(fileInfo.fileName) --file name (variable size) + + zipFile:write(fileInfo.extraField) --extra field (variable size) + + zipFile:write(fileInfo.comment) --file comment (variable size) + + centeralDirectorySize = centeralDirectorySize + + 46 + + #fileInfo.fileName + + #fileInfo.extraField + + #fileInfo.comment + end + + --Return centeral directory info + return { + offset = centeralDirectoryOffset, + size = centeralDirectorySize, + entries = #filesInfos, + } +end + +local function writeEndOfCenteralDirectory(zipFile, centeralDirectoryInfo, zipComment) + --[[ + End of central directory record: + + end of central dir signature 4 bytes (0x06054b50) + number of this disk 2 bytes + number of the disk with the + start of the central directory 2 bytes + total number of entries in the + central directory on this disk 2 bytes + total number of entries in + the central directory 2 bytes + size of the central directory 4 bytes + offset of start of central + directory with respect to + the starting disk number 4 bytes + .ZIP file comment length 2 bytes + .ZIP file comment (variable size) + ]] + + zipFile:write("\80\75\5\6") --end of central dir signature - 4 bytes - (0x06054b50) + zipFile:write(encodeNumber(0, 2)) --number of this disk - 2 bytes + + zipFile:write(encodeNumber(0, 2)) --number of the disk with the start of the central directory - 2 bytes + + zipFile:write(encodeNumber(centeralDirectoryInfo.entries, 2)) --total number of entries in the central directory on this disk - 2 bytes + + zipFile:write(encodeNumber(centeralDirectoryInfo.entries, 2)) --total number of entries in the central directory - 2 bytes + + zipFile:write(encodeNumber(centeralDirectoryInfo.size, 4)) --size of the central directory - 4 bytes + + zipFile:write(encodeNumber(centeralDirectoryInfo.offset, 4)) --offset of start of central directory with respect to the starting disk number - 4 bytes + + zipFile:write(encodeNumber(zipComment and #zipComment or 0, 2)) --.ZIP file comment length - 2 bytes + + zipFile:write(zipComment or "") --.ZIP file comment (variable size) +end + +--== User API ==-- + +local zapi = {} + +function zapi.newZipWriter(zipFile) + local zipFile = zipFile or newStringFile() + + local filesInfos = {} + + local zipFinished = false + + local writer = {} + + function writer.addFile(fileName, fileData, modTime, extraField, fileComment, attributes) + if zipFinished then + return error("The .ZIP file is finished !") + end + + local fileInfo = writeFile(zipFile, fileName, fileData, modTime, extraField, fileComment, attributes) + + filesInfos[#filesInfos + 1] = fileInfo + end + + function writer.finishZip(zipComment) + if zipFinished then + return error("The .ZIP file is already finished !") + end + + local centeralDirectoryInfo = writeCenteralDirectory(zipFile, filesInfos) + writeEndOfCenteralDirectory(zipFile, centeralDirectoryInfo, zipComment) + + zipFinished = true + + return zipFile + end + + return writer +end + +function zapi.createZip(path, zipFile, yield_in_between) + path = path:gsub("\\", "/") + + if path:sub(1, 1) == "/" then + path = path:sub(2, -1) + end + if path:sub(-1, -1) == "/" then + path = path:sub(1, -2) + end + local basename = path:sub(#path:match(".*/") + 1) + + if yield_in_between then + coroutine.yield() + end + local writer = zapi.newZipWriter(zipFile) + + local function index(dir) + if yield_in_between then + coroutine.yield() + end + local dirInfo = love.filesystem.getInfo(dir) + + if dirInfo.type == "file" then + local fileData = love.filesystem.read(dir) + local fileName = basename .. "/" .. dir:sub(#path + 2, -1) + local modTime = dirInfo.modTime + + writer.addFile(fileName, fileData, modTime) + elseif dirInfo.type == "directory" then + for id, item in ipairs(love.filesystem.getDirectoryItems(dir)) do + index(dir .. "/" .. item) + end + end + end + + if not love.filesystem.getInfo(path) then + return false, "Source doesn't exist !" + end + + index(path) + if yield_in_between then + coroutine.yield() + end + + local zipData = writer.finishZip() + + if not zipFile then + return true, zipData:read() + end + + zipFile:flush() + zipFile:close() + + return true +end + +function zapi.writeZip(path, destination, yield_in_between) + local zipFile, zipFileErr = love.filesystem.openFile(destination, "w") + if not zipFile then + return false, "Failed to open destination zip file: " .. tostring(zipFileErr) + end + + return zapi.createZip(path, zipFile, yield_in_between) +end + +return zapi diff --git a/ui/overlay/packs/download.lua b/ui/overlay/packs/download.lua new file mode 100644 index 0000000..4ae6ccf --- /dev/null +++ b/ui/overlay/packs/download.lua @@ -0,0 +1,49 @@ +local version, pack_name, tmp_folder, server_api_url, pack_size = ... +xpcall(function() + local zip = require("extlibs.zip") + local log = require("log")("ui.overlay.packs.download") + local http = require("socket.http") + local url = require("socket.url") + + local filename = string.format("%s%s_%s.zip", tmp_folder, version, pack_name) + local file = love.filesystem.openFile(filename, "w") + local download_size, last_progress = 0, nil + log("Downloading", pack_name) + local channel = love.thread.getChannel(string.format("pack_download_progress_%d_%s", version, pack_name)) + channel:clear() + channel:push(0) + local success, err = http.request({ + url = server_api_url .. "get_pack/" .. version .. "/" .. url.escape(pack_name), + sink = function(chunk, err) + if err then + log(err) + file:close() + love.filesystem.remove(filename) + elseif chunk then + file:write(chunk) + download_size = download_size + #chunk + local progress = math.floor(download_size / pack_size * 100) + if progress ~= last_progress then + channel:push(progress) + last_progress = progress + end + return 1 + end + end, + }) + file:close() + if not success then + love.filesystem.remove(filename) + error("Failed http request: " .. err) + end + if download_size < pack_size then + error(string.format("Failed download: missing %d bytes", pack_size - download_size)) + end + log("Extracting", filename) + local zip_file = zip:new(filename) + zip_file:unzip("packs" .. version) + zip_file:close() + love.filesystem.remove(filename) +end, function(err) + love.thread.getChannel(string.format("pack_download_error_%d_%s", version, pack_name)):push(err) +end)