napkin/bitpak.lua

156 lines
3.3 KiB
Lua

local bitpak = {}
bitpak.__index = bitpak
local ffi = require("ffi")
local bit = require("bit")
ffi.cdef [[
typedef struct {
char magic[8]; // BITPAK01
size_t chunk_size;
size_t offset;
} BitpakHeader;
]]
bitpak.load = function(filename)
local f = io.open(filename, "rb")
if not f then
return nil, "Could not read file"
end
local header_data = f:read(ffi.sizeof("BitpakHeader"))
if
not header_data
or #header_data < ffi.sizeof("BitpakHeader")
then
f:close()
return nil, "Invalid or corrupted file header"
end
local header = ffi.new("BitpakHeader")
ffi.copy(header, header_data, ffi.sizeof("BitpakHeader"))
-- Check magic signature
if ffi.string(header.magic, 8) ~= "BITPAK01" then
f:close()
return nil, "Invalid file format"
end
local str = f:read("*a")
f:close()
local data = ffi.new("unsigned char[?]", #str)
ffi.copy(data, str)
return setmetatable({
data = data,
header = header
}, bitpak)
end
bitpak.create = function(setup)
setup = setup or {}
local chunk_size = setup.chunk_size or 16
local header = ffi.new("BitpakHeader", {
magic = "BITPAK01",
chunk_size = chunk_size,
offset = 0
})
local data = ffi.new("unsigned char[?]", chunk_size)
return setmetatable({
header = header,
data = data
}, bitpak)
end
bitpak.save = function(self, filename)
local f = io.open(filename, "wb")
if not f then
return nil, "Could not open file"
end
f:write(
ffi.string(
ffi.cast("const char *", self.header),
ffi.sizeof(self.header)
)
)
f:write(
ffi.string(
self.data, ffi.sizeof(self.data)
)
)
f:close()
return true
end
local find_and_set_bit = function(self, i)
local byte = self.data[i]
if byte == 0xff then
return false
end
local bit_pos = 0
if math.random(2) == 1 then
while bit.band(byte, 1) == 1 do
byte = bit.rshift(byte, 1)
bit_pos = bit_pos + 1
end
else
bit_pos = 7
while bit.band(byte, 128) == 128 do
byte = bit.lshift(byte, 1)
bit_pos = bit_pos - 1
end
end
self.data[i] = bit.bor(
self.data[i], bit.lshift(1, bit_pos)
)
return (i * 8) + bit_pos
end
bitpak.allocate = function(self, attempts, no_grow)
local chunk_size = tonumber(self.header.chunk_size)
local offset = tonumber(self.header.offset)
local length = ffi.sizeof(self.data)
attempts = attempts or math.floor(chunk_size)
for x = 1, attempts do
local i = math.random(offset, length)
local c = find_and_set_bit(self, i)
if c then
return c
end
end
for x = offset, length do
local c = find_and_set_bit(self, x)
if c then
return c
end
end
if no_grow then
return nil, "No free space"
end
local new_data = ffi.new("unsigned char[?]", length + chunk_size)
ffi.copy(new_data, self.data, length)
self.data = new_data
self.header.offset = offset + chunk_size
return self:allocate(attempts, no_grow)
end
return bitpak