From c2e638cbec42558126232590879059d244ff15bb Mon Sep 17 00:00:00 2001
From: Erica Marigold <hi@devcomp.xyz>
Date: Mon, 30 Dec 2024 11:13:57 +0000
Subject: [PATCH] style: apply stylua formatter

---
 examples/tour.luau |  71 ++---
 lib/crc.luau       |  60 ++--
 lib/inflate.luau   | 674 ++++++++++++++++++++--------------------
 lib/init.luau      | 746 +++++++++++++++++++++++----------------------
 pesde.lock         | 131 ++++++++
 pesde.toml         |   3 +
 6 files changed, 911 insertions(+), 774 deletions(-)

diff --git a/examples/tour.luau b/examples/tour.luau
index 6a58cc6..1e592ad 100644
--- a/examples/tour.luau
+++ b/examples/tour.luau
@@ -1,35 +1,36 @@
-local fs = require("@lune/fs")
-local zip = require("../lib")
-
-local file = fs.readFile("test.zip")
-local reader = zip.load(buffer.fromstring(file))
-
-print("Directory structure:")
-reader:walk(function(entry, depth)
-    local prefix = string.rep("  ", depth)
-    local suffix = if not entry.isDirectory then string.format(" (%d bytes)", entry.size) else ""
-    print(prefix .. entry.name .. suffix)
-end)
-
-print("\nContents of `/`:")
-local assets = reader:listDirectory("/")
-for _, entry in assets do
-    print(entry.name, if entry.isDirectory then "DIR" else entry.size)
-    if not entry.isDirectory then
-        local extracted = reader:extract(entry, { isString = true })
-        print("Content:", extracted)
-    end
-end
-
--- Get archive statistics
-local stats = reader:getStats()
-print(string.format([[
-
-Archive stats:
-Files: %d
-Directories: %d
-Total size: %d bytes]],
-    stats.fileCount,
-    stats.dirCount,
-    stats.totalSize
-))
+local fs = require("@lune/fs")
+local zip = require("../lib")
+
+local file = fs.readFile("test.zip")
+local reader = zip.load(buffer.fromstring(file))
+
+print("Directory structure:")
+reader:walk(function(entry, depth)
+	local prefix = string.rep("  ", depth)
+	local suffix = if not entry.isDirectory then string.format(" (%d bytes)", entry.size) else ""
+	print(prefix .. entry.name .. suffix)
+end)
+
+print("\nContents of `/`:")
+local assets = reader:listDirectory("/")
+for _, entry in assets do
+	print(entry.name, if entry.isDirectory then "DIR" else entry.size)
+	if not entry.isDirectory then
+		local extracted = reader:extract(entry, { isString = true })
+		print("Content:", extracted)
+	end
+end
+
+-- Get archive statistics
+local stats = reader:getStats()
+print(string.format(
+	[[
+
+Archive stats:
+Files: %d
+Directories: %d
+Total size: %d bytes]],
+	stats.fileCount,
+	stats.dirCount,
+	stats.totalSize
+))
diff --git a/lib/crc.luau b/lib/crc.luau
index bd9fd79..549c5df 100644
--- a/lib/crc.luau
+++ b/lib/crc.luau
@@ -1,30 +1,30 @@
-local CRC32_TABLE = table.create(256)
-
--- Initialize the lookup table and lock it in place
-for i = 0, 255 do
-    local crc = i
-    for _ = 1, 8 do
-        if bit32.band(crc, 1) == 1 then
-            crc = bit32.bxor(bit32.rshift(crc, 1), 0xEDB88320)
-        else
-            crc = bit32.rshift(crc, 1)
-        end
-    end
-    CRC32_TABLE[i] = crc
-end
-
-table.freeze(CRC32_TABLE)
-
-local function crc32(buf: buffer): number
-    local crc = 0xFFFFFFFF
-    
-    for i = 0, buffer.len(buf) - 1 do
-        local byte = buffer.readu8(buf, i)
-        local index = bit32.band(bit32.bxor(crc, byte), 0xFF)
-        crc = bit32.bxor(bit32.rshift(crc, 8), CRC32_TABLE[index])
-    end
-    
-    return bit32.bxor(crc, 0xFFFFFFFF)
-end
-
-return crc32
+local CRC32_TABLE = table.create(256)
+
+-- Initialize the lookup table and lock it in place
+for i = 0, 255 do
+	local crc = i
+	for _ = 1, 8 do
+		if bit32.band(crc, 1) == 1 then
+			crc = bit32.bxor(bit32.rshift(crc, 1), 0xEDB88320)
+		else
+			crc = bit32.rshift(crc, 1)
+		end
+	end
+	CRC32_TABLE[i] = crc
+end
+
+table.freeze(CRC32_TABLE)
+
+local function crc32(buf: buffer): number
+	local crc = 0xFFFFFFFF
+
+	for i = 0, buffer.len(buf) - 1 do
+		local byte = buffer.readu8(buf, i)
+		local index = bit32.band(bit32.bxor(crc, byte), 0xFF)
+		crc = bit32.bxor(bit32.rshift(crc, 8), CRC32_TABLE[index])
+	end
+
+	return bit32.bxor(crc, 0xFFFFFFFF)
+end
+
+return crc32
diff --git a/lib/inflate.luau b/lib/inflate.luau
index b1c96ee..efba03a 100644
--- a/lib/inflate.luau
+++ b/lib/inflate.luau
@@ -1,61 +1,61 @@
-local Tree = {}
-
-export type Tree = typeof(setmetatable({} :: TreeInner, { __index = Tree }))
-type TreeInner = {
-	table: { number }, -- len: 16 (🏳️‍⚧️❓)
-	trans: { number }, -- len: 288
-}
-
-function Tree.new(): Tree
-	return setmetatable(
-		{
-			table = table.create(16, 0),
-			trans = table.create(288, 0),
-		} :: TreeInner,
-		{ __index = Tree }
-	)
-end
-
-local Data = {}
-export type Data = typeof(setmetatable({} :: DataInner, { __index = Data }))
-export type DataInner = {
-	source: buffer,
-	sourceIndex: number,
-	tag: number,
-	bitcount: number,
-
-	dest: buffer,
-	destLen: number,
-	
-    ltree: Tree,
-	dtree: Tree,
-}
-
-function Data.new(source: buffer, dest: buffer): Data
-	return setmetatable(
-		{
-			source = source,
-			sourceIndex = 0,
-			tag = 0,
-			bitcount = 0,
-			dest = dest,
-			destLen = 0,
-			ltree = Tree.new(),
-			dtree = Tree.new(),
-		} :: DataInner,
-		{ __index = Data }
-	)
-end
-
--- Static structures
-local staticLengthTree = Tree.new()
-local staticDistTree = Tree.new()
-
--- Extra bits and base tables
-local lengthBits = table.create(30, 0)
-local lengthBase = table.create(30, 0)
-local distBits = table.create(30, 0)
-local distBase = table.create(30, 0)
+local Tree = {}
+
+export type Tree = typeof(setmetatable({} :: TreeInner, { __index = Tree }))
+type TreeInner = {
+	table: { number }, -- len: 16 (🏳️‍⚧️❓)
+	trans: { number }, -- len: 288
+}
+
+function Tree.new(): Tree
+	return setmetatable(
+		{
+			table = table.create(16, 0),
+			trans = table.create(288, 0),
+		} :: TreeInner,
+		{ __index = Tree }
+	)
+end
+
+local Data = {}
+export type Data = typeof(setmetatable({} :: DataInner, { __index = Data }))
+export type DataInner = {
+	source: buffer,
+	sourceIndex: number,
+	tag: number,
+	bitcount: number,
+
+	dest: buffer,
+	destLen: number,
+
+	ltree: Tree,
+	dtree: Tree,
+}
+
+function Data.new(source: buffer, dest: buffer): Data
+	return setmetatable(
+		{
+			source = source,
+			sourceIndex = 0,
+			tag = 0,
+			bitcount = 0,
+			dest = dest,
+			destLen = 0,
+			ltree = Tree.new(),
+			dtree = Tree.new(),
+		} :: DataInner,
+		{ __index = Data }
+	)
+end
+
+-- Static structures
+local staticLengthTree = Tree.new()
+local staticDistTree = Tree.new()
+
+-- Extra bits and base tables
+local lengthBits = table.create(30, 0)
+local lengthBase = table.create(30, 0)
+local distBits = table.create(30, 0)
+local distBase = table.create(30, 0)
 
 -- Special ordering of code length codes
 -- stylua: ignore
@@ -64,282 +64,282 @@ local clcIndex = {
     10, 5, 11, 4, 12, 3, 13, 2,
     14, 1, 15
 }
-
-local codeTree = Tree.new()
-local lengths = table.create(288 + 32, 0)
-
-local function buildBitsBase(bits: { number }, base: { number }, delta: number, first: number)
-	local sum = first
-
-	-- build bits table
-	for i = 0, delta - 1 do
-		bits[i] = 0
-	end
-	for i = 0, 29 - delta do
-		bits[i + delta] = math.floor(i / delta)
-	end
-
-	-- build base table
-	for i = 0, 29 do
-		base[i] = sum
-		sum = sum + bit32.lshift(1, bits[i])
-	end
-end
-
-local function buildFixedTrees(lengthTree: Tree, distTree: Tree)
-	-- build fixed length tree
-	for i = 0, 6 do
-		lengthTree.table[i] = 0
-	end
-	lengthTree.table[7] = 24
-	lengthTree.table[8] = 152
-	lengthTree.table[9] = 112
-
-	for i = 0, 23 do
-		lengthTree.trans[i] = 256 + i
-	end
-	for i = 0, 143 do
-		lengthTree.trans[24 + i] = i
-	end
-	for i = 0, 7 do
-		lengthTree.trans[24 + 144 + i] = 280 + i
-	end
-	for i = 0, 111 do
-		lengthTree.trans[24 + 144 + 8 + i] = 144 + i
-	end
-
-	-- build fixed distance tree
-	for i = 0, 4 do
-		distTree.table[i] = 0
-	end
-	distTree.table[5] = 32
-
-	for i = 0, 31 do
-		distTree.trans[i] = i
-	end
-end
-
-local offs = table.create(16, 0)
-
-local function buildTree(t: Tree, lengths: { number }, off: number, num: number)
-	-- clear code length count table
-	for i = 0, 15 do
-		t.table[i] = 0
-	end
-
-	-- scan symbol lengths, and sum code length counts
-	for i = 0, num - 1 do
-		t.table[lengths[off + i]] += 1
-	end
-
-	t.table[0] = 0
-
-	-- compute offset table for distribution sort
-	local sum = 0
-	for i = 0, 15 do
-		offs[i] = sum
-		sum = sum + t.table[i]
-	end
-
-	-- create code->symbol translation table
-	for i = 0, num - 1 do
-		local len = lengths[off + i]
-		if len > 0 then
-			t.trans[offs[len]] = i
-			offs[len] += 1
-		end
-	end
-end
-
-local function getBit(d: Data): number
-	if d.bitcount <= 0 then
-		d.tag = buffer.readu8(d.source, d.sourceIndex)
-		d.sourceIndex += 1
-		d.bitcount = 8
-	end
-
-	local bit = bit32.band(d.tag, 1)
-	d.tag = bit32.rshift(d.tag, 1)
-	d.bitcount -= 1
-
-	return bit
-end
-
-local function readBits(d: Data, num: number?, base: number): number
-	if not num then
-		return base
-	end
-
-	while d.bitcount < 24 and d.sourceIndex < buffer.len(d.source) do
-		d.tag = bit32.bor(d.tag, bit32.lshift(buffer.readu8(d.source, d.sourceIndex), d.bitcount))
-		d.sourceIndex += 1
-		d.bitcount += 8
-	end
-
-	local val = bit32.band(d.tag, bit32.rshift(0xffff, 16 - num))
-	d.tag = bit32.rshift(d.tag, num)
-	d.bitcount -= num
-
-	return val + base
-end
-
-local function decodeSymbol(d: Data, t: Tree): number
-	while d.bitcount < 24 and d.sourceIndex < buffer.len(d.source) do
-		d.tag = bit32.bor(d.tag, bit32.lshift(buffer.readu8(d.source, d.sourceIndex), d.bitcount))
-		d.sourceIndex += 1
-		d.bitcount += 8
-	end
-
-	local sum, cur, len = 0, 0, 0
-	local tag = d.tag
-
-	repeat
-		cur = 2 * cur + bit32.band(tag, 1)
-		tag = bit32.rshift(tag, 1)
-		len += 1
-		sum += t.table[len]
-		cur -= t.table[len]
-	until cur < 0
-
-	d.tag = tag
-	d.bitcount -= len
-
-	return t.trans[sum + cur]
-end
-
-local function decodeTrees(d: Data, lengthTree: Tree, distTree: Tree)
-	local hlit = readBits(d, 5, 257)
-	local hdist = readBits(d, 5, 1)
-	local hclen = readBits(d, 4, 4)
-
-	for i = 0, 18 do
-		lengths[i] = 0
-	end
-
-	for i = 0, hclen - 1 do
-		lengths[clcIndex[i + 1]] = readBits(d, 3, 0)
-	end
-
-	buildTree(codeTree, lengths, 0, 19)
-
-	local num = 0
-	while num < hlit + hdist do
-		local sym = decodeSymbol(d, codeTree)
-
-		if sym == 16 then
-			local prev = lengths[num - 1]
-			for _ = 1, readBits(d, 2, 3) do
-				lengths[num] = prev
-				num += 1
-			end
-		elseif sym == 17 then
-			for _ = 1, readBits(d, 3, 3) do
-				lengths[num] = 0
-				num += 1
-			end
-		elseif sym == 18 then
-			for _ = 1, readBits(d, 7, 11) do
-				lengths[num] = 0
-				num += 1
-			end
-		else
-			lengths[num] = sym
-			num += 1
-		end
-	end
-
-	buildTree(lengthTree, lengths, 0, hlit)
-	buildTree(distTree, lengths, hlit, hdist)
-end
-
-local function inflateBlockData(d: Data, lengthTree: Tree, distTree: Tree)
-	while true do
-		local sym = decodeSymbol(d, lengthTree)
-
-		if sym == 256 then
-			return
-		end
-
-		if sym < 256 then
-			buffer.writeu8(d.dest, d.destLen, sym)
-			d.destLen += 1
-		else
-			sym -= 257
-
-			local length = readBits(d, lengthBits[sym], lengthBase[sym])
-			local dist = decodeSymbol(d, distTree)
-
-			local offs = d.destLen - readBits(d, distBits[dist], distBase[dist])
-
-			for i = offs, offs + length - 1 do
-				buffer.writeu8(d.dest, d.destLen, buffer.readu8(d.dest, i))
-				d.destLen += 1
-			end
-		end
-	end
-end
-
-local function inflateUncompressedBlock(d: Data)
-	while d.bitcount > 8 do
-		d.sourceIndex -= 1
-		d.bitcount -= 8
-	end
-
-	local length = buffer.readu8(d.source, d.sourceIndex + 1)
-	length = 256 * length + buffer.readu8(d.source, d.sourceIndex)
-
-	local invlength = buffer.readu8(d.source, d.sourceIndex + 3)
-	invlength = 256 * invlength + buffer.readu8(d.source, d.sourceIndex + 2)
-
-	if length ~= bit32.bxor(invlength, 0xffff) then
-		error("Invalid block length")
-	end
-
-	d.sourceIndex += 4
-
-	for _ = 1, length do
-		buffer.writeu8(d.dest, d.destLen, buffer.readu8(d.source, d.sourceIndex))
-		d.destLen += 1
-		d.sourceIndex += 1
-	end
-
-	d.bitcount = 0
-end
-
-local function uncompress(source: buffer): buffer
-	local dest = buffer.create(buffer.len(source) * 4)
-	local d = Data.new(source, dest)
-
-	repeat
-		local bfinal = getBit(d)
-		local btype = readBits(d, 2, 0)
-
-		if btype == 0 then
-			inflateUncompressedBlock(d)
-		elseif btype == 1 then
-			inflateBlockData(d, staticLengthTree, staticDistTree)
-		elseif btype == 2 then
-			decodeTrees(d, d.ltree, d.dtree)
-			inflateBlockData(d, d.ltree, d.dtree)
-		else
-			error("Invalid block type")
-		end
-	until bfinal == 1
-
-	if d.destLen < buffer.len(dest) then
-		local result = buffer.create(d.destLen)
-		buffer.copy(result, 0, dest, 0, d.destLen)
-		return result
-	end
-
-	return dest
-end
-
--- Initialize static trees and tables
-buildFixedTrees(staticLengthTree, staticDistTree)
-buildBitsBase(lengthBits, lengthBase, 4, 3)
-buildBitsBase(distBits, distBase, 2, 1)
-lengthBits[28] = 0
-lengthBase[28] = 258
-
-return uncompress
+
+local codeTree = Tree.new()
+local lengths = table.create(288 + 32, 0)
+
+local function buildBitsBase(bits: { number }, base: { number }, delta: number, first: number)
+	local sum = first
+
+	-- build bits table
+	for i = 0, delta - 1 do
+		bits[i] = 0
+	end
+	for i = 0, 29 - delta do
+		bits[i + delta] = math.floor(i / delta)
+	end
+
+	-- build base table
+	for i = 0, 29 do
+		base[i] = sum
+		sum = sum + bit32.lshift(1, bits[i])
+	end
+end
+
+local function buildFixedTrees(lengthTree: Tree, distTree: Tree)
+	-- build fixed length tree
+	for i = 0, 6 do
+		lengthTree.table[i] = 0
+	end
+	lengthTree.table[7] = 24
+	lengthTree.table[8] = 152
+	lengthTree.table[9] = 112
+
+	for i = 0, 23 do
+		lengthTree.trans[i] = 256 + i
+	end
+	for i = 0, 143 do
+		lengthTree.trans[24 + i] = i
+	end
+	for i = 0, 7 do
+		lengthTree.trans[24 + 144 + i] = 280 + i
+	end
+	for i = 0, 111 do
+		lengthTree.trans[24 + 144 + 8 + i] = 144 + i
+	end
+
+	-- build fixed distance tree
+	for i = 0, 4 do
+		distTree.table[i] = 0
+	end
+	distTree.table[5] = 32
+
+	for i = 0, 31 do
+		distTree.trans[i] = i
+	end
+end
+
+local offs = table.create(16, 0)
+
+local function buildTree(t: Tree, lengths: { number }, off: number, num: number)
+	-- clear code length count table
+	for i = 0, 15 do
+		t.table[i] = 0
+	end
+
+	-- scan symbol lengths, and sum code length counts
+	for i = 0, num - 1 do
+		t.table[lengths[off + i]] += 1
+	end
+
+	t.table[0] = 0
+
+	-- compute offset table for distribution sort
+	local sum = 0
+	for i = 0, 15 do
+		offs[i] = sum
+		sum = sum + t.table[i]
+	end
+
+	-- create code->symbol translation table
+	for i = 0, num - 1 do
+		local len = lengths[off + i]
+		if len > 0 then
+			t.trans[offs[len]] = i
+			offs[len] += 1
+		end
+	end
+end
+
+local function getBit(d: Data): number
+	if d.bitcount <= 0 then
+		d.tag = buffer.readu8(d.source, d.sourceIndex)
+		d.sourceIndex += 1
+		d.bitcount = 8
+	end
+
+	local bit = bit32.band(d.tag, 1)
+	d.tag = bit32.rshift(d.tag, 1)
+	d.bitcount -= 1
+
+	return bit
+end
+
+local function readBits(d: Data, num: number?, base: number): number
+	if not num then
+		return base
+	end
+
+	while d.bitcount < 24 and d.sourceIndex < buffer.len(d.source) do
+		d.tag = bit32.bor(d.tag, bit32.lshift(buffer.readu8(d.source, d.sourceIndex), d.bitcount))
+		d.sourceIndex += 1
+		d.bitcount += 8
+	end
+
+	local val = bit32.band(d.tag, bit32.rshift(0xffff, 16 - num))
+	d.tag = bit32.rshift(d.tag, num)
+	d.bitcount -= num
+
+	return val + base
+end
+
+local function decodeSymbol(d: Data, t: Tree): number
+	while d.bitcount < 24 and d.sourceIndex < buffer.len(d.source) do
+		d.tag = bit32.bor(d.tag, bit32.lshift(buffer.readu8(d.source, d.sourceIndex), d.bitcount))
+		d.sourceIndex += 1
+		d.bitcount += 8
+	end
+
+	local sum, cur, len = 0, 0, 0
+	local tag = d.tag
+
+	repeat
+		cur = 2 * cur + bit32.band(tag, 1)
+		tag = bit32.rshift(tag, 1)
+		len += 1
+		sum += t.table[len]
+		cur -= t.table[len]
+	until cur < 0
+
+	d.tag = tag
+	d.bitcount -= len
+
+	return t.trans[sum + cur]
+end
+
+local function decodeTrees(d: Data, lengthTree: Tree, distTree: Tree)
+	local hlit = readBits(d, 5, 257)
+	local hdist = readBits(d, 5, 1)
+	local hclen = readBits(d, 4, 4)
+
+	for i = 0, 18 do
+		lengths[i] = 0
+	end
+
+	for i = 0, hclen - 1 do
+		lengths[clcIndex[i + 1]] = readBits(d, 3, 0)
+	end
+
+	buildTree(codeTree, lengths, 0, 19)
+
+	local num = 0
+	while num < hlit + hdist do
+		local sym = decodeSymbol(d, codeTree)
+
+		if sym == 16 then
+			local prev = lengths[num - 1]
+			for _ = 1, readBits(d, 2, 3) do
+				lengths[num] = prev
+				num += 1
+			end
+		elseif sym == 17 then
+			for _ = 1, readBits(d, 3, 3) do
+				lengths[num] = 0
+				num += 1
+			end
+		elseif sym == 18 then
+			for _ = 1, readBits(d, 7, 11) do
+				lengths[num] = 0
+				num += 1
+			end
+		else
+			lengths[num] = sym
+			num += 1
+		end
+	end
+
+	buildTree(lengthTree, lengths, 0, hlit)
+	buildTree(distTree, lengths, hlit, hdist)
+end
+
+local function inflateBlockData(d: Data, lengthTree: Tree, distTree: Tree)
+	while true do
+		local sym = decodeSymbol(d, lengthTree)
+
+		if sym == 256 then
+			return
+		end
+
+		if sym < 256 then
+			buffer.writeu8(d.dest, d.destLen, sym)
+			d.destLen += 1
+		else
+			sym -= 257
+
+			local length = readBits(d, lengthBits[sym], lengthBase[sym])
+			local dist = decodeSymbol(d, distTree)
+
+			local offs = d.destLen - readBits(d, distBits[dist], distBase[dist])
+
+			for i = offs, offs + length - 1 do
+				buffer.writeu8(d.dest, d.destLen, buffer.readu8(d.dest, i))
+				d.destLen += 1
+			end
+		end
+	end
+end
+
+local function inflateUncompressedBlock(d: Data)
+	while d.bitcount > 8 do
+		d.sourceIndex -= 1
+		d.bitcount -= 8
+	end
+
+	local length = buffer.readu8(d.source, d.sourceIndex + 1)
+	length = 256 * length + buffer.readu8(d.source, d.sourceIndex)
+
+	local invlength = buffer.readu8(d.source, d.sourceIndex + 3)
+	invlength = 256 * invlength + buffer.readu8(d.source, d.sourceIndex + 2)
+
+	if length ~= bit32.bxor(invlength, 0xffff) then
+		error("Invalid block length")
+	end
+
+	d.sourceIndex += 4
+
+	for _ = 1, length do
+		buffer.writeu8(d.dest, d.destLen, buffer.readu8(d.source, d.sourceIndex))
+		d.destLen += 1
+		d.sourceIndex += 1
+	end
+
+	d.bitcount = 0
+end
+
+local function uncompress(source: buffer): buffer
+	local dest = buffer.create(buffer.len(source) * 4)
+	local d = Data.new(source, dest)
+
+	repeat
+		local bfinal = getBit(d)
+		local btype = readBits(d, 2, 0)
+
+		if btype == 0 then
+			inflateUncompressedBlock(d)
+		elseif btype == 1 then
+			inflateBlockData(d, staticLengthTree, staticDistTree)
+		elseif btype == 2 then
+			decodeTrees(d, d.ltree, d.dtree)
+			inflateBlockData(d, d.ltree, d.dtree)
+		else
+			error("Invalid block type")
+		end
+	until bfinal == 1
+
+	if d.destLen < buffer.len(dest) then
+		local result = buffer.create(d.destLen)
+		buffer.copy(result, 0, dest, 0, d.destLen)
+		return result
+	end
+
+	return dest
+end
+
+-- Initialize static trees and tables
+buildFixedTrees(staticLengthTree, staticDistTree)
+buildBitsBase(lengthBits, lengthBase, 4, 3)
+buildBitsBase(distBits, distBase, 2, 1)
+lengthBits[28] = 0
+lengthBase[28] = 258
+
+return uncompress
diff --git a/lib/init.luau b/lib/init.luau
index 6350671..13ffcf9 100644
--- a/lib/init.luau
+++ b/lib/init.luau
@@ -1,51 +1,51 @@
-local inflate = require("./inflate")
-local crc32 = require("./crc")
-
--- Little endian constant signatures used in the ZIP file format
-local SIGNATURES = table.freeze({
-	-- Marks the beginning of each file in the ZIP
-	LOCAL_FILE = 0x04034b50,
-	-- Marks entries in the central directory
-	CENTRAL_DIR = 0x02014b50,
-	-- Marks the end of the central directory
-	END_OF_CENTRAL_DIR = 0x06054b50,
-})
-
-type CrcValidationOptions = {
-	skip: boolean,
-	expected: number,
-}
-
-local function validateCrc(decompressed: buffer, validation: CrcValidationOptions)
-	-- Unless skipping validation is requested, we verify the checksum
-	if validation.skip then
-		local computed = crc32(decompressed)
-		assert(
-			validation.expected == computed,
-			`Validation failed; CRC checksum does not match: {string.format("%x", computed)} ~= {string.format(
-				"%x",
-				computed
-			)} (expected ~= got)`
-		)
-	end
-end
-
-local DECOMPRESSION_ROUTINES: { [number]: (buffer, validation: CrcValidationOptions) -> buffer } = table.freeze({
-	[0x00] = function(buf, validation)
-		validateCrc(buf, validation)
-		return buf
-	end,
-	[0x08] = function(buf, validation)
-		local decompressed = inflate(buf)
-		validateCrc(decompressed, validation)
-		return decompressed
-	end,
-})
-
--- TODO: ERROR HANDLING!
-
-local ZipEntry = {}
-export type ZipEntry = typeof(setmetatable({} :: ZipEntryInner, { __index = ZipEntry }))
+local inflate = require("./inflate")
+local crc32 = require("./crc")
+
+-- Little endian constant signatures used in the ZIP file format
+local SIGNATURES = table.freeze({
+	-- Marks the beginning of each file in the ZIP
+	LOCAL_FILE = 0x04034b50,
+	-- Marks entries in the central directory
+	CENTRAL_DIR = 0x02014b50,
+	-- Marks the end of the central directory
+	END_OF_CENTRAL_DIR = 0x06054b50,
+})
+
+type CrcValidationOptions = {
+	skip: boolean,
+	expected: number,
+}
+
+local function validateCrc(decompressed: buffer, validation: CrcValidationOptions)
+	-- Unless skipping validation is requested, we verify the checksum
+	if validation.skip then
+		local computed = crc32(decompressed)
+		assert(
+			validation.expected == computed,
+			`Validation failed; CRC checksum does not match: {string.format("%x", computed)} ~= {string.format(
+				"%x",
+				computed
+			)} (expected ~= got)`
+		)
+	end
+end
+
+local DECOMPRESSION_ROUTINES: { [number]: (buffer, validation: CrcValidationOptions) -> buffer } = table.freeze({
+	[0x00] = function(buf, validation)
+		validateCrc(buf, validation)
+		return buf
+	end,
+	[0x08] = function(buf, validation)
+		local decompressed = inflate(buf)
+		validateCrc(decompressed, validation)
+		return decompressed
+	end,
+})
+
+-- TODO: ERROR HANDLING!
+
+local ZipEntry = {}
+export type ZipEntry = typeof(setmetatable({} :: ZipEntryInner, { __index = ZipEntry }))
 -- stylua: ignore
 type ZipEntryInner = {
     name: string,           -- File path within ZIP, '/' suffix indicates directory
@@ -57,37 +57,37 @@ type ZipEntryInner = {
     parent: ZipEntry?,      -- The parent of the current entry, nil for root
     children: { ZipEntry }, -- The children of the entry
 }
-
-function ZipEntry.new(name: string, size: number, offset: number, timestamp: number, crc: number): ZipEntry
-	return setmetatable(
-		{
-			name = name,
-			size = size,
-			offset = offset,
-			timestamp = timestamp,
-			crc = crc,
-			isDirectory = string.sub(name, -1) == "/",
-			parent = nil,
-			children = {},
-		} :: ZipEntryInner,
-		{ __index = ZipEntry }
-	)
-end
-
-function ZipEntry.getPath(self: ZipEntry): string
-	local path = self.name
-	local current = self.parent
-
-	while current and current.name ~= "/" do
-		path = current.name .. path
-		current = current.parent
-	end
-
-	return path
-end
-
-local ZipReader = {}
-export type ZipReader = typeof(setmetatable({} :: ZipReaderInner, { __index = ZipReader }))
+
+function ZipEntry.new(name: string, size: number, offset: number, timestamp: number, crc: number): ZipEntry
+	return setmetatable(
+		{
+			name = name,
+			size = size,
+			offset = offset,
+			timestamp = timestamp,
+			crc = crc,
+			isDirectory = string.sub(name, -1) == "/",
+			parent = nil,
+			children = {},
+		} :: ZipEntryInner,
+		{ __index = ZipEntry }
+	)
+end
+
+function ZipEntry.getPath(self: ZipEntry): string
+	local path = self.name
+	local current = self.parent
+
+	while current and current.name ~= "/" do
+		path = current.name .. path
+		current = current.parent
+	end
+
+	return path
+end
+
+local ZipReader = {}
+export type ZipReader = typeof(setmetatable({} :: ZipReaderInner, { __index = ZipReader }))
 -- stylua: ignore
 type ZipReaderInner = {
 	data: buffer,                         -- The buffer containing the raw bytes of the ZIP
@@ -95,296 +95,298 @@ type ZipReaderInner = {
 	directories: { [string]: ZipEntry },  -- The directories and their respective entries 
 	root: ZipEntry,                       -- The entry of the root directory
 }
-
-function ZipReader.new(data): ZipReader
-	local root = ZipEntry.new("/", 0, 0, 0, 0)
-	root.isDirectory = true
-
-	local this = setmetatable(
-		{
-			data = data,
-			entries = {},
-			directories = {},
-			root = root,
-		} :: ZipReaderInner,
-		{ __index = ZipReader }
-	)
-
-	this:parseCentralDirectory()
-	this:buildDirectoryTree()
-	return this
-end
-
-function ZipReader.parseCentralDirectory(self: ZipReader): ()
-	-- ZIP files are read from the end, starting with the End of Central Directory record
-	-- The EoCD is at least 22 bytes and contains pointers to the rest of the ZIP structure
-	local bufSize = buffer.len(self.data)
-	local pos = bufSize - 22
-
-	-- Search backwards for the EoCD signature
-	while pos > 0 do
-		-- Read 4 bytes as uint32 in little-endian format
-		if buffer.readu32(self.data, pos) == SIGNATURES.END_OF_CENTRAL_DIR then
-			break
-		end
-		pos = pos - 1
-	end
-
-	-- Central Directory offset is stored 16 bytes into the EoCD record
-	local cdOffset = buffer.readu32(self.data, pos + 16)
-	-- Number of entries is stored 10 bytes into the EoCD record
-	local cdEntries = buffer.readu16(self.data, pos + 10)
-
-	-- Process each entry in the Central Directory
-	pos = cdOffset
-	for i = 1, cdEntries do
-		-- Central Directory Entry format:
-		-- Offset  Bytes  Description
-		-- ------------------------------------------------
-		-- 0       4      Central directory entry signature
-		-- 28      2      File name length (n)
-		-- 30      2      Extra field length (m)
-		-- 32      2      Comment length (k)
-		-- 12      4      Last mod time/date
-		-- 16      4      CRC-32
-		-- 24      4      Uncompressed size
-		-- 42      4      Local header offset
-		-- 46      n      File name
-		-- 46+n    m      Extra field
-		-- 46+n+m  k      Comment
-
-		local nameLength = buffer.readu16(self.data, pos + 28)
-		local extraLength = buffer.readu16(self.data, pos + 30)
-		local commentLength = buffer.readu16(self.data, pos + 32)
-		local timestamp = buffer.readu32(self.data, pos + 12)
-		local crc = buffer.readu32(self.data, pos + 16)
-		local size = buffer.readu32(self.data, pos + 24)
-		local offset = buffer.readu32(self.data, pos + 42)
-
-		local nameBuffer = buffer.create(nameLength)
-		buffer.copy(nameBuffer, 0, self.data, pos + 46, nameLength)
-		local name = buffer.tostring(nameBuffer)
-
-		local entry = ZipEntry.new(name, size, offset, timestamp, crc)
-		table.insert(self.entries, entry)
-
-		pos = pos + 46 + nameLength + extraLength + commentLength
-	end
-end
-
-function ZipReader.buildDirectoryTree(self: ZipReader): ()
-	for _, entry in self.entries do
-		local parts = {}
-		-- Split entry path into individual components
-		-- e.g. "folder/subfolder/file.txt" -> {"folder", "subfolder", "file.txt"}
-		for part in string.gmatch(entry.name, "([^/]+)/?") do
-			table.insert(parts, part)
-		end
-
-		-- Start from root directory
-		local current = self.root
-		local path = ""
-
-		-- Process each path component
-		for i, part in parts do
-			path ..= part
-			if i < #parts then
-				-- Create missing directory entries for intermediate paths
-				if not self.directories[path] then
-					local dir = ZipEntry.new(path, 0, 0, entry.timestamp, 0)
-					dir.isDirectory = true
-					dir.parent = current
-
-					-- Track directory in both lookup table and parent's children
-					self.directories[path] = dir
-					table.insert(current.children, dir)
-				end
-
-				-- Move deeper into the tree
-				current = self.directories[path]
-				continue
-			end
-
-			-- Link file entry to its parent directory
-			entry.parent = current
-			table.insert(current.children, entry)
-		end
-	end
-end
-
-function ZipReader.findEntry(self: ZipReader, path: string): ZipEntry
-	if path == "/" then
-		-- If the root directory's entry was requested we do not
-		-- need to do any additional work
-		return self.root
-	end
-
-	-- Normalize path by removing leading and trailing slashes
-	-- This ensures consistent lookup regardless of input format
-	-- e.g., "/folder/file.txt/" -> "folder/file.txt"
-	path = string.gsub(path, "^/", ""):gsub("/$", "")
-
-	-- First check regular files and explicit directories
-	for _, entry in self.entries do
-		-- Compare normalized paths
-		if string.gsub(entry.name, "/$", "") == path then
-			return entry
-		end
-	end
-
-	-- If not found, check virtual directory entries
-	-- These are directories that were created implicitly
-	return self.directories[path]
-end
-
-type ExtractionOptions = {
-	decompress: boolean?,
-	isString: boolean?,
-	skipValidation: boolean?,
-}
-function ZipReader.extract(self: ZipReader, entry: ZipEntry, options: ExtractionOptions?): buffer | string
-	-- Local File Header format:
-	-- Offset  Bytes  Description
-	-- 0       4      Local file header signature
-	-- 8       2      Compression method (8 = DEFLATE)
-	-- 14      4      CRC32 checksume
-	-- 18      4      Compressed size
-	-- 22      4      Uncompressed size
-	-- 26      2      File name length (n)
-	-- 28      2      Extra field length (m)
-	-- 30      n      File name
-	-- 30+n    m      Extra field
-	-- 30+n+m  -      File data
-
-	if entry.isDirectory then
-		error("Cannot extract directory")
-	end
-
-	local defaultOptions: ExtractionOptions = {
-		decompress = true,
-		isString = false,
-		skipValidation = false,
-	}
-
-	-- TODO: Use a `Partial` type function for this in the future! 
-	local optionsOrDefault: {
-		decompress: boolean,
-		isString: boolean,
-		skipValidation: boolean
-	} = if options then setmetatable(options, { __index = defaultOptions }) :: any else defaultOptions
-
-	local pos = entry.offset
-	if buffer.readu32(self.data, pos) ~= SIGNATURES.LOCAL_FILE then
-		error("Invalid local file header")
-	end
-
-	local crcChecksum = buffer.readu32(self.data, pos + 14)
-	local compressedSize = buffer.readu32(self.data, pos + 18)
-	local uncompressedSize = buffer.readu32(self.data, pos + 22)
-	local nameLength = buffer.readu16(self.data, pos + 26)
-	local extraLength = buffer.readu16(self.data, pos + 28)
-
-	pos = pos + 30 + nameLength + extraLength
-
-	local content = buffer.create(compressedSize)
-	buffer.copy(content, 0, self.data, pos, compressedSize)
-
-	if optionsOrDefault.decompress then
-		local compressionMethod = buffer.readu16(self.data, entry.offset + 8)
-		local decompress = DECOMPRESSION_ROUTINES[compressionMethod]
-		if decompress == nil then
-			error(`Unsupported compression, ID: {compressionMethod}`)
-		end
-
-		content = decompress(content, {
-			expected = crcChecksum,
-			skip = optionsOrDefault.skipValidation,
-		})
-
-		-- Unless skipping validation is requested, we make sure the uncompressed size matches
-		assert(
-			optionsOrDefault.skipValidation or uncompressedSize == buffer.len(content),
-			"Validation failed; uncompressed size does not match"
-		)
-	end
-
-	return if optionsOrDefault.isString then buffer.tostring(content) else content
-end
-
-function ZipReader.extractDirectory(
-	self: ZipReader,
-	path: string,
-	options: ExtractionOptions
-): { [string]: buffer } | { [string]: string }
-	local files: { [string]: buffer } | { [string]: string } = {}
-	-- Normalize path by removing leading slash for consistent prefix matching
-	path = string.gsub(path, "^/", "")
-
-	-- Iterate through all entries to find files within target directory
-	for _, entry in self.entries do
-		-- Check if entry is a file (not directory) and its path starts with target directory
-		if not entry.isDirectory and string.sub(entry.name, 1, #path) == path then
-			-- Store extracted content mapped to full path
-			files[entry.name] = self:extract(entry, options)
-		end
-	end
-
-	-- Return a map of file to contents
-	return files
-end
-
-function ZipReader.listDirectory(self: ZipReader, path: string): { ZipEntry }
-	-- Locate the entry with the path
-	local entry = self:findEntry(path)
-	if not entry or not entry.isDirectory then
-		-- If an entry was not found, we error
-		error("Not a directory")
-	end
-
-	-- Return the children of our discovered entry
-	return entry.children
-end
-
-function ZipReader.walk(self: ZipReader, callback: (entry: ZipEntry, depth: number) -> ()): ()
-	-- Wrapper function which recursively calls callback for every child
-	-- in an entry
-	local function walkEntry(entry: ZipEntry, depth: number)
-		callback(entry, depth)
-
-		for _, child in entry.children do
-			-- ooo spooky recursion... blame this if shit go wrong
-			walkEntry(child, depth + 1)
-		end
-	end
-
-	walkEntry(self.root, 0)
-end
-
-export type ZipStatistics = { fileCount: number, dirCount: number, totalSize: number }
-function ZipReader.getStats(self: ZipReader): ZipStatistics
-	local stats: ZipStatistics = {
-		fileCount = 0,
-		dirCount = 0,
-		totalSize = 0,
-	}
-
-	-- Iterate through the entries, updating stats
-	for _, entry in self.entries do
-		if entry.isDirectory then
-			stats.dirCount = stats.dirCount + 1
-			continue
-		end
-
-		stats.fileCount = stats.fileCount + 1
-		stats.totalSize = stats.totalSize + entry.size
-	end
-
-	return stats
-end
-
-return {
-	-- Creates a `ZipReader` from a `buffer` of ZIP data.
-	load = function(data: buffer)
-		return ZipReader.new(data)
-	end,
-}
+
+function ZipReader.new(data): ZipReader
+	local root = ZipEntry.new("/", 0, 0, 0, 0)
+	root.isDirectory = true
+
+	local this = setmetatable(
+		{
+			data = data,
+			entries = {},
+			directories = {},
+			root = root,
+		} :: ZipReaderInner,
+		{ __index = ZipReader }
+	)
+
+	this:parseCentralDirectory()
+	this:buildDirectoryTree()
+	return this
+end
+
+function ZipReader.parseCentralDirectory(self: ZipReader): ()
+	-- ZIP files are read from the end, starting with the End of Central Directory record
+	-- The EoCD is at least 22 bytes and contains pointers to the rest of the ZIP structure
+	local bufSize = buffer.len(self.data)
+	local pos = bufSize - 22
+
+	-- Search backwards for the EoCD signature
+	while pos > 0 do
+		-- Read 4 bytes as uint32 in little-endian format
+		if buffer.readu32(self.data, pos) == SIGNATURES.END_OF_CENTRAL_DIR then
+			break
+		end
+		pos = pos - 1
+	end
+
+	-- Central Directory offset is stored 16 bytes into the EoCD record
+	local cdOffset = buffer.readu32(self.data, pos + 16)
+	-- Number of entries is stored 10 bytes into the EoCD record
+	local cdEntries = buffer.readu16(self.data, pos + 10)
+
+	-- Process each entry in the Central Directory
+	pos = cdOffset
+	for i = 1, cdEntries do
+		-- Central Directory Entry format:
+		-- Offset  Bytes  Description
+		-- ------------------------------------------------
+		-- 0       4      Central directory entry signature
+		-- 28      2      File name length (n)
+		-- 30      2      Extra field length (m)
+		-- 32      2      Comment length (k)
+		-- 12      4      Last mod time/date
+		-- 16      4      CRC-32
+		-- 24      4      Uncompressed size
+		-- 42      4      Local header offset
+		-- 46      n      File name
+		-- 46+n    m      Extra field
+		-- 46+n+m  k      Comment
+
+		local nameLength = buffer.readu16(self.data, pos + 28)
+		local extraLength = buffer.readu16(self.data, pos + 30)
+		local commentLength = buffer.readu16(self.data, pos + 32)
+		local timestamp = buffer.readu32(self.data, pos + 12)
+		local crc = buffer.readu32(self.data, pos + 16)
+		local size = buffer.readu32(self.data, pos + 24)
+		local offset = buffer.readu32(self.data, pos + 42)
+
+		local nameBuffer = buffer.create(nameLength)
+		buffer.copy(nameBuffer, 0, self.data, pos + 46, nameLength)
+		local name = buffer.tostring(nameBuffer)
+
+		local entry = ZipEntry.new(name, size, offset, timestamp, crc)
+		table.insert(self.entries, entry)
+
+		pos = pos + 46 + nameLength + extraLength + commentLength
+	end
+end
+
+function ZipReader.buildDirectoryTree(self: ZipReader): ()
+	for _, entry in self.entries do
+		local parts = {}
+		-- Split entry path into individual components
+		-- e.g. "folder/subfolder/file.txt" -> {"folder", "subfolder", "file.txt"}
+		for part in string.gmatch(entry.name, "([^/]+)/?") do
+			table.insert(parts, part)
+		end
+
+		-- Start from root directory
+		local current = self.root
+		local path = ""
+
+		-- Process each path component
+		for i, part in parts do
+			path ..= part
+			if i < #parts then
+				-- Create missing directory entries for intermediate paths
+				if not self.directories[path] then
+					local dir = ZipEntry.new(path, 0, 0, entry.timestamp, 0)
+					dir.isDirectory = true
+					dir.parent = current
+
+					-- Track directory in both lookup table and parent's children
+					self.directories[path] = dir
+					table.insert(current.children, dir)
+				end
+
+				-- Move deeper into the tree
+				current = self.directories[path]
+				continue
+			end
+
+			-- Link file entry to its parent directory
+			entry.parent = current
+			table.insert(current.children, entry)
+		end
+	end
+end
+
+function ZipReader.findEntry(self: ZipReader, path: string): ZipEntry
+	if path == "/" then
+		-- If the root directory's entry was requested we do not
+		-- need to do any additional work
+		return self.root
+	end
+
+	-- Normalize path by removing leading and trailing slashes
+	-- This ensures consistent lookup regardless of input format
+	-- e.g., "/folder/file.txt/" -> "folder/file.txt"
+	path = string.gsub(path, "^/", ""):gsub("/$", "")
+
+	-- First check regular files and explicit directories
+	for _, entry in self.entries do
+		-- Compare normalized paths
+		if string.gsub(entry.name, "/$", "") == path then
+			return entry
+		end
+	end
+
+	-- If not found, check virtual directory entries
+	-- These are directories that were created implicitly
+	return self.directories[path]
+end
+
+type ExtractionOptions = {
+	decompress: boolean?,
+	isString: boolean?,
+	skipValidation: boolean?,
+}
+function ZipReader.extract(self: ZipReader, entry: ZipEntry, options: ExtractionOptions?): buffer | string
+	-- Local File Header format:
+	-- Offset  Bytes  Description
+	-- 0       4      Local file header signature
+	-- 8       2      Compression method (8 = DEFLATE)
+	-- 14      4      CRC32 checksume
+	-- 18      4      Compressed size
+	-- 22      4      Uncompressed size
+	-- 26      2      File name length (n)
+	-- 28      2      Extra field length (m)
+	-- 30      n      File name
+	-- 30+n    m      Extra field
+	-- 30+n+m  -      File data
+
+	if entry.isDirectory then
+		error("Cannot extract directory")
+	end
+
+	local defaultOptions: ExtractionOptions = {
+		decompress = true,
+		isString = false,
+		skipValidation = false,
+	}
+
+	-- TODO: Use a `Partial` type function for this in the future!
+	local optionsOrDefault: {
+		decompress: boolean,
+		isString: boolean,
+		skipValidation: boolean,
+	} = if options
+		then setmetatable(options, { __index = defaultOptions }) :: any
+		else defaultOptions
+
+	local pos = entry.offset
+	if buffer.readu32(self.data, pos) ~= SIGNATURES.LOCAL_FILE then
+		error("Invalid local file header")
+	end
+
+	local crcChecksum = buffer.readu32(self.data, pos + 14)
+	local compressedSize = buffer.readu32(self.data, pos + 18)
+	local uncompressedSize = buffer.readu32(self.data, pos + 22)
+	local nameLength = buffer.readu16(self.data, pos + 26)
+	local extraLength = buffer.readu16(self.data, pos + 28)
+
+	pos = pos + 30 + nameLength + extraLength
+
+	local content = buffer.create(compressedSize)
+	buffer.copy(content, 0, self.data, pos, compressedSize)
+
+	if optionsOrDefault.decompress then
+		local compressionMethod = buffer.readu16(self.data, entry.offset + 8)
+		local decompress = DECOMPRESSION_ROUTINES[compressionMethod]
+		if decompress == nil then
+			error(`Unsupported compression, ID: {compressionMethod}`)
+		end
+
+		content = decompress(content, {
+			expected = crcChecksum,
+			skip = optionsOrDefault.skipValidation,
+		})
+
+		-- Unless skipping validation is requested, we make sure the uncompressed size matches
+		assert(
+			optionsOrDefault.skipValidation or uncompressedSize == buffer.len(content),
+			"Validation failed; uncompressed size does not match"
+		)
+	end
+
+	return if optionsOrDefault.isString then buffer.tostring(content) else content
+end
+
+function ZipReader.extractDirectory(
+	self: ZipReader,
+	path: string,
+	options: ExtractionOptions
+): { [string]: buffer } | { [string]: string }
+	local files: { [string]: buffer } | { [string]: string } = {}
+	-- Normalize path by removing leading slash for consistent prefix matching
+	path = string.gsub(path, "^/", "")
+
+	-- Iterate through all entries to find files within target directory
+	for _, entry in self.entries do
+		-- Check if entry is a file (not directory) and its path starts with target directory
+		if not entry.isDirectory and string.sub(entry.name, 1, #path) == path then
+			-- Store extracted content mapped to full path
+			files[entry.name] = self:extract(entry, options)
+		end
+	end
+
+	-- Return a map of file to contents
+	return files
+end
+
+function ZipReader.listDirectory(self: ZipReader, path: string): { ZipEntry }
+	-- Locate the entry with the path
+	local entry = self:findEntry(path)
+	if not entry or not entry.isDirectory then
+		-- If an entry was not found, we error
+		error("Not a directory")
+	end
+
+	-- Return the children of our discovered entry
+	return entry.children
+end
+
+function ZipReader.walk(self: ZipReader, callback: (entry: ZipEntry, depth: number) -> ()): ()
+	-- Wrapper function which recursively calls callback for every child
+	-- in an entry
+	local function walkEntry(entry: ZipEntry, depth: number)
+		callback(entry, depth)
+
+		for _, child in entry.children do
+			-- ooo spooky recursion... blame this if shit go wrong
+			walkEntry(child, depth + 1)
+		end
+	end
+
+	walkEntry(self.root, 0)
+end
+
+export type ZipStatistics = { fileCount: number, dirCount: number, totalSize: number }
+function ZipReader.getStats(self: ZipReader): ZipStatistics
+	local stats: ZipStatistics = {
+		fileCount = 0,
+		dirCount = 0,
+		totalSize = 0,
+	}
+
+	-- Iterate through the entries, updating stats
+	for _, entry in self.entries do
+		if entry.isDirectory then
+			stats.dirCount = stats.dirCount + 1
+			continue
+		end
+
+		stats.fileCount = stats.fileCount + 1
+		stats.totalSize = stats.totalSize + entry.size
+	end
+
+	return stats
+end
+
+return {
+	-- Creates a `ZipReader` from a `buffer` of ZIP data.
+	load = function(data: buffer)
+		return ZipReader.new(data)
+	end,
+}
diff --git a/pesde.lock b/pesde.lock
index f0fbad0..4a45469 100644
--- a/pesde.lock
+++ b/pesde.lock
@@ -2,6 +2,80 @@ name = "0x5eal/unzip"
 version = "0.1.0"
 target = "luau"
 
+[graph."jiwonz/dirs"."0.1.2 lune"]
+resolved_ty = "standard"
+
+[graph."jiwonz/dirs"."0.1.2 lune".target]
+environment = "lune"
+lib = "src/init.luau"
+
+[graph."jiwonz/dirs"."0.1.2 lune".dependencies]
+"jiwonz/pathfs" = ["0.1.0 lune", "pathfs"]
+
+[graph."jiwonz/dirs"."0.1.2 lune".pkg_ref]
+ref_ty = "pesde"
+name = "jiwonz/dirs"
+version = "0.1.2"
+index_url = "https://github.com/daimond113/pesde-index"
+
+[graph."jiwonz/dirs"."0.1.2 lune".pkg_ref.dependencies]
+pathfs = [{ name = "jiwonz/pathfs", version = "^0.1.0", index = "https://github.com/daimond113/pesde-index" }, "standard"]
+
+[graph."jiwonz/dirs"."0.1.2 lune".pkg_ref.target]
+environment = "lune"
+lib = "src/init.luau"
+
+[graph."jiwonz/pathfs"."0.1.0 lune"]
+resolved_ty = "standard"
+
+[graph."jiwonz/pathfs"."0.1.0 lune".target]
+environment = "lune"
+lib = "init.luau"
+
+[graph."jiwonz/pathfs"."0.1.0 lune".pkg_ref]
+ref_ty = "pesde"
+name = "jiwonz/pathfs"
+version = "0.1.0"
+index_url = "https://github.com/daimond113/pesde-index"
+
+[graph."jiwonz/pathfs"."0.1.0 lune".pkg_ref.target]
+environment = "lune"
+lib = "init.luau"
+
+[graph."lukadev_0/option"."1.2.0 lune"]
+resolved_ty = "standard"
+
+[graph."lukadev_0/option"."1.2.0 lune".target]
+environment = "lune"
+lib = "lib/init.luau"
+
+[graph."lukadev_0/option"."1.2.0 lune".pkg_ref]
+ref_ty = "pesde"
+name = "lukadev_0/option"
+version = "1.2.0"
+index_url = "https://github.com/daimond113/pesde-index"
+
+[graph."lukadev_0/option"."1.2.0 lune".pkg_ref.target]
+environment = "lune"
+lib = "lib/init.luau"
+
+[graph."lukadev_0/result"."1.2.0 lune"]
+resolved_ty = "standard"
+
+[graph."lukadev_0/result"."1.2.0 lune".target]
+environment = "lune"
+lib = "lib/init.luau"
+
+[graph."lukadev_0/result"."1.2.0 lune".pkg_ref]
+ref_ty = "pesde"
+name = "lukadev_0/result"
+version = "1.2.0"
+index_url = "https://github.com/daimond113/pesde-index"
+
+[graph."lukadev_0/result"."1.2.0 lune".pkg_ref.target]
+environment = "lune"
+lib = "lib/init.luau"
+
 [graph."lukadev_0/result"."1.2.0 luau"]
 direct = ["result", { name = "lukadev_0/result", version = "^1.2.0" }, "standard"]
 resolved_ty = "standard"
@@ -19,3 +93,60 @@ index_url = "https://github.com/pesde-pkg/index"
 [graph."lukadev_0/result"."1.2.0 luau".pkg_ref.target]
 environment = "luau"
 lib = "lib/init.luau"
+
+[graph."pesde/stylua"."2.0.2 lune"]
+direct = ["stylua", { name = "pesde/stylua", version = "^2.0.2", target = "lune" }, "dev"]
+resolved_ty = "dev"
+
+[graph."pesde/stylua"."2.0.2 lune".target]
+environment = "lune"
+bin = "init.luau"
+
+[graph."pesde/stylua"."2.0.2 lune".dependencies]
+"lukadev_0/option" = ["1.2.0 lune", "option"]
+"lukadev_0/result" = ["1.2.0 lune", "result"]
+"pesde/toolchainlib" = ["0.1.7 lune", "core"]
+
+[graph."pesde/stylua"."2.0.2 lune".pkg_ref]
+ref_ty = "pesde"
+name = "pesde/stylua"
+version = "2.0.2"
+index_url = "https://github.com/pesde-pkg/index"
+
+[graph."pesde/stylua"."2.0.2 lune".pkg_ref.dependencies]
+core = [{ name = "pesde/toolchainlib", version = "^0.1.3", index = "https://github.com/daimond113/pesde-index", target = "lune" }, "standard"]
+option = [{ name = "lukadev_0/option", version = "^1.2.0", index = "https://github.com/daimond113/pesde-index" }, "standard"]
+result = [{ name = "lukadev_0/result", version = "^1.2.0", index = "https://github.com/daimond113/pesde-index" }, "standard"]
+
+[graph."pesde/stylua"."2.0.2 lune".pkg_ref.target]
+environment = "lune"
+bin = "init.luau"
+
+[graph."pesde/toolchainlib"."0.1.7 lune"]
+resolved_ty = "standard"
+
+[graph."pesde/toolchainlib"."0.1.7 lune".target]
+environment = "lune"
+lib = "src/init.luau"
+
+[graph."pesde/toolchainlib"."0.1.7 lune".dependencies]
+"jiwonz/dirs" = ["0.1.2 lune", "dirs"]
+"jiwonz/pathfs" = ["0.1.0 lune", "pathfs"]
+"lukadev_0/option" = ["1.2.0 lune", "option"]
+"lukadev_0/result" = ["1.2.0 lune", "result"]
+
+[graph."pesde/toolchainlib"."0.1.7 lune".pkg_ref]
+ref_ty = "pesde"
+name = "pesde/toolchainlib"
+version = "0.1.7"
+index_url = "https://github.com/daimond113/pesde-index"
+
+[graph."pesde/toolchainlib"."0.1.7 lune".pkg_ref.dependencies]
+dirs = [{ name = "jiwonz/dirs", version = "^0.1.1", index = "https://github.com/daimond113/pesde-index" }, "standard"]
+option = [{ name = "lukadev_0/option", version = "^1.2.0", index = "https://github.com/daimond113/pesde-index" }, "peer"]
+pathfs = [{ name = "jiwonz/pathfs", version = "^0.1.0", index = "https://github.com/daimond113/pesde-index" }, "standard"]
+result = [{ name = "lukadev_0/result", version = "^1.2.0", index = "https://github.com/daimond113/pesde-index" }, "peer"]
+
+[graph."pesde/toolchainlib"."0.1.7 lune".pkg_ref.target]
+environment = "lune"
+lib = "src/init.luau"
diff --git a/pesde.toml b/pesde.toml
index a1ee59e..557e5e9 100644
--- a/pesde.toml
+++ b/pesde.toml
@@ -13,3 +13,6 @@ default = "https://github.com/pesde-pkg/index"
 
 [dependencies]
 result = { name = "lukadev_0/result", version = "^1.2.0" }
+
+[dev_dependencies]
+stylua = { name = "pesde/stylua", version = "^2.0.2", target = "lune" }