mirror of
https://github.com/Adorable-Catgirl/Zorya-NEO.git
synced 2024-11-23 18:58:06 +11:00
324 lines
6.8 KiB
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 |