216 lines
6.0 KiB
Lua
216 lines
6.0 KiB
Lua
|
|
-- This Lua code provides some built-in utilities for writing Lua add-ons
|
|
-- It is eval'd automatically once BLLua3 has loaded the TS API and environment
|
|
-- It only has access to the sandboxed lua environment, just like user code.
|
|
|
|
ts = _bllua_ts
|
|
|
|
-- Provide limited OS functions
|
|
os = os or {}
|
|
function os.time() return math.floor(tonumber(_bllua_ts.call('getSimTime'))/1000) end
|
|
function os.clock() return tonumber(_bllua_ts.call('getSimTime'))/1000 end
|
|
|
|
-- Virtual file class, emulating a file object as returned by io.open
|
|
-- Used to wrap io.open to allow reading from zips (using TS)
|
|
-- Not perfect because TS file I/O sucks
|
|
-- Can't read nulls, can't distinguish between CRLF and LF.
|
|
-- Todo someday: actually read the zip in lua?
|
|
local file_meta = {
|
|
read = function(file, mode)
|
|
file:_init()
|
|
if not file or type(file)~='table' or not file._is_file then error('File:read: Not a file', 2) end
|
|
if file._is_open ~= true then error('File:read: File is closed', 2) end
|
|
if mode=='*n' then
|
|
local ws, n = file.data:match('^([ \t\r\n]*)([0-9%.%-e]+)', file.pos)
|
|
if n then
|
|
file.pos = file.pos + #ws + #n
|
|
return n
|
|
else
|
|
return nil
|
|
end
|
|
elseif mode=='*a' then
|
|
local d = file.data:sub(file.pos, #file.data)
|
|
file.pos = #file.data + 1
|
|
return d
|
|
elseif mode=='*l' then
|
|
local l, ws = file.data:match('^([^\r\n]*)(\r?\n)', file.pos)
|
|
if not l then
|
|
l = file.data:match('^([^\r\n]*)$', file.pos); ws = '';
|
|
if l=='' then return nil end
|
|
end
|
|
if l then
|
|
file.pos = file.pos + #l + #ws
|
|
return l
|
|
else
|
|
return nil
|
|
end
|
|
elseif type(mode)=='number' then
|
|
local d = file.data:sub(file.pos, file.pos+mode)
|
|
file.pos = file.pos + #d
|
|
return d
|
|
else
|
|
error('File:read: Invalid mode \''..mode..'\'', 2)
|
|
end
|
|
end,
|
|
lines = function(file)
|
|
file:_init()
|
|
return function()
|
|
return file:read('*l')
|
|
end
|
|
end,
|
|
close = function(file)
|
|
if not file._is_open then error('File:close: File is not open', 2) end
|
|
file._is_open = false
|
|
end,
|
|
__index = function(f, k) return rawget(f, k) or getmetatable(f)[k] end,
|
|
_init = function(f)
|
|
if not f.data then
|
|
f.data = _bllua_ts.call('_bllua_ReadEntireFile', f.filename)
|
|
end
|
|
end,
|
|
}
|
|
local function new_file_obj(fn)
|
|
local file = {
|
|
_is_file = true,
|
|
_is_open = true,
|
|
pos = 1,
|
|
__index = file_meta.__index,
|
|
filename = fn,
|
|
data = nil,
|
|
}
|
|
setmetatable(file, file_meta)
|
|
return file
|
|
end
|
|
|
|
local function tflip(t) local u = {}; for _, n in ipairs(t) do u[n] = true end; return u; end
|
|
local allowed_zip_dirs = tflip{
|
|
'add-ons', 'base', 'config', 'saves', 'screenshots', 'shaders'
|
|
}
|
|
local function io_open_absolute(fn, mode)
|
|
-- if file exists, use original mode
|
|
local res, err = _bllua_io_open(fn, mode)
|
|
if res then return res
|
|
elseif err and not err:find('No such file or directory$') then return nil, err end
|
|
|
|
-- otherwise, if TS sees file but Lua doesn't, it must be in a zip, so use TS reader
|
|
local dir = fn:match('^[^/]+')
|
|
if not allowed_zip_dirs[dir:lower()] then return nil, 'Zip is not in one of the allowed directories' end
|
|
local exist = _bllua_ts.call('isFile', fn) == '1'
|
|
if not exist then return nil, err end
|
|
|
|
if mode~=nil and mode~='r' and mode~='rb' then
|
|
return nil, 'Files in zips can only be opened in read mode' end
|
|
|
|
-- return a temp lua file object with the data
|
|
local fi = new_file_obj(fn)
|
|
return fi
|
|
end
|
|
|
|
io = io or {}
|
|
function io.open(fn, mode, errn)
|
|
errn = errn or 1
|
|
|
|
-- try to open the file with relative path, otherwise use absolute path
|
|
local curfn = debug.getfilename(errn + 1) or _bllua_ts.getvar('Con::File')
|
|
if curfn == '' then curfn = nil end
|
|
if fn:find('^%.') then
|
|
local relfn = curfn and fn:find('^%./') and
|
|
curfn:gsub('[^/]+$', '')..fn:gsub('^%./', '')
|
|
if relfn then
|
|
local fi, err = io_open_absolute(relfn, mode)
|
|
return fi, err, relfn
|
|
else
|
|
return nil, 'Invalid path', fn
|
|
end
|
|
else
|
|
local fi, err = io_open_absolute(fn, mode)
|
|
return fi, err, fn
|
|
end
|
|
end
|
|
function io.lines(fn)
|
|
local fi, err, fn2 = io.open(fn, nil, 2)
|
|
if not fi then error('Error opening file \''..fn2..'\': '..err, 2) end
|
|
return fi:lines()
|
|
end
|
|
function io.type(f)
|
|
if type(f)=='table' and f._is_file then
|
|
return f._is_open and 'file' or 'closed file'
|
|
else
|
|
return _bllua_io_type(f)
|
|
end
|
|
end
|
|
|
|
-- provide dofile
|
|
function dofile(fn, errn)
|
|
errn = errn or 1
|
|
|
|
local fi, err, fn2 = io.open(fn, 'r', errn+1)
|
|
if not fi then error('Error executing file \''..fn2..'\': '..err, errn+1) end
|
|
|
|
print('Executing '..fn2)
|
|
local text = fi:read('*a')
|
|
fi:close()
|
|
return assert(loadstring('--[['..fn2..']]'..text))()
|
|
end
|
|
|
|
-- provide require (just a wrapper for dofile)
|
|
-- searches for ?.lua and ?/init.lua in the following directories:
|
|
-- location of current file
|
|
-- blockland directory
|
|
-- current add-on
|
|
local function file_exists(fn, errn)
|
|
local fi, err, fn2 = io.open(fn, 'r', errn+1)
|
|
if fi then
|
|
fi:close()
|
|
return fn2
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
local require_memo = {}
|
|
function require(mod)
|
|
if require_memo[mod] then return unpack(require_memo[mod]) end
|
|
local fp = mod:gsub('%.', '/')
|
|
local fns = {
|
|
'./'..fp..'.lua', -- local file
|
|
'./'..fp..'/init.lua', -- local library
|
|
fp..'.lua', -- global file
|
|
fp..'/init.lua', -- global library
|
|
}
|
|
if fp:lower():find('^add-ons/') then
|
|
local addonpath = fp:lower():match('^add-ons/[^/]+')..'/'
|
|
table.insert(fns, addonpath..fp..'.lua') -- add-on file
|
|
table.insert(fns, addonpath..fp..'/init.lua') -- add-on library
|
|
end
|
|
for _,fn in ipairs(fns) do
|
|
local fne = file_exists(fn, 2)
|
|
if fne then
|
|
local res = {dofile(fne, 2)}
|
|
require_memo[mod] = res
|
|
return unpack(res)
|
|
end
|
|
end
|
|
return _bllua_requiresecure(mod)
|
|
end
|
|
|
|
local function isValidCode(code)
|
|
local f,e = loadstring(code)
|
|
return f~=nil
|
|
end
|
|
function _bllua_smarteval(code)
|
|
if (not code:find('^print%(')) and isValidCode('print('..code..')') then
|
|
code = 'print('..code..')' end
|
|
local f,e = loadstring(code)
|
|
if f then
|
|
return f()
|
|
else
|
|
print(e)
|
|
end
|
|
end
|
|
|
|
function ts.setvar(name, val)
|
|
_bllua_ts.call('_bllua_set_var', name, val)
|
|
end
|
|
|
|
_bllua_ts.call('echo', ' Executed libts-lua.lua')
|