Skip to content

Commit 15a3199

Browse files
committed
Initial implementation of UNZIP.LUA script.
1 parent 87bd1c8 commit 15a3199

File tree

1 file changed

+293
-0
lines changed

1 file changed

+293
-0
lines changed

util/UNZIP.LUA

Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
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

Comments
 (0)