Zorya-NEO/lib/util_urf/init.lua

324 lines
6.8 KiB
Lua

-- P A I N
local flag_crit = 1 << 5
local flag_required = 1 << 6
local flag_ext = 1 << 7
local function read_ali(fs, h)
local tmp = 0
local ctr = 0
while true do
local b = fs.read(h, 1):byte()
tmp = tmp | ((b & 0x7F) << (ctr*7))
if (b & 0x80 > 0) then
break
end
end
return tmp
end
local function read_int(fs, h, count)
local tmp = 0
for i=0, count-1 do
tmp = tmp | (fs.read(h, 1):byte() << (count*8))
end
return tmp
end
local function read_string(fs, h)
local len = read_ali(fs, h)
return fs.read(h, len)
end
local xdec = {
["NAME"] = function(fs, h)
return read_string(fs, h)
end,
["PERM"] = function(fs, h)
return read_int(fs, h, 2)
end,
["W32P"] = function(fs, h)
return fs.read(h, 1):byte()
end,
["OTIM"] = function(fs, h)
return read_int(fs, h, 8)
end,
["MTIM"] = function(fs, h)
return read_int(fs, h, 8)
end,
["CTIM"] = function(fs, h)
return read_int(fs, h, 8)
end,
["ATIM"] = function(fs, h)
return read_int(fs, h, 8)
end,
["SCOS"] = function(fs, h)
return read_string(fs, h)
end
}
local decode = {
["X"] = function(fs, h, size)
local oid = read_ali(fs, h)
local xtype = fs.read(h, 4)
return {
type = "meta",
id = oid,
key = xtype,
value = xdec[xtype](fs, h)
}
end,
["F"] = function(fs, h, size)
return {
type="file",
name = read_string(fs, h),
offset = read_ali(fs, h),
size = read_ali(fs, h),
id = read_ali(fs, h),
pid = read_ali(fs, h)
}
end,
["D"] = function(fs, h, size)
return {
type="dir",
name = read_string(fs, h),
id = read_ali(fs, h),
pid = read_ali(fs, h)
}
end,
["Z"] = function(fs, h, size)
return {
type="eoh",
size = read_ali(fs, h)
}
end
}
local function read_entrydat(fs, h)
local etype = fs.read(h, 1)
local ebyte = etype:byte()
local size = read_ali(fs, h)
if (ebyte & flag_crit > 0 and not decode[etype]) then error("Unknown critical entry.") end
if (ebyte & flag_required == 0) then error("Required flag not set.") end
if (decode[etype]) then
return decode[etype](fs, h, size)
end
return {type="unknown"}
end
local function get_offset(fs, h)
fs.seek(h, "set", 9)
local ent = {}
while ent.type ~= "eoh" do
ent = read_entrydat(fs, h)
end
return fs.seek(h, "cur", 0), ent.size
end
local function path_part_iter(path)
path = path:gsub("/+", "/"):gsub("^/", ""):gsub("/$", "")
local lpos = 1
return function()
if not lpos then return nil end
local s, e = path:find("(.-)/", lpos)
if not s then
local lp = lpos
lpos = nil
return path:sub(lp)
end
local rtn = path:sub(s, e-1)
lpos = e+1
return rtn
end
end
local unpack = unpack or table.unpack
local function sfpcall(a, func, ...)
local ptr = a.fs.seek(a.h, "cur", 0)
local rv = {func(...)}
a.fs.seek(a.h, "set", ptr)
return unpack(rv)
end
local function cache_obj(a, objdat)
a.cache = a.cache or {}
a.cache[#a.cache+1] = objdat
end
local function get_obj_prop(a, obj, name)
name = name:upper()
if (a.cache) then
for i=1, #a.cache do
if (a.cache[i].type=="meta" and a.cache[i].id==obj and a.cache[i].key == name) then return a.cache[i].value end
end
end
fs.seek(h, "set", 9)
local ent = {}
while ent.type ~= "eoh" do
ent = read_entrydat(fs, h)
if (ent.type=="meta" and ent.id==obj and ent.key == name) then
cache_obj(a, ent)
return ent.value
end
end
end
local function path_to_obj(a, path)
if (path == "/" or path == "") then
return {
type = "dir",
name = "/root\\",
id = 0,
pid = 0
}
end
local tbl = {}
for p in path_part_iter(path) do
tbl[#tbl+1] = p
end
local part = 1
local pid = 0
--search cache
if (a.cache) then
local i = 0
while i < #a.cache do
i = i+1
if (a.cache[i].type == "file" or a.cache[i].type == "dir") then
if (a.cache[i].pid == pid and (a.cache[i].name == tbl[part] or get_obj_prop(a, a.cache[i].id, "NAME") == tbl[part])) then
part = part+1
pid = a.cache[i].id
i=0
if (part == #tbl) then
return a.cache[i], pid
end
end
end
end
end
-- Now we search the hard way, then cache along the way.
fs.seek(h, "set", 9)
local ent = {}
while ent.type ~= "eoh" do
ent = read_entrydat(fs, h)
if (ent.type == "file" or ent.type=="dir") then
if (ent.name == tbl[part] or sfpcall(get_obj_prop, a, ent.id, "NAME") == tbl[part]) then
pid = ent.id
cache_obj(a, ent)
part = part + 1
end
if (part == #tbl) then
break
end
end
end
return ent, pid
end
local function get_by_id(a, id)
if (a.cache) then
for i=1, #a.cache do
if ((a.cache[i].type=="file" or a.cache[i].type=="dir") and a.cache[i].id==id) then return a.cache[i] end
end
end
fs.seek(h, "set", 9)
local ent = {}
while ent.type ~= "eoh" do
ent = read_entrydat(fs, h)
if ((ent.type=="file" or ent.type=="dir") and ent.id==id) then
cache_obj(a, ent)
return ent
end
end
end
local function id_to_path(a, id)
local ne = get_by_id(a, id)
if not ne then return nil, "not found" end
local path = get_obj_prop(a, id, "NAME") or ne.name
path = "/" .. path
while ne and ne.pid > 0 do
ne = get_by_id(a, ne.pid)
if not ne then return nil, "not found" end
path = "/" .. (get_obj_prop(a, ne.id, "NAME") or ne.name) .. path
end
return path
end
local urf = {}
function urf.open(drive, path)
local fs = component.proxy(drive)
local hand = fs.open(path)
if fs.read(hand, 8) ~= "URF\x11\1\1\x12\0" then return nil, "bad signature" end
local e, z = get_offset(fs, hand)
return setmetatable({
fs = fs,
h = hand,
cache = {},
epos = e,
bsize = z
}, {__index=arc})
end
local arc = {}
function arc:fetch(path)
local obj = path_to_obj(self, path)
if (obj.type ~= "file") then return nil, "not a file" end
self.fs.seek(self.h, "set", self.epos+obj.offset)
return self.fs.read(self.h, obj.size)
end
function arc:exists(path)
local obj = path_to_obj(path)
return obj.type ~= "eoh"
end
function arc:close()
self.cache = nil
self.fs.close(self.h)
self.fs = nil
end
function arc:list_dir(path)
local obj = path_to_obj(self, path)
if (obj.type ~= "dir") then return nil, "not a dir" end
local objects = {}
for i=1, self.cache do
if (self.cache[i].pid == obj.id) then
objects[#objects+1] = (get_obj_prop(self, self.cache[i].id, "NAME") or self.cache[i].name)
end
end
self.fs.seek(self.h, "set", 9)
local ent = {}
while ent.type ~= "eoh" do
ent = read_entrydat(self.fs, self.h)
if ((ent.type=="file" or ent.type=="dir") and ent.pid==obj.id) then
cache_obj(self, ent)
objects[#objects+1] = sfpcall(get_obj_prop, self, ent.id, "NAME") or ent.name
end
end
return objects
end
function arc:stream(path)
local obj = path_to_obj(self, path)
local fpos = self.epos+obj.offset
local pos = 1
local function read(amt)
self.seek(self.tbl[i].pos-self.seek(0)+pos-1)
pos = pos + amt
return self.read(amt)
end
local function seek(amt)
pos = pos + amt
return pos
end
local function close()end
return read, seek, close
return nil, "file not found"
end
return urf