156 lines
3.3 KiB
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
|