|
| 1 | + |
| 2 | + |
| 3 | +local function bxor(a,b) return a ~ b end |
| 4 | +local function band(a,b) return a & b end |
| 5 | +local function bor(a,b) return a | b end |
| 6 | +local function lshift(a,b) return a << b end |
| 7 | +local function rshift(a,b) return a >> b end |
| 8 | + |
| 9 | +local function newbitstream(data) |
| 10 | + local pos, bitbuf, bitcnt = 1, 0, 0 |
| 11 | + local function needbits(n) |
| 12 | + while bitcnt < n do |
| 13 | + local b = data:byte(pos) or 0 |
| 14 | + pos = pos + 1 |
| 15 | + bitbuf = bitbuf + lshift(b, bitcnt) |
| 16 | + bitcnt = bitcnt + 8 |
| 17 | + end |
| 18 | + end |
| 19 | + local function readbits(n) |
| 20 | + needbits(n) |
| 21 | + local v = band(bitbuf, (1 << n) - 1) |
| 22 | + bitbuf = rshift(bitbuf, n) |
| 23 | + bitcnt = bitcnt - n |
| 24 | + return v |
| 25 | + end |
| 26 | + local function alignbyte() bitbuf, bitcnt = 0, 0 end |
| 27 | + local function getpos() return pos end |
| 28 | + local function skip(n) pos = pos + n end |
| 29 | + return { readbits = readbits, align = alignbyte, pos = getpos, skip = skip } |
| 30 | +end |
| 31 | + |
| 32 | +local function revbits(x, bits) |
| 33 | + local y = 0 |
| 34 | + for i = 1, bits do |
| 35 | + y = (y << 1) | (x & 1) |
| 36 | + x = x >> 1 |
| 37 | + end |
| 38 | + return y |
| 39 | +end |
| 40 | + |
| 41 | +local function make_huff(lengths) |
| 42 | + local maxlen, counts = 0, {} |
| 43 | + for _, len in ipairs(lengths) do |
| 44 | + if len > 0 then |
| 45 | + counts[len] = (counts[len] or 0) + 1 |
| 46 | + if len > maxlen then maxlen = len end |
| 47 | + end |
| 48 | + end |
| 49 | + |
| 50 | + local code = 0 |
| 51 | + local next_code = {} |
| 52 | + for bits = 1, maxlen do |
| 53 | + code = (code + (counts[bits - 1] or 0)) << 1 |
| 54 | + next_code[bits] = code |
| 55 | + end |
| 56 | + |
| 57 | + local tab = {} |
| 58 | + for sym, len in ipairs(lengths) do |
| 59 | + if len > 0 then |
| 60 | + local c = revbits(next_code[len], len) |
| 61 | + tab[c] = { sym = sym - 1, len = len } |
| 62 | + next_code[len] = next_code[len] + 1 |
| 63 | + end |
| 64 | + end |
| 65 | + |
| 66 | + return { tab = tab, max = maxlen } |
| 67 | +end |
| 68 | + |
| 69 | +local function read_huff(bs, h) |
| 70 | + local code = 0 |
| 71 | + for len = 1, h.max do |
| 72 | + code = code | (bs.readbits(1) << (len - 1)) |
| 73 | + local masked = band(code, (1 << len) - 1) |
| 74 | + local e = h.tab[masked] |
| 75 | + if e and e.len == len then return e.sym end |
| 76 | + end |
| 77 | + error("invalid Huffman code") |
| 78 | +end |
| 79 | + |
| 80 | +local function inflate(data) |
| 81 | + local bs = newbitstream(data) |
| 82 | + local out = {} -- out[i] = single-character string for byte i (1-based) |
| 83 | + local outpos = 0 |
| 84 | + local function append_byte(byte) |
| 85 | + outpos = outpos + 1 |
| 86 | + out[outpos] = string.char(byte) |
| 87 | + end |
| 88 | + local function append_str(s) |
| 89 | + -- push each byte as single-character entry |
| 90 | + for i = 1, #s do |
| 91 | + outpos = outpos + 1 |
| 92 | + out[outpos] = s:sub(i,i) |
| 93 | + end |
| 94 | + end |
| 95 | + |
| 96 | + local done = false |
| 97 | + while not done do |
| 98 | + local final = bs.readbits(1) |
| 99 | + local btype = bs.readbits(2) |
| 100 | + |
| 101 | + if btype == 0 then |
| 102 | + -- uncompressed block: align, then read LEN, NLEN from byte stream |
| 103 | + bs.align() |
| 104 | + local p = bs.pos() |
| 105 | + local function le16(s, i) |
| 106 | + local a,b = s:byte(i,i+1); return a + b*256 |
| 107 | + end |
| 108 | + local len = le16(data, p) |
| 109 | + local nlen = le16(data, p + 2) |
| 110 | + bs.skip(4) -- consume LEN and NLEN |
| 111 | + local s = data:sub(bs.pos(), bs.pos() + len - 1) |
| 112 | + append_str(s) |
| 113 | + bs.skip(len) |
| 114 | + |
| 115 | + elseif btype == 1 or btype == 2 then |
| 116 | + local litlen, dist |
| 117 | + if btype == 1 then |
| 118 | + -- fixed Huffman |
| 119 | + local ll = {} |
| 120 | + for _ = 0, 143 do ll[#ll+1] = 8 end |
| 121 | + for _ = 144, 255 do ll[#ll+1] = 9 end |
| 122 | + for _ = 256, 279 do ll[#ll+1] = 7 end |
| 123 | + for _ = 280, 287 do ll[#ll+1] = 8 end |
| 124 | + litlen = make_huff(ll) |
| 125 | + local dl = {} |
| 126 | + for _ = 0, 31 do dl[#dl+1] = 5 end |
| 127 | + dist = make_huff(dl) |
| 128 | + else |
| 129 | + -- dynamic Huffman |
| 130 | + local hlit = bs.readbits(5) + 257 |
| 131 | + local hdist = bs.readbits(5) + 1 |
| 132 | + local hclen = bs.readbits(4) + 4 |
| 133 | + local order = {16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15} |
| 134 | + local clen = {} |
| 135 | + for i = 1, 19 do clen[i] = 0 end |
| 136 | + for i = 1, hclen do clen[order[i] + 1] = bs.readbits(3) end |
| 137 | + local chuff = make_huff(clen) |
| 138 | + local lens = {} |
| 139 | + while #lens < hlit + hdist do |
| 140 | + local sym = read_huff(bs, chuff) |
| 141 | + if sym <= 15 then |
| 142 | + lens[#lens + 1] = sym |
| 143 | + elseif sym == 16 then |
| 144 | + local r = bs.readbits(2) + 3 |
| 145 | + local last = lens[#lens] |
| 146 | + for _ = 1, r do lens[#lens + 1] = last end |
| 147 | + elseif sym == 17 then |
| 148 | + local r = bs.readbits(3) + 3 |
| 149 | + for _ = 1, r do lens[#lens + 1] = 0 end |
| 150 | + elseif sym == 18 then |
| 151 | + local r = bs.readbits(7) + 11 |
| 152 | + for _ = 1, r do lens[#lens + 1] = 0 end |
| 153 | + end |
| 154 | + end |
| 155 | + local ll, dl = {}, {} |
| 156 | + for i = 1, hlit do ll[i] = lens[i] end |
| 157 | + for i = hlit + 1, hlit + hdist do dl[i - hlit] = lens[i] end |
| 158 | + litlen = make_huff(ll) |
| 159 | + dist = make_huff(dl) |
| 160 | + end |
| 161 | + |
| 162 | + local len_extra = { |
| 163 | + [257]={3,0},[258]={4,0},[259]={5,0},[260]={6,0},[261]={7,0}, |
| 164 | + [262]={8,0},[263]={9,0},[264]={10,0},[265]={11,1},[266]={13,1}, |
| 165 | + [267]={15,1},[268]={17,1},[269]={19,2},[270]={23,2},[271]={27,2}, |
| 166 | + [272]={31,2},[273]={35,3},[274]={43,3},[275]={51,3},[276]={59,3}, |
| 167 | + [277]={67,4},[278]={83,4},[279]={99,4},[280]={115,4},[281]={131,5}, |
| 168 | + [282]={163,5},[283]={195,5},[284]={227,5},[285]={258,0} |
| 169 | + } |
| 170 | + local dist_extra = { |
| 171 | + {1,0},{2,0},{3,0},{4,0},{5,1},{7,1},{9,2},{13,2},{17,3},{25,3}, |
| 172 | + {33,4},{49,4},{65,5},{97,5},{129,6},{193,6},{257,7},{385,7},{513,8}, |
| 173 | + {769,8},{1025,9},{1537,9},{2049,10},{3073,10},{4097,11},{6145,11}, |
| 174 | + {8193,12},{12289,12},{16385,13},{24577,13} |
| 175 | + } |
| 176 | + |
| 177 | + while true do |
| 178 | + local sym = read_huff(bs, litlen) |
| 179 | + if sym < 256 then |
| 180 | + append_byte(sym) |
| 181 | + elseif sym == 256 then |
| 182 | + break |
| 183 | + else |
| 184 | + local entry = len_extra[sym] |
| 185 | + local base, extra = entry[1], entry[2] |
| 186 | + local add = extra > 0 and bs.readbits(extra) or 0 |
| 187 | + local length = base + add |
| 188 | + local dsym = read_huff(bs, dist) |
| 189 | + local dentry = dist_extra[dsym + 1] |
| 190 | + local dbase, dextra = dentry[1], dentry[2] |
| 191 | + local dadd = dextra > 0 and bs.readbits(dextra) or 0 |
| 192 | + local distval = dbase + dadd |
| 193 | + |
| 194 | + if distval <= 0 or distval > outpos then |
| 195 | + error("invalid distance "..tostring(distval).." (outpos="..tostring(outpos)..")") |
| 196 | + end |
| 197 | + |
| 198 | + local base_index = outpos - distval |
| 199 | + for i = 1, length do |
| 200 | + -- copy byte-by-byte (handles overlaps correctly) |
| 201 | + local bytechar = out[base_index + i] |
| 202 | + append_byte(bytechar:byte()) |
| 203 | + end |
| 204 | + end |
| 205 | + end |
| 206 | + else |
| 207 | + error("unsupported block type: "..tostring(btype)) |
| 208 | + end |
| 209 | + |
| 210 | + if final == 1 then done = true end |
| 211 | + end |
| 212 | + |
| 213 | + return table.concat(out) |
| 214 | +end |
| 215 | + |
| 216 | +local function crc32(s) |
| 217 | + local crc = 0xFFFFFFFF |
| 218 | + for i = 1, #s do |
| 219 | + local b = s:byte(i) |
| 220 | + crc = bxor(crc, b) |
| 221 | + for _ = 1, 8 do |
| 222 | + local mask = -(crc & 1) |
| 223 | + crc = bxor(rshift(crc,1), band(0xEDB88320, mask)) |
| 224 | + end |
| 225 | + end |
| 226 | + return bxor(crc, 0xFFFFFFFF) |
| 227 | +end |
| 228 | + |
| 229 | +local function le16(s, i) local a,b = s:byte(i,i+1); return a + b*256 end |
| 230 | +local function le32(s, i) |
| 231 | + local a,b,c,d = s:byte(i, i+3) |
| 232 | + return a + b*256 + c*65536 + d*16777216 |
| 233 | +end |
| 234 | + |
| 235 | +local function unzip(path) |
| 236 | + local f = assert(io.open(path, "rb")) |
| 237 | + local data = f:read("*a"); f:close() |
| 238 | + local pos = 1 |
| 239 | + while true do |
| 240 | + if data:sub(pos, pos+3) ~= "PK\3\4" then break end |
| 241 | + local version_needed = le16(data, pos+4) |
| 242 | + local flags = le16(data, pos+6) |
| 243 | + local cm = le16(data, pos+8) |
| 244 | + local crc_expected = le32(data, pos+14) |
| 245 | + local comp_size = le32(data, pos+18) |
| 246 | + local uncomp_size = le32(data, pos+22) |
| 247 | + local name_len = le16(data, pos+26) |
| 248 | + local extra_len = le16(data, pos+28) |
| 249 | + local filename = data:sub(pos+30, pos+29 + name_len) |
| 250 | + local start = pos + 30 + name_len + extra_len |
| 251 | + local compdata = data:sub(start, start + comp_size - 1) |
| 252 | + |
| 253 | + local outstr |
| 254 | + if cm == 0 then |
| 255 | + outstr = compdata |
| 256 | + elseif cm == 8 then |
| 257 | + outstr = inflate(compdata) |
| 258 | + else |
| 259 | + io.stderr:write("Unsupported compression method: "..tostring(cm).."\n") |
| 260 | + end |
| 261 | + |
| 262 | + if outstr then |
| 263 | + if #outstr ~= uncomp_size then |
| 264 | + io.stderr:write(string.format("Warning: uncompressed size mismatch for %s: expected %d, got %d\n", filename, uncomp_size, #outstr)) |
| 265 | + end |
| 266 | + local gotcrc = crc32(outstr) |
| 267 | + if crc_expected ~= gotcrc then |
| 268 | + io.stderr:write(string.format("CRC mismatch for %s: expected %08x, got %08x\n", filename, crc_expected, gotcrc)) |
| 269 | + else |
| 270 | + print(string.format("CRC OK: %08x", gotcrc)) |
| 271 | + end |
| 272 | + |
| 273 | + local dir = filename:match("(.+)/") |
| 274 | + if dir then |
| 275 | + if os.execute then |
| 276 | + -- portable make dir (POSIX/Windows-friendly-ish) |
| 277 | + os.execute('mkdir -p "'..dir..'"') |
| 278 | + end |
| 279 | + end |
| 280 | + local of = assert(io.open(filename, "wb")) |
| 281 | + of:write(outstr) |
| 282 | + of:close() |
| 283 | + end |
| 284 | + |
| 285 | + pos = start + comp_size |
| 286 | + end |
| 287 | +end |
| 288 | + |
| 289 | +if not arg[1] then |
| 290 | + io.stderr:write("Usage: lua unzip_fixed.lua <file.zip>\n") |
| 291 | + os.exit(1) |
| 292 | +end |
| 293 | +unzip(arg[1]) |
0 commit comments